• 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.         } 

  • 相关阅读:
    python3中内置函数map 和 reduce函数的使用
    爬山算法和模拟退火算法
    Link-Cut Tree(LCT)
    启发式搜索——A*算法
    树上分块
    CodeChef TRIPS-Children Trips 树上分块
    CodeChef:Chef and Problems(分块)
    莫队算法
    Konig定理及证明
    块状链表
  • 原文地址:https://www.cnblogs.com/bill-technology/p/4130938.html
Copyright © 2020-2023  润新知