zoukankan      html  css  js  c++  java
  • Android输入法框架系统(上)

    输入法,就是用来输入字符(包括英文,俄文,中文)的工具。输入法你可以看成是一种字符发生器,它将输入数据触摸事件或者按键事件转化为其他更丰富的字符。在PC时代,输入法的原始输入来自实体键盘,鼠标,然后输入法将这些事件对应的ASCII码转换为俄文,中文,当然如果是英文是不需要转换,直接发送即可。而在Android系统里,由于输入法dialog永远没法成为焦点window,所以输入法永远没法获取到按键事件,也就是说输入法的输入数据只能来自触摸事件,输入法显示出键盘(大家称之为软键盘),用户点击键盘UI, 然后输入法将触摸事件所在位置的字符当做原始字符输入,最后组装成更为丰富的字符(多个字符组成拼音,然后转化为中文),然后就是发送到对应的程序。

            Android系统中,输入法可以是可以安装的,也就是说系统可以有多个输入法((sougou输入法,百度输入法),但是只有一个是激活的,当然用户可以切换输入法。同时,输入法是以service的方式运行的,输入法同一时间只能服务一个程序,只有最顶层的可见的程序才能接收到输入法的输入数据。

           输入法系统的整个框架如下:

           

           InputMethodManagerService(下文也称IMMS)负责管理系统的所有输入法,包括输入法service(InputMethodService简称IMS)加载及切换。程序获得焦点时,就会通过InputMethodManager向InputMethodManagerService通知自己获得焦点并请求绑定自己到当前输入法上。同时,当程序的某个需要输入法的view比如EditorView获得焦点时就会通过InputMethodManager向InputMethodManagerService请求显示输入法,而这时InputMethodManagerService收到请求后,会将请求的EditText的数据通信接口发送给当前输入法,并请求显输入法。输入法收到请求后,就显示自己的UI dialog,同时保存目标view的数据结构,当用户实现输入后,直接通过view的数据通信接口将字符传递到对应的View。接下来就来分析这些过程。


    InputMethodManager创建


            每个程序有一个InputMethodManager实例,这个是程序和InputMethodManagerService通信的接口,该实例在ViewRootImpl初始化的时候创建。

     

    1. public ViewRootImpl(Context context, Display display) {  
    2.     mContext = context;  
    3.     mWindowSession = WindowManagerGlobal.getWindowSession();  
    4. }  
    5.   
    6. public static IWindowSession getWindowSession() {  
    7.     synchronized (WindowManagerGlobal.class) {  
    8.         if (sWindowSession == null) {  
    9.             try {  
    10.                 //这个进程的InputMethodManager实例就生成了  
    11.                 InputMethodManager imm = InputMethodManager.getInstance();  
    12.                 IWindowManager windowManager = getWindowManagerService();  
    13.             } catch (RemoteException e) {  
    14.                 Log.e(TAG, "Failed to open window session", e);  
    15.             }  
    16.         }  
    17.         return sWindowSession;  
    18.     }  
    19. }  
    20.   
    21. public static InputMethodManager getInstance() {  
    22.     synchronized (InputMethodManager.class) {  
    23.         if (sInstance == null) {  
    24.             // InputMethodManager其实就是一个Binder service的proxy  
    25.             IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);  
    26.             IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);  
    27.             sInstance = new InputMethodManager(service, Looper.getMainLooper());  
    28.         }  
    29.         return sInstance;  
    30.     }  
    31. }  


    程序的Window获得焦点


               程序的window获得焦点的时序图如下


     

    系统WindowManagerService更新焦点window

             哪个程序获得焦点是由系统决定的,是由WindowManagerService决定的,当系统的window状态发生变化时(比如window新增,删除)就会调用函数updateFocusedWindowLocked来更新焦点window。

    1. private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {  
    2.     //计算焦点window  
    3.     WindowState newFocus = computeFocusedWindowLocked();  
    4.     if (mCurrentFocus != newFocus) {  
    5.         //焦点window发生变化,post一个message来通知程序焦点发生变化了  
    6.         mH.removeMessages(H.REPORT_FOCUS_CHANGE);  
    7.         mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);  
    8.         return true;  
    9.     }  
    10.     return false;  
    11. }  
    12.   
    13. private WindowState computeFocusedWindowLocked() {  
    14.     if (mAnimator.mUniverseBackground != null  
    15.             && mAnimator.mUniverseBackground.mWin.canReceiveKeys()) {  
    16.         return mAnimator.mUniverseBackground.mWin;  
    17.     }  
    18.   
    19.     final int displayCount = mDisplayContents.size();  
    20.     for (int i = 0; i < displayCount; i++) {  
    21.         final DisplayContent displayContent = mDisplayContents.valueAt(i);  
    22.         WindowState win = findFocusedWindowLocked(displayContent);  
    23.         if (win != null) {  
    24.             return win;  
    25.         }  
    26.     }  
    27.     return null;  
    28. }  
    29.   
    30. //该函数就是找出最top的可以接收按键事件的window,这个window就获得焦点  
    31. private WindowState findFocusedWindowLocked(DisplayContent displayContent) {  
    32.     final WindowList windows = displayContent.getWindowList();  
    33.     for (int i = windows.size() - 1; i >= 0; i--) {  
    34.         final WindowState win = windows.get(i);  
    35.         //是否为activity的window  
    36.         AppWindowToken wtoken = win.mAppToken;  
    37.         //重要函数,window是否可以获取焦点  
    38.         if (!win.canReceiveKeys()) {  
    39.             continue;  
    40.         }  
    41.         // mFocusedApp是最top的activity ,下面逻辑是为了确保焦点window的app  
    42.         //必须是焦点程序之上,所以这个逻辑其实并没有多大作用,只是为了检测出  
    43.         //错误  
    44.         if (wtoken != null && win.mAttrs.type != TYPE_APPLICATION_STARTING &&  
    45.                 mFocusedApp != null) {  
    46.             ArrayList<Task> tasks = displayContent.getTasks();  
    47.             for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {  
    48.                 AppTokenList tokens = tasks.get(taskNdx).mAppTokens;  
    49.                 int tokenNdx = tokens.size() - 1;  
    50.                 for ( ; tokenNdx >= 0; --tokenNdx) {  
    51.                     final AppWindowToken token = tokens.get(tokenNdx);  
    52.                     if (wtoken == token) {  
    53.                         break;  
    54.                     }  
    55.                     if (mFocusedApp == token) {  
    56.                         return null;  
    57.                     }  
    58.                 }  
    59.             }  
    60.         }  
    61.         return win;  
    62.     }  
    63.     return null;  
    64. }  
    65.   
    66. public final boolean canReceiveKeys() {  
    67.     return isVisibleOrAdding()  
    68.             && (mViewVisibility == View.VISIBLE)  
    69.             && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0);  
    70. }  
    71. //由于输入法的window带有FLAG_NOT_FOCUSABLE, 从上可见其不可能是焦点window  
    72. //接下来系统开始通知程序端哪个window获得了焦点。  
    73.   
    74. final class H extends Handler {  
    75.     @Override  
    76.     public void handleMessage(Message msg) {  
    77.         switch (msg.what) {  
    78.             case REPORT_FOCUS_CHANGE: {  
    79.                 WindowState lastFocus;  
    80.                 WindowState newFocus;  
    81.   
    82.                 synchronized(mWindowMap) {  
    83.                     lastFocus = mLastFocus;  
    84.                     newFocus = mCurrentFocus;  
    85.                     if (lastFocus == newFocus) {  
    86.                         // Focus is not changing, so nothing to do.  
    87.                         return;  
    88.                     }  
    89.                     mLastFocus = newFocus;  
    90.                 }  
    91.                 if (newFocus != null) {  
    92.                     //通知新的焦点程序其获得了焦点  
    93.                     newFocus.reportFocusChangedSerialized(true, mInTouchMode);  
    94.                     notifyFocusChanged();  
    95.                 }  
    96.   
    97.                 if (lastFocus != null) {  
    98.                     //通知老的焦点程序其获得了焦点  
    99.                     lastFocus.reportFocusChangedSerialized(false, mInTouchMode);  
    100.                 }  
    101.             } break;  
    102.  }  
    103.   
    104.  public void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {  
    105.     try {  
    106.         //这个就是通过Binder告知client其获得或失去了焦点  
    107.         mClient.windowFocusChanged(focused, inTouchMode);  
    108.     } catch (RemoteException e) {  
    109.     }  
    110. }  


            上面的mClient.windowFocusChanged会调回到ViewRootImpl中的W实例:

    程序获得焦点改变事件

               

    1. //ViewRootImpl.java  
    2. static class W extends IWindow.Stub {  
    3.     @Override  
    4.     public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {  
    5.         final ViewRootImpl viewAncestor = mViewAncestor.get();  
    6.         if (viewAncestor != null) {  
    7.             viewAncestor.windowFocusChanged(hasFocus, inTouchMode);  
    8.         }  
    9.     }  
    10.   
    11.   
    12. public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {  
    13.     Message msg = Message.obtain();  
    14.     msg.what = MSG_WINDOW_FOCUS_CHANGED;  
    15.     msg.arg1 = hasFocus ? 1 : 0;  
    16.     msg.arg2 = inTouchMode ? 1 : 0;  
    17.     mHandler.sendMessage(msg);  
    18. }  
    19.   
    20. //程序获得焦点会通过调用mView.dispatchWindowFocusChanged和  
    21. //imm.onWindowFocus来通知IMMS焦点信息发生改变,需要更新输入法了  
    22. final class ViewRootHandler extends Handler {  
    23.     @Override  
    24.     public void handleMessage(Message msg) {  
    25.         switch (msg.what) {  
    26.         case MSG_WINDOW_FOCUS_CHANGED: {  
    27.             if (mAdded) {  
    28.                 boolean hasWindowFocus = msg.arg1 != 0;  
    29.                 mAttachInfo.mHasWindowFocus = hasWindowFocus;  
    30.                 mLastWasImTarget = WindowManager.LayoutParams  
    31.                         .mayUseInputMethod(mWindowAttributes.flags);  
    32.                 InputMethodManager imm = InputMethodManager.peekInstance();  
    33.                 if (mView != null) {  
    34.                     //调用根view的dispatchWindowFocusChanged函数通知view  
    35.                     //程序获得焦点  
    36.                     mView.dispatchWindowFocusChanged(hasWindowFocus);  
    37.                     mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(  
    38. indowFocus);  
    39.                 }  
    40.                 if (hasWindowFocus) {  
    41.                     if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {  
    42.                         //通知imm该window获得焦点  
    43.                         imm.onWindowFocus(mView, mView.findFocus(),  
    44.                                 mWindowAttributes.softInputMode,  
    45.                                 !mHasHadWindowFocus, mWindowAttributes.flags);  
    46.                     }  
    47.                 }  
    48.             }  
    49.   
    50.         } break;  
    51. }  
    52.   
    53. //上面的根view就是DecorView,它只是调用父类ViewGroup  
    54. //的dispatchWindowFocusChanged  
    55. //ViewGroup.java  
    56. @Override  
    57. public void dispatchWindowFocusChanged(boolean hasFocus) {  
    58.     super.dispatchWindowFocusChanged(hasFocus);  
    59.     final int count = mChildrenCount;  
    60.     final View[] children = mChildren;  
    61.     //让每个子view处理window焦点改变时间  
    62.     //但是只有获得焦点的view才会处理这个时间  
    63.     for (int i = 0; i < count; i++) {  
    64.         children[i].dispatchWindowFocusChanged(hasFocus);  
    65.     }  
    66. }  
    67.   
    68. //View.java  
    69. public void onWindowFocusChanged(boolean hasWindowFocus) {  
    70.     InputMethodManager imm = InputMethodManager.peekInstance();  
    71.     if (!hasWindowFocus) {  
    72.     } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {  
    73.         //获得焦点的view通过 InputMethodManager向service通知自己获得焦点  
    74.         imm.focusIn(this);  
    75.     }  
    76. }  

    焦点View向IMMS请求绑定输入法

             焦点view请求绑定输入法是通过调用InputMethodManager. focusIn实现的

           

    1. public void focusIn(View view) {  
    2.     synchronized (mH) {  
    3.         focusInLocked(view);  
    4.     }  
    5. }  
    6.   
    7. void focusInLocked(View view) {   
    8.     //保存焦点view变量  
    9.     mNextServedView = view;  
    10.     scheduleCheckFocusLocked(view);  
    11. }  
    12.   
    13. static void scheduleCheckFocusLocked(View view) {  
    14.     ViewRootImpl viewRootImpl = view.getViewRootImpl();  
    15.     if (viewRootImpl != null) {  
    16.         viewRootImpl.dispatchCheckFocus();  
    17.     }  
    18. }  
    19.   
    20. public void dispatchCheckFocus() {  
    21.     if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {  
    22.         // This will result in a call to checkFocus() below.  
    23.         mHandler.sendEmptyMessage(MSG_CHECK_FOCUS);  
    24.     }  
    25. }  
    26.         case MSG_CHECK_FOCUS: {  
    27.             InputMethodManager imm = InputMethodManager.peekInstance();  
    28.             if (imm != null) {  
    29.                 imm.checkFocus();  
    30.             }  
    31.         } break;  
    32.   
    33. public void checkFocus() {  
    34.     if (checkFocusNoStartInput(falsetrue)) {  
    35.         startInputInner(null000);  
    36.     }  
    37. }  
    38.   
    39.   
    40. boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode,  
    41.         int windowFlags) {  
    42.     final View view;  
    43.     synchronized (mH) {  
    44.         //获得上面的焦点view  
    45.         view = mServedView;  
    46.     }  
    47.   
    48.     EditorInfo tba = new EditorInfo();  
    49.     tba.packageName = view.getContext().getPackageName();  
    50.     tba.fieldId = view.getId();  
    51.     //创建数据通信连接接口,这个会传送到InputMethodService  
    52.     //InputMethodService后面就通过这个connection将输入法的字符传递给该view  
    53.     InputConnection ic = view.onCreateInputConnection(tba);  
    54.       
    55.     synchronized (mH) {  
    56.         mServedInputConnection = ic;  
    57.         ControlledInputConnectionWrapper servedContext;  
    58.         if (ic != null) {  
    59.             mCursorSelStart = tba.initialSelStart;  
    60.             mCursorSelEnd = tba.initialSelEnd;  
    61.             mCursorCandStart = -1;  
    62.             mCursorCandEnd = -1;  
    63.             mCursorRect.setEmpty();  
    64.             //将InputConnection封装为binder对象,这个是真正可以实现跨进程通  
    65.               //信的封装类  
    66.               servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);  
    67.         }  
    68.         mServedInputConnectionWrapper = servedContext;  
    69.           
    70.         try {  
    71.             InputBindResult res;  
    72.             if (windowGainingFocus != null) {  
    73.                 //focusIn这个不会走到这条分支  
    74.                 res = mService.windowGainedFocus(mClient, windowGainingFocus,  
    75.                         controlFlags, softInputMode, windowFlags,  
    76.                         tba, servedContext);  
    77.             } else {  
    78.                 //通知InputMethodManagerService,该程序的view获得焦点,IMMS  
    79.                 //就会将这个view和输入法绑定  
    80.                 res = mService.startInput(mClient,  
    81.                         servedContext, tba, controlFlags);  
    82.             }  
    83.             if (res != null) {  
    84.                 if (res.id != null) {  
    85.                     setInputChannelLocked(res.channel);  
    86.                     mBindSequence = res.sequence;  
    87.                     //获得了输入法的通信接口  
    88.                     mCurMethod = res.method;  
    89.                     mCurId = res.id;  
    90.                 }  
    91.             }  
    92.         }  
    93.     }  
    94.   
    95.     return true;  
    96. }  


     

    IMMS处理view绑定输入法事件

             为了讲解整个绑定过程,我们假设此时输入法service还没启动,这个情况下的输入法绑定是最长的,整个过程经历过如下过程:

    1)       启动输入法service

    2)       绑定输入法window的token

    3)       请求输入法为焦点程序创建一个连接会话-

    4)       将输入法的接口传递回程序client端

    5)       绑定输入法和焦点view

              1-4是和程序相关的,而5是和view相关的。所以你可以说1~4是用来绑定程序window和输入法,而5是用来绑定程序view和输入法。

              输入法还没启动时,弹出输入法会经过1~5,输入法已经启动,但是焦点window发生变化时会经历3~5,焦点window没有变化,只是改变了焦点view,则只会经历5。整个流程如下:

     

    启动输入法service

    1. @Override  
    2. public InputBindResult startInput(IInputMethodClient client,  
    3.         IInputContext inputContext, EditorInfo attribute, int controlFlags) {  
    4.     synchronized (mMethodMap) {  
    5.         final long ident = Binder.clearCallingIdentity();  
    6.         try {  
    7.             return startInputLocked(client, inputContext, attribute, controlFlags);  
    8.         }  
    9.     }  
    10. }  
    11. InputBindResult startInputLocked(IInputMethodClient client,  
    12.         IInputContext inputContext, EditorInfo attribute, int controlFlags) {  
    13.     //程序在service端对应的数据结构  
    14.     ClientState cs = mClients.get(client.asBinder());  
    15.     return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);  
    16. }  
    17.   
    18. InputBindResult startInputUncheckedLocked(ClientState cs,  
    19.         IInputContext inputContext, EditorInfo attribute, int controlFlags) {  
    20.     //如果新程序和当前活动的程序不同  
    21.     if (mCurClient != cs) {  
    22.         //取消当前活动程序和输入法的绑定  
    23.         unbindCurrentClientLocked();  
    24.     }  
    25.   
    26.     //将新程序设置为当前活动的程序  
    27.     mCurClient = cs;  
    28.     mCurInputContext = inputContext;  
    29.     mCurAttribute = attribute;  
    30.   
    31.     if (mCurId != null && mCurId.equals(mCurMethodId)) {  
    32.         if (cs.curSession != null) {  
    33.             //连接已经建立,直接开始绑定  
    34.             return attachNewInputLocked(  
    35.                     (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);  
    36.         }  
    37.         if (mHaveConnection) {  
    38.             //输入法的连接是否已经创建,如果已经创建,直接传递给程序client端  
    39.             if (mCurMethod != null) {  
    40.                 requestClientSessionLocked(cs);  
    41.                 return new InputBindResult(nullnull, mCurId, mCurSeq);  
    42.             }  
    43.         }  
    44.     }  
    45.     //否则需要启动输入法,并建立连接  
    46.     return startInputInnerLocked();  
    47. }  
    48.   
    49. InputBindResult startInputInnerLocked() {  
    50.     InputMethodInfo info = mMethodMap.get(mCurMethodId);  
    51.   
    52.     unbindCurrentMethodLocked(falsetrue);  
    53.   
    54.     //启动输入法service  
    55.     mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);  
    56.     mCurIntent.setComponent(info.getComponent());  
    57.     mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,  
    58.             com.android.internal.R.string.input_method_binding_label);  
    59.     mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(  
    60.             mContext, 0new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));  
    61.     if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE  
    62.             | Context.BIND_NOT_VISIBLE | Context.BIND_SHOWING_UI)) {  
    63.         mHaveConnection = true;  
    64.         mCurId = info.getId();  
    65.         //这个token是给输入法service用来绑定输入法的window的,通过这个token  
    66.         //InputMethodManagerService可以很方便的直接管理输入法的window  
    67.         mCurToken = new Binder();  
    68.         try {  
    69.             mIWindowManager.addWindowToken(mCurToken,  
    70.                     WindowManager.LayoutParams.TYPE_INPUT_METHOD);  
    71.         } catch (RemoteException e) {  
    72.         }  
    73.         return new InputBindResult(nullnull, mCurId, mCurSeq);  
    74.     }  
    75.     return null;  
    76. }  
    77.   
    78. private boolean bindCurrentInputMethodService(  
    79.         Intent service, ServiceConnection conn, int flags) {  
    80.     if (service == null || conn == null) {  
    81.         Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);  
    82.         return false;  
    83.     }  
    84.     return mContext.bindServiceAsUser(service, conn, flags,  
    85.             new UserHandle(mSettings.getCurrentUserId()));  
    86. }  
    87.   
    88. //输入法启动完成后就在函数onBind 传回一个binder接口  
    89. @Override  
    90. final public IBinder onBind(Intent intent) {  
    91.     if (mInputMethod == null) {  
    92.         mInputMethod = onCreateInputMethodInterface();  
    93.     }  
    94.     // IInputMethodWrapper只是一个wrapper,它负责将IMMS的调用转化为message  
    95.     //然后在message线程再调用mInputMethod对应的接口   
    96.       //这样输入法的处理就是异步的了,因此你说它就是mInputMethod  
    97.     return new IInputMethodWrapper(this, mInputMethod);  
    98. }  
    99.   
    100. @Override  
    101. public AbstractInputMethodImpl onCreateInputMethodInterface() {  
    102.     return new InputMethodImpl();  
    103. }  
    104.   
    105. //由于IMMS是以bindService的方式启动输入法service,所以当输入法service启动完  
    106. //成后它就会回调IMMS的onServiceConnected  
    107.   
    108. @Override  
    109. public void onServiceConnected(ComponentName name, IBinder service) {  
    110.     synchronized (mMethodMap) {  
    111.         if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {  
    112.             //保存输入法service传递过来的通信接口IInputMethod  
    113.             mCurMethod = IInputMethod.Stub.asInterface(service);  
    114.             //将刚刚创建的window token传递给输入法service,然后输入用这个token  
    115.             //创建window,这样IMMS可以用根据这个token找到输入法在IMMS里  
    116.               //的数据及输入法window在WMS里的数据  
    117.               executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(  
    118.                     MSG_ATTACH_TOKEN, mCurMethod, mCurToken));  
    119.             if (mCurClient != null) {  
    120.             //请求为程序和输入法建立一个连接会话,这样client就可以直接和  
    121.                //输入法通信了  
    122.                 requestClientSessionLocked(mCurClient);  
    123.             }  
    124.         }  
    125.     }  
    126. }  


     

    输入法Window token的绑定及使用分析

          输入法Window token绑定

            IMMS在输入法启动完成并回调onServiceConnected时会将一个Window token传递给输入法。

     

    1. @Override  
    2. public void onServiceConnected(ComponentName name, IBinder service) {  
    3.     synchronized (mMethodMap) {  
    4.         if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {  
    5.             mCurMethod = IInputMethod.Stub.asInterface(service);  
    6.             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(  
    7.                     MSG_ATTACH_TOKEN, mCurMethod, mCurToken));  
    8.             if (mCurClient != null) {  
    9.                 clearClientSessionLocked(mCurClient);  
    10.                 requestClientSessionLocked(mCurClient);  
    11.             }  
    12.         }  
    13.     }  
    14. }  
    15.         case MSG_ATTACH_TOKEN:  
    16.             args = (SomeArgs)msg.obj;  
    17.             try {  
    18.                 //和输入法通信  
    19.                 ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);  
    20.             } catch (RemoteException e) {  
    21.             }  
    22.             args.recycle();  
    23.   
    24. public class InputMethodService extends AbstractInputMethodService {  
    25. public class InputMethodImpl extends AbstractInputMethodImpl {  
    26.     public void attachToken(IBinder token) {  
    27.         if (mToken == null) {  
    28.             //保存token  
    29.             mToken = token;  
    30.             //这样输入法的window就绑定这个window token  
    31.             mWindow.setToken(token);  
    32.         }  
    33.     }  
    34. }  


          输入法Window token使用

           由于系统存在多个输入法,所以输入法要和IMMS通信,必须要个机制来标示自己是哪个输入法,这个就是通过上面的输入法Window token来实现的,比如输入法自己关闭自己:

     

    1. //InputMethodService.java输入法接口  
    2. public void requestHideSelf(int flags) {  
    3.     //mToken就是上面提到的过程----IMMS传递给输入法的  
    4.     mImm.hideSoftInputFromInputMethod(mToken, flags);  
    5. }  
    6. //InputMethodManager.java  
    7. public void hideSoftInputFromInputMethod(IBinder token, int flags) {  
    8.     try {  
    9.         mService.hideMySoftInput(token, flags);  
    10.     } catch (RemoteException e) {  
    11.         throw new RuntimeException(e);  
    12.     }  
    13. }  
    14.   
    15. //IMMS  
    16. @Override  
    17. public void hideMySoftInput(IBinder token, int flags) {  
    18.     if (!calledFromValidUser()) {  
    19.         return;  
    20.     }  
    21.     synchronized (mMethodMap) {  
    22.         if (token == null || mCurToken != token) {  
    23.             if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "  
    24.                     + Binder.getCallingUid() + " token: " + token);  
    25.             return;  
    26.         }  
    27.         long ident = Binder.clearCallingIdentity();  
    28.         try {  
    29.             hideCurrentInputLocked(flags, null);  
    30.         } finally {  
    31.             Binder.restoreCallingIdentity(ident);  
    32.         }  
    33.     }  
    34. }  


    输入法连接会话创建

           到此程序和输入法的session就建立了

    1. @Override  
    2. public void onServiceConnected(ComponentName name, IBinder service) {  
    3.     synchronized (mMethodMap) {  
    4.         if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {  
    5.             if (mCurClient != null) {  
    6.                 clearClientSessionLocked(mCurClient);  
    7.                 requestClientSessionLocked(mCurClient);  
    8.             }  
    9.         }  
    10.     }  
    11. }  
    12.   
    13. void requestClientSessionLocked(ClientState cs) {  
    14.     if (!cs.sessionRequested) {  
    15.         //这里又出现了InputChannel对,很面熟吧,在前面几篇文章已经详细分析过  
    16.           //了,可见它已经成为一种通用的跨平台的数据通信接口了  
    17.         InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());  
    18.         cs.sessionRequested = true;  
    19.         executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(  
    20.                 MSG_CREATE_SESSION, mCurMethod, channels[1],  
    21.                 new MethodCallback(this, mCurMethod, channels[0])));  
    22.     }  
    23.  }  
    24.         case MSG_CREATE_SESSION: {  
    25.             args = (SomeArgs)msg.obj;  
    26.             IInputMethod method = (IInputMethod)args.arg1;  
    27.             InputChannel channel = (InputChannel)args.arg2;  
    28.             try {  
    29.                 method.createSession(channel, (IInputSessionCallback)args.arg3);  
    30.             } catch (RemoteException e) {  
    31.             }  
    32. //上面是IMMS端,下面就看IMS输入法端的处理  
    33.  public abstract class AbstractInputMethodService extends Service  
    34.     implements KeyEvent.Callback {  
    35.  public abstract class AbstractInputMethodImpl implements InputMethod {  
    36.     public void createSession(SessionCallback callback) {  
    37.         callback.sessionCreated(onCreateInputMethodSessionInterface());  
    38.     }  
    39. pre class="java" name="code">     }  
    1. }  
    } @Override public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() { //sesion的真正实现 return new InputMethodSessionImpl(); } //然后回到了IMMS void onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel) { synchronized (mMethodMap) { if (mCurMethod != null && method != null && mCurMethod.asBinder() == method.asBinder()) { if (mCurClient != null) { //将session相关的数据封装到SessionState对象里 mCurClient.curSession = new SessionState(mCurClient, method, session, channel); //这个会开始真正的绑定 InputBindResult res = attachNewInputLocked(true); return; } } } }

    传递输入法接口给程序

    1. void onSessionCreated(IInputMethod method, IInputMethodSession session,  
    2.         InputChannel channel) {  
    3.     synchronized (mMethodMap) {  
    4.         if (mCurMethod != null && method != null  
    5.                 && mCurMethod.asBinder() == method.asBinder()) {  
    6.             if (mCurClient != null) {  
    7.                 InputBindResult res = attachNewInputLocked(true);  
    8.                 if (res.method != null) {  
    9.                     executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(  
    10.                             MSG_BIND_METHOD, mCurClient.client, res));  
    11.                 }  
    12.                 return;  
    13.             }  
    14.         }  
    15.     }  
    16.     channel.dispose();  
    17. }  
    18.         case MSG_BIND_METHOD: {  
    19.             args = (SomeArgs)msg.obj;  
    20.             IInputMethodClient client = (IInputMethodClient)args.arg1;  
    21.             InputBindResult res = (InputBindResult)args.arg2;  
    22.             try {  
    23.                 //会调回到程序端  
    24.                 client.onBindMethod(res);  
    25.             }  
    26.             args.recycle();  
    27.             return true;  
    28.         }  


     

    输入法和view绑定

    1. //IMMS   
    2. InputBindResult attachNewInputLocked(boolean initial) {  
    3.      if (!mBoundToMethod) {  
    4.          executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(  
    5.                  MSG_BIND_INPUT, mCurMethod, mCurClient.binding));  
    6.          mBoundToMethod = true;  
    7.      }  
    8.      final SessionState session = mCurClient.curSession;  
    9.      if (initial) {  
    10.          executeOrSendMessage(session.method, mCaller.obtainMessageOOO(  
    11.                  MSG_START_INPUT, session, mCurInputContext, mCurAttribute));  
    12.      } else {  
    13.          executeOrSendMessage(session.method, mCaller.obtainMessageOOO(  
    14.                  MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));  
    15.      }  
    16.      return new InputBindResult(session.session,  
    17.              session.channel != null ? session.channel.dup() : null, mCurId, mCurSeq);  
    18.  }  
    19.          case MSG_BIND_INPUT:  
    20.              args = (SomeArgs)msg.obj;  
    21.              try {  
    22.                  ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);  
    23.              } catch (RemoteException e) {  
    24.              }  
    25.              args.recycle();  
    26.              return true;  
    27.           
    28.          case MSG_START_INPUT:  
    29.              args = (SomeArgs)msg.obj;  
    30.              try {  
    31.                  SessionState session = (SessionState)args.arg1;  
    32.                  session.method.startInput((IInputContext)args.arg2,  
    33.                          (EditorInfo)args.arg3);  
    34.              } catch (RemoteException e) {  
    35.              }  
    36.              args.recycle();  
    37.              return true;  
    38.  //IMS  
    39.  @Override  
    40.  public void startInput(IInputContext inputContext, EditorInfo attribute) {  
    41.      mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,  
    42.              inputContext, attribute));  
    43.  }  
    44.          case DO_START_INPUT: {  
    45.              SomeArgs args = (SomeArgs)msg.obj;  
    46.              // IInputContext就是输入法和文本输入view的通信接口  
    47.              //通过这个接口,输入法能够获取view的信息,也能够直接将文本传  
    48.              //送给view  
    49.              IInputContext inputContext = (IInputContext)args.arg1;  
    50.              InputConnection ic = inputContext != null  
    51.                      ? new InputConnectionWrapper(inputContext) : null;  
    52.              EditorInfo info = (EditorInfo)args.arg2;  
    53.              inputMethod.startInput(ic, info);  
    54.              args.recycle();  
    55.              return;  
    56.          }  
    57.  public class InputMethodImpl extends AbstractInputMethodImpl {  
    58.      public void startInput(InputConnection ic, EditorInfo attribute) {  
    59.          doStartInput(ic, attribute, false);  
    60.      }  
    61.  }  
    62.   
    63.  void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {  
    64.      if (!restarting) {  
    65.          doFinishInput();  
    66.      }  
    67.      mInputStarted = true;  
    68.      mStartedInputConnection = ic;  
    69.      mInputEditorInfo = attribute;  
    70.      initialize();  
    71.      onStartInput(attribute, restarting);  
    72.      if (mWindowVisible) {  
    73.          if (mShowInputRequested) {  
    74.              mInputViewStarted = true;  
    75.              //真正的输入法需要在这个接口里实现输入法的内容  
    76.              onStartInputView(mInputEditorInfo, restarting);  
    77.              startExtractingText(true);  
    78.          } else if (mCandidatesVisibility == View.VISIBLE) {  
    79.              mCandidatesViewStarted = true;  
    80.              onStartCandidatesView(mInputEditorInfo, restarting);  
    81.          }  
    82.      }  
    83.  }  


     

            到此焦点view已经通过调用IMMS的startInput和输入法绑定了,但是此时输入法还没有显示。但是系统紧接着会调用windowGainFocus来显示输入法。


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

           请查看输入法框架下篇

    输入法响应显示请求

           请查看输入法框架下篇

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

           请查看输入法框架下篇

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

           请查看输入法框架下篇

  • 相关阅读:
    搞一个先试试
    java map排序
    文件上传
    文件下载
    Filter过滤器
    java编写一个简单的验证码
    centos7安装mysql
    linux安装jdk,tomcat服务器
    DBUtil工具类
    mysql
  • 原文地址:https://www.cnblogs.com/bill-technology/p/4130939.html
Copyright © 2011-2022 走看看