Android输入法框架系统(下)

程序焦点获取事件导致输入法显示

         从上面可以知道程序获得焦点时,程序端会先间接的调用IMMS的startInput将焦点View绑定到输入法,然后会调用IMMS的windowGainFocus函数,这个函数就可能显示输入法, 是否显示输入法由焦点view的属性决定。过程流程图如下:

 

代码处理逻辑如下:

  1.  //ViewRootImpl.java  
  2.        case MSG_WINDOW_FOCUS_CHANGED: {  
  3.                if (hasWindowFocus) {  
  4.                    if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {  
  5.                        imm.onWindowFocus(mView, mView.findFocus(),  
  6.                                mWindowAttributes.softInputMode,  
  7.                                !mHasHadWindowFocus, mWindowAttributes.flags);  
  8.                    }  
  9.                }  
  10.        }  
  11.   
  12. //InputMethodManager  
  13. public void onWindowFocus(View rootView, View focusedView, int softInputMode,  
  14.        boolean first, int windowFlags) {  
  15.    boolean forceNewFocus = false;  
  16.    synchronized (mH) {  
  17.        //和上面view获取焦点事件的处理一样  
  18.        focusInLocked(focusedView != null ? focusedView : rootView);  
  19.    }  
  20.    //确认当前focused view是否已经调用过startInputInner来绑定输入法  
  21.    //因为在前面mView.dispatchWindowFocusChanged处理过程focused view已经完成  
  22.    //了绑定,所以大部分情况下,该函数返回false,即不会再次调用startInputInner  
  23.    if (checkFocusNoStartInput(forceNewFocus, true)) {  
  24.        if (startInputInner(rootView.getWindowToken(),  
  25.                controlFlags, softInputMode, windowFlags)) {  
  26.            return;  
  27.        }  
  28.    }  
  29.   
  30.    synchronized (mH) {  
  31.        try {  
  32.            //调用IMMS windowGainedFocus函数  
  33.            mService.windowGainedFocus(mClient, rootView.getWindowToken(),  
  34.                    controlFlags, softInputMode, windowFlags, nullnull);  
  35.        } catch (RemoteException e) {  
  36.        }  
  37.    }  

输入法响应显示请求

         从上面可以看出,输入法响应显示请求是通过IInputMethod,而这个是在输入法service完成启动通过onBind接口传递过去的,所以我们先来看下这个IInputMethod的实现是什么?

         输入法service都是继承InputMethodService类

  1. public class InputMethodService extends AbstractInputMethodService {  
  2.     @Override  
  3.     public AbstractInputMethodImpl onCreateInputMethodInterface() {  
  4.         return new InputMethodImpl();  
  5.     }  
  6. }  
  7.   
  8. public abstract class AbstractInputMethodService extends Service  
  9.         implements KeyEvent.Callback {  
  10.     private InputMethod mInputMethod;  
  11.     @Override  
  12.     final public IBinder onBind(Intent intent) {  
  13.         if (mInputMethod == null) {  
  14.             mInputMethod = onCreateInputMethodInterface();  
  15.         }  
  16.         return new IInputMethodWrapper(this, mInputMethod);  
  17. }  
  18. }  

        从上可见IMMS保存的IInputMethod的实现是封装了InputMethodImpl的类IInputMethodWrapper,那肯定就是它负责处理消息MSG_SHOW_SOFT_INPUT,处理逻辑如下。

      

  1.     public IInputMethodWrapper(AbstractInputMethodService context,  
  2.             InputMethod inputMethod) {  
  3.         mTarget = new WeakReference<AbstractInputMethodService>(context);  
  4.         mCaller = new HandlerCaller(context.getApplicationContext(), null,  
  5.                 thistrue /*asyncHandler*/);  
  6.         mInputMethod = new WeakReference<InputMethod>(inputMethod);  
  7.         mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;  
  8.     }  
  9.   
  10.     public InputMethod getInternalInputMethod() {  
  11.         return mInputMethod.get();  
  12.     }  
  13.   
  14.     @Override  
  15.     public void executeMessage(Message msg) {  
  16.         InputMethod inputMethod = mInputMethod.get();  
  17.         switch (msg.what) {  
  18.             case DO_SHOW_SOFT_INPUT:  
  19.                 //这个inputMethod是通过onCreateInputMethodInterface函数创建的  
  20.                 //InputMethodImpl对象  
  21.                 inputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj);  
  22.                 return;  
  23.         }  
  24.     }  
  25.   
  26.     public class InputMethodImpl extends AbstractInputMethodImpl {  
  27.         public void showSoftInput(int flags, ResultReceiver resultReceiver) {  
  28.             boolean wasVis = isInputViewShown();  
  29.             mShowInputFlags = 0;  
  30.             if (onShowInputRequested(flags, false)) {  
  31.                 try {  
  32.                     //这个是真正显示UI的函数  
  33.                     showWindow(true);  
  34.                 }  
  35.             }  
  36.         }  
  37.     }  
  38.   
  39.     public class InputMethodService extends AbstractInputMethodService {  
  40.   
  41.     @Override public void onCreate() {  
  42.         mTheme = Resources.selectSystemTheme(mTheme,  
  43.                 getApplicationInfo().targetSdkVersion,  
  44.                 android.R.style.Theme_InputMethod,  
  45.                 android.R.style.Theme_Holo_InputMethod,  
  46.                 android.R.style.Theme_DeviceDefault_InputMethod);  
  47.         // SoftInputWindow就是大家一般用的Dialog的子类  
  48.         mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);  
  49.         initViews();  
  50.         mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);  
  51.     }  
  52.   
  53.     public void showWindow(boolean showInput) {          
  54.         try {  
  55.             mWindowWasVisible = mWindowVisible;  
  56.             mInShowWindow = true;  
  57.             showWindowInner(showInput);  
  58.         } finally {  
  59.             mWindowWasVisible = true;  
  60.             mInShowWindow = false;  
  61.         }  
  62.     }  
  63.       
  64.     void showWindowInner(boolean showInput) {  
  65.         initialize();  
  66.         updateFullscreenMode();  
  67.         //这个函数会创建输入法的键盘  
  68.         updateInputViewShown();  
  69.           
  70.         if (!mWindowAdded || !mWindowCreated) {  
  71.             mWindowAdded = true;  
  72.             mWindowCreated = true;  
  73.             initialize();  
  74.             //创建输入法dialog里的词条选择View  
  75.             View v = onCreateCandidatesView();  
  76.             if (v != null) {  
  77.                 setCandidatesView(v);  
  78.             }  
  79.         }  
  80.         if (mShowInputRequested) {  
  81.             if (!mInputViewStarted) {  
  82.                 mInputViewStarted = true;  
  83.                 onStartInputView(mInputEditorInfo, false);  
  84.             }  
  85.         } else if (!mCandidatesViewStarted) {  
  86.             mCandidatesViewStarted = true;  
  87.             onStartCandidatesView(mInputEditorInfo, false);  
  88.         }  
  89.         if (!wasVisible) {  
  90.             mImm.setImeWindowStatus(mToken, IME_ACTIVE, mBackDisposition);  
  91.             onWindowShown();  
  92.             //这个是Dialog的window,这里开始就显示UI了  
  93.             mWindow.show();  
  94.         }  
  95.     }  
  96.   
  97.     public void updateInputViewShown() {  
  98.         boolean isShown = mShowInputRequested && onEvaluateInputViewShown();  
  99.         if (mIsInputViewShown != isShown && mWindowVisible) {  
  100.             mIsInputViewShown = isShown;  
  101.             mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE);  
  102.             if (mInputView == null) {  
  103.                 initialize();  
  104.                 //这个是核心view,创建显示键盘的根view  
  105.                 View v = onCreateInputView();  
  106.                 if (v != null) {  
  107.                     setInputView(v);  
  108.                 }  
  109.             }  
  110.         }  
  111.     }  
  112. }  

