Android输入法框架系统(上)

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

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

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

       《Android输入法框架系统(上)》

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


InputMethodManager创建

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

 

[java]
view plain
copy
《Android输入法框架系统(上)》
《Android输入法框架系统(上)》

  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获得焦点的时序图如下


 《Android输入法框架系统(上)》

系统WindowManagerService更新焦点window

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

[java]
view plain
copy
《Android输入法框架系统(上)》
《Android输入法框架系统(上)》

  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实例:

程序获得焦点改变事件

           

[java]
view plain
copy
《Android输入法框架系统(上)》
《Android输入法框架系统(上)》

  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实现的

       

[java]
view plain
copy
《Android输入法框架系统(上)》
《Android输入法框架系统(上)》

  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。整个流程如下:

 《Android输入法框架系统(上)》

启动输入法service

[java]
view plain
copy
《Android输入法框架系统(上)》
《Android输入法框架系统(上)》

  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传递给输入法。

 

[java]
view plain
copy
《Android输入法框架系统(上)》
《Android输入法框架系统(上)》

  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来实现的,比如输入法自己关闭自己:

 

[java]
view plain
copy
《Android输入法框架系统(上)》
《Android输入法框架系统(上)》

  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就建立了

[java]
view plain
copy
《Android输入法框架系统(上)》
《Android输入法框架系统(上)》

  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”>     }  

[java]
view plain
copy
《Android输入法框架系统(上)》
《Android输入法框架系统(上)》

  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; } } } }

传递输入法接口给程序

[java]
view plain
copy
《Android输入法框架系统(上)》
《Android输入法框架系统(上)》

  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绑定

[java]
view plain
copy
《Android输入法框架系统(上)》
《Android输入法框架系统(上)》

  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

       请查看输入法框架下篇

    原文作者:WindowManagerService
    原文地址: http://www.cnblogs.com/bill-technology/p/4130939.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