用户单击输入框View导致输入法显示

        在上一篇InputChannel章节我们说到,事件传递到程序端,最后让ViewPostImeInputStage来处。处理逻辑如下:


         

  1. final class ViewPostImeInputStage extends InputStage {  
  2.     public ViewPostImeInputStage(InputStage next) {  
  3.         super(next);  
  4.     }  
  5.   
  6.     @Override  
  7.     protected int onProcess(QueuedInputEvent q) {  
  8.         if (q.mEvent instanceof KeyEvent) {  
  9.         } else {  
  10.             final int source = q.mEvent.getSource();  
  11.             if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {  
  12.                 //处理touch事件  
  13.                 return processPointerEvent(q);  
  14.             }  
  15.         }  
  16.     }  
  17.   
  18.     private int processPointerEvent(QueuedInputEvent q) {  
  19.         final MotionEvent event = (MotionEvent)q.mEvent;  
  20.   
  21.         if (mView.dispatchPointerEvent(event)) {  
  22.             return FINISH_HANDLED;  
  23.         }  
  24.         return FORWARD;  
  25.     }  
  26. }  

         从上可知最后会调用DecorView的dispatchPointerEvent,DecorView也是一个view,所以该函数其实就是View的dispatchPointerEvent函数。

  1.  //View.java  
  2.  public final boolean dispatchPointerEvent(MotionEvent event) {  
  3.      if (event.isTouchEvent()) {  
  4.          return dispatchTouchEvent(event);  
  5.      }  
  6. }  
  7.  //DecorView又是一个ViewGroup,所以会调用ViewGroup的dispatchTouchEvent  
  8.  //ViewGroup.java  
  9.  public boolean dispatchTouchEvent(MotionEvent ev) {  
  10.      if (mInputEventConsistencyVerifier != null) {  
  11.          mInputEventConsistencyVerifier.onTouchEvent(ev, 1);  
  12.      }  
  13.   
  14.      boolean handled = false;  
  15.      if (onFilterTouchEventForSecurity(ev)) {  
  16.          final int action = ev.getAction();  
  17.          final int actionMasked = action & MotionEvent.ACTION_MASK;  
  18.   
  19.          // Handle an initial down.  
  20.          if (actionMasked == MotionEvent.ACTION_DOWN) {  
  21.              // Throw away all previous state when starting a new touch gesture.  
  22.              // The framework may have dropped the up or cancel event for the previous gesture  
  23.              // due to an app switch, ANR, or some other state change.  
  24.              cancelAndClearTouchTargets(ev);  
  25.              resetTouchState();  
  26.          }  
  27.   
  28.          // Check for interception.  
  29.          final boolean intercepted;  
  30.          if (actionMasked == MotionEvent.ACTION_DOWN  
  31.                  || mFirstTouchTarget != null) {  
  32.              final boolean disallowIntercept = (mGroupFlags &  
  33. AG_DISALLOW_INTERCEPT) != 0;  
  34.              if (!disallowIntercept) {  
  35.                  //先给该view一个处理事件的机会,如果Intercept,则事件不会往  
  36.                  //下发送  
  37.                  intercepted = onInterceptTouchEvent(ev);  
  38.                  ev.setAction(action); // restore action in case it was changed  
  39.              } else {  
  40.                  intercepted = false;  
  41.              }  
  42.          } else {  
  43.              // There are no touch targets and this action is not an initial down  
  44.              // so this view group continues to intercept touches.  
  45.              intercepted = true;  
  46.          }  
  47.          //按照冒泡法,将触摸事件传递给每个child处理  
  48.          if (mFirstTouchTarget != null) {  
  49.              // Dispatch to touch targets, excluding the new touch target if we already  
  50.              // dispatched to it.  Cancel touch targets if necessary.  
  51.              TouchTarget predecessor = null;  
  52.              TouchTarget target = mFirstTouchTarget;  
  53.              while (target != null) {  
  54.                  final TouchTarget next = target.next;  
  55.                  if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {  
  56.                      handled = true;  
  57.                  } else {  
  58.                      final boolean cancelChild = resetCancelNextUpFlag(target.child)  
  59.                              || intercepted;  
  60.                      //真正处理函数  
  61.                      if (dispatchTransformedTouchEvent(ev, cancelChild,  
  62.                              target.child, target.pointerIdBits)) {  
  63.                          handled = true;  
  64.                      }  
  65.                      if (cancelChild) {  
  66.                          if (predecessor == null) {  
  67.                              mFirstTouchTarget = next;  
  68.                          } else {  
  69.                              predecessor.next = next;  
  70.                          }  
  71.                          target.recycle();  
  72.                          target = next;  
  73.                          continue;  
  74.                      }  
  75.                  }  
  76.                  predecessor = target;  
  77.                  target = next;  
  78.              }  
  79.          }  
  80.      }  
  81.      return handled;  
  82.  }  
  83.  private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,  
  84.          View child, int desiredPointerIdBits) {  
  85.      // child == null意味着该parent已经调用完所有的child的dispatchTouchEvent  
  86.      //所以从这里可以看出是child优先处理触摸事件的  
  87.      if (child == null) {  
  88.          handled = super.dispatchTouchEvent(transformedEvent);  
  89.      } else {  
  90.          handled = child.dispatchTouchEvent(transformedEvent);  
  91.      }  
  92.      return handled;  
  93.  }  
  94.  //这里的child如果仍就是一个ViewGroup,则和上面的逻辑一样。如果是一般的view,则  
  95.  //直接调用view. dispatchTouchEvent  
  96.  public boolean dispatchTouchEvent(MotionEvent event) {  
  97.      if (onFilterTouchEventForSecurity(event)) {  
  98.          //这个就是我们常使用view.setOnTouchListener调用保存下来的信息  
  99.          ListenerInfo li = mListenerInfo;  
  100.          if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
  101.                  && li.mOnTouchListener.onTouch(this, event)) {  
  102.              return true;  
  103.          }  
  104.          //view的默认处理,即调用onTouchEvent函数  
  105.          if (onTouchEvent(event)) {  
  106.              return true;  
  107.          }  
  108.      }  
  109.      return false;  
  110. }  
  111.   
  112.  //TextView.java  
  113.  @Override  
  114.  public boolean onTouchEvent(MotionEvent event) {  
  115.     //非TextView只会执行View. onTouchEvent,该函数是另一种将view和输入法绑定的调用  
  116.     //而TextView会调用imm.showSoftInput会显示输入法  
  117.      final boolean superResult = super.onTouchEvent(event);  
  118.       if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()  
  119.              && mText instanceof Spannable && mLayout != null) {  
  120.          if (touchIsFinished && (isTextEditable() || textIsSelectable)) {  
  121.              // Show the IME, except when selecting in read-only text.  
  122.              final InputMethodManager imm = InputMethodManager.peekInstance();  
  123.              viewClicked(imm);  
  124.              //这个是真正显示输入法的调用  
  125.              if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {  
  126.                  handled |= imm != null && imm.showSoftInput(this0);  
  127.              }  
  128.              handled = true;  
  129.          }  
  130.   
  131.          if (handled) {  
  132.              return true;  
  133.          }  
  134.      }  
  135.   
  136.      return superResult;  
  137. }  
  138.   
  139. //View.java的onTouchEvent  
  140.  public boolean onTouchEvent(MotionEvent event) {  
  141.      final int viewFlags = mViewFlags;  
  142.   
  143.      if (((viewFlags & CLICKABLE) == CLICKABLE ||  
  144.              (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
  145.          switch (event.getAction()) {  
  146.              case MotionEvent.ACTION_UP:  
  147.                  boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;  
  148.                  if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {  
  149.                      // take focus if we don't have it already and we should in  
  150.                      // touch mode.  
  151.                      boolean focusTaken = false;  
  152.                      //让view获得焦点  
  153.                      if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
  154.                          focusTaken = requestFocus();  
  155.                      }  
  156.                  }  
  157.                  break;  
  158.          }  
  159.          return true;  
  160.      }  
  161.   
  162.      return false;  
  163.  }  
  164.   
  165.  public boolean requestFocus(int direction, Rect previouslyFocusedRect) {  
  166.      return requestFocusNoSearch(direction, previouslyFocusedRect);  
  167.  }  
  168.   
  169.  private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {  
  170.      // 该view必须是可以获取焦点的  
  171.      if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE ||  
  172.              (mViewFlags & VISIBILITY_MASK) != VISIBLE) {  
  173.          return false;  
  174.      }  
  175.   
  176.      // 这个检查得到对象大家可能经常用过,就是这个属性  
  177.      //android:descendantFocusability=”blocksDescendants”,这个属性可以解决listView  
  178.     //等容器类View没法获取点击事件问题,它的实现就在此,当父亲设置了这个属性  
  179.      //子view就没法获取焦点了  
  180.      if (hasAncestorThatBlocksDescendantFocus()) {  
  181.          return false;  
  182.      }  
  183.      //获取焦点处理逻辑  
  184.      handleFocusGainInternal(direction, previouslyFocusedRect);  
  185.      return true;  
  186.  }  
  187.   
  188.  void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {  
  189.      if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {  
  190.          mPrivateFlags |= PFLAG_FOCUSED;  
  191.   
  192.          View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;  
  193.          //由于当前焦点view没法知道旧的焦点view,没法告知旧的焦点view失去焦点  
  194.          //所以必须叫父亲去做这个事情  
  195.          if (mP arent != null) {  
  196.              mParent.requestChildFocus(thisthis);  
  197.          }  
  198.          //这个函数很重要,编辑类view(比如TextEditor)和普通view的差别就在此  
  199.          //和输入法相关的处理也在此  
  200.          onFocusChanged(true, direction, previouslyFocusedRect);  
  201.          refreshDrawableState();  
  202.      }  
  203. }  
  204. //基类View的处理:  
  205.  protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {  
  206.      InputMethodManager imm = InputMethodManager.peekInstance();  
  207.      if (!gainFocus) {  
  208.      } else if (imm != null && mAttachInfo != null  
  209.              && mAttachInfo.mHasWindowFocus) {  
  210.          //通知IMMS该view获得了焦点,到此,这后面的逻辑就和上面的window获  
  211.          //得焦点导致view和输入法绑定的逻辑一样了  
  212.          imm.focusIn(this);  
  213.      }  
  214.  }  


输入法传递输入文本信息给view

        输入法如何获得输入文本信息通信接口

        从上面的输入法绑定的分析中可以知道,输入法其startInput接口被调用的时候获得了文本信息通信接口,这个通信接口是IInputContext的封装InputConnection,获取点如下:

  1. //InputMethodService.java  
  2. void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {  
  3.     if (!restarting) {  
  4.         doFinishInput();  
  5.     }  
  6.     mInputStarted = true;  
  7.     //这个就是通信接口  
  8.     mStartedInputConnection = ic;  
  9. }  
  10. public InputConnection getCurrentInputConnection() {  
  11.     InputConnection ic = mStartedInputConnection;  
  12.     if (ic != null) {  
  13.         return ic;  
  14.     }  
  15.     return mInputConnection;  
  16. }  

        输入法如何传递文本信息给view   

         从上可见,输入法要传递文本信息时,肯定是先调用getCurrentInputConnection拿到接口,然后再传递信息,我们以pinyin输入法的实现来解释这个过程。

         Pinyin输入法传递输入信息最后都会调用到sendKeyChar函数

        

  1. public void sendKeyChar(char charCode) {  
  2.     switch (charCode) {  
  3.         case ' '// Apps may be listening to an enter key to perform an action  
  4.             if (!sendDefaultEditorAction(true)) {  
  5.                 sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);  
  6.             }  
  7.             break;  
  8.         default:  
  9.             // Make sure that digits go through any text watcher on the client side.  
  10.             if (charCode >= '0' && charCode <= '9') {  
  11.                 sendDownUpKeyEvents(charCode - '0' + KeyEvent.KEYCODE_0);  
  12.             } else {  
  13.                 InputConnection ic = getCurrentInputConnection();  
  14.                 if (ic != null) {  
  15.                     //这个是真正传递信息到view的跨进程接口  
  16.                     ic.commitText(String.valueOf((char) charCode), 1);  
  17.                 }  
  18.             }  
  19.             break;  
  20.     }  
  21. }  

View接收输入文本信息

         从上面可知,输入法端最后会通过InputConnection逻辑来传递文本信息,那程序view端的InputConnection是如何创建的呢?

          

  1. //InputMethodManager.java  
  2. boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode,  
  3.     EditorInfo tba = new EditorInfo();  
  4.     tba.packageName = view.getContext().getPackageName();  
  5.     tba.fieldId = view.getId();  
  6.     //由具体的view创建  
  7.     InputConnection ic = view.onCreateInputConnection(tba);  
  8.     return true;  
  9. }  
  10. //我们先看下textView会创建怎样的InputConnection?  
  11. //TextView.java  
  12. @Override  
  13. public InputConnection onCreateInputConnection(EditorInfo outAttrs) {  
  14.   {  
  15.         outAttrs.hintText = mHint;  
  16.         if (mText instanceof Editable) {  
  17.             //露面了,是 EditableInputConnection, textView作为参数传入  
  18.             InputConnection ic = new EditableInputConnection(this);  
  19.             return ic;  
  20.         }  
  21.     }  
  22.     return null;  
  23. }  
      接下来肯定是EditableInputConnection 接收文本消息了
  1. public class EditableInputConnection extends BaseInputConnection {  
  2.     //该函数很重要,super.commitText会将字符添加到Editable里  
  3.     @Override  
  4.     public Editable getEditable() {  
  5.         TextView tv = mTextView;  
  6.         if (tv != null) {  
  7.             return tv.getEditableText();  
  8.         }  
  9.         return null;  
  10.     }  
  11.   
  12.     @Override  
  13.     public boolean commitText(CharSequence text, int newCursorPosition) {  
  14.         mTextView.resetErrorChangedFlag();  
  15.         //调用父类的方法  
  16.         boolean success = super.commitText(text, newCursorPosition);  
  17.         mTextView.hideErrorIfUnchanged();  
  18.   
  19.         return success;  
  20.     }  
  21. }  
  22.   
  23. public class BaseInputConnection implements InputConnection {  
  24.     public boolean commitText(CharSequence text, int newCursorPosition) {  
  25.         replaceText(text, newCursorPosition, false);  
  26.         sendCurrentText();  
  27.         return true;  
  28.     }  
  29.   
  30.     private void replaceText(CharSequence text, int newCursorPosition,  
  31.                 boolean composing) {  
  32.                 //获取eidtor  
  33.         final Editable content = getEditable();  
  34.         if (content == null) {  
  35.             return;  
  36.         }  
  37.           
  38.         beginBatchEdit();  
  39.         ………………..  
  40.                  //修改editor  
  41.         content.replace(a, b, text);          
  42.         endBatchEdit();  
  43.     }  
  44.       
  45.     private void sendCurrentText() {          
  46.         Editable content = getEditable();  
  47.         if (content != null) {  
  48.             final int N = content.length();  
  49.               
  50.             // 将输入文本模拟为为一个key事件,这样view就会更新内容了  
  51.             KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),  
  52.                     content.toString(), KeyCharacterMap.VIRTUAL_KEYBOARD, 0);  
  53.             sendKeyEvent(event);  
  54.             content.clear();  
  55.         }  
  56.      }  
  57.   
  58.      public boolean sendKeyEvent(KeyEvent event) {  
  59.        //同ViewRootImpl有按键事件,到此为止就像是外接键盘的按键事件似的  
  60.         synchronized (mIMM.mH) {  
  61.             ViewRootImpl viewRootImpl = mTargetView != null ? mTargetView.getViewRootImpl() : null;  
  62.             if (viewRootImpl == null) {  
  63.                 if (mIMM.mServedView != null) {  
  64.                     viewRootImpl = mIMM.mServedView.getViewRootImpl();  
  65.                 }  
  66.             }  
  67.             if (viewRootImpl != null) {  
  68.                 //发送信息  
  69.                 viewRootImpl.dispatchKeyFromIme(event);  
  70.             }  
  71.         } 

原文地址:https://www.cnblogs.com/bill-technology/p/4130938.html