输入法,就是用来输入字符(包括英文,俄文,中文)的工具。输入法你可以看成是一种字符发生器,它将输入数据触摸事件或者按键事件转化为其他更丰富的字符。在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. public static IWindowSession getWindowSession() {
  6. synchronized (WindowManagerGlobal.class) {
  7. if (sWindowSession == null) {
  8. try {
  9. //这个进程的InputMethodManager实例就生成了
  10. InputMethodManager imm = InputMethodManager.getInstance();
  11. IWindowManager windowManager = getWindowManagerService();
  12. } catch (RemoteException e) {
  13. Log.e(TAG, "Failed to open window session", e);
  14. }
  15. }
  16. return sWindowSession;
  17. }
  18. }
  19. public static InputMethodManager getInstance() {
  20. synchronized (InputMethodManager.class) {
  21. if (sInstance == null) {
  22. // InputMethodManager其实就是一个Binder service的proxy
  23. IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
  24. IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
  25. sInstance = new InputMethodManager(service, Looper.getMainLooper());
  26. }
  27. return sInstance;
  28. }
  29. }

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

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

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

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

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

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

输入法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. public class InputMethodService extends AbstractInputMethodService {
  24. public class InputMethodImpl extends AbstractInputMethodImpl {
  25. public void attachToken(IBinder token) {
  26. if (mToken == null) {
  27. //保存token
  28. mToken = token;
  29. //这样输入法的window就绑定这个window token
  30. mWindow.setToken(token);
  31. }
  32. }
  33. }

输入法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. //IMMS
  15. @Override
  16. public void hideMySoftInput(IBinder token, int flags) {
  17. if (!calledFromValidUser()) {
  18. return;
  19. }
  20. synchronized (mMethodMap) {
  21. if (token == null || mCurToken != token) {
  22. if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
  23. + Binder.getCallingUid() + " token: " + token);
  24. return;
  25. }
  26. long ident = Binder.clearCallingIdentity();
  27. try {
  28. hideCurrentInputLocked(flags, null);
  29. } finally {
  30. Binder.restoreCallingIdentity(ident);
  31. }
  32. }
  33. }

输入法连接会话创建

到此程序和输入法的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. void requestClientSessionLocked(ClientState cs) {
  13. if (!cs.sessionRequested) {
  14. //这里又出现了InputChannel对,很面熟吧,在前面几篇文章已经详细分析过
  15. //了,可见它已经成为一种通用的跨平台的数据通信接口了
  16. InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
  17. cs.sessionRequested = true;
  18. executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
  19. MSG_CREATE_SESSION, mCurMethod, channels[1],
  20. new MethodCallback(this, mCurMethod, channels[0])));
  21. }
  22. }
  23. case MSG_CREATE_SESSION: {
  24. args = (SomeArgs)msg.obj;
  25. IInputMethod method = (IInputMethod)args.arg1;
  26. InputChannel channel = (InputChannel)args.arg2;
  27. try {
  28. method.createSession(channel, (IInputSessionCallback)args.arg3);
  29. } catch (RemoteException e) {
  30. }
  31. //上面是IMMS端,下面就看IMS输入法端的处理
  32. public abstract class AbstractInputMethodService extends Service
  33. implements KeyEvent.Callback {
  34. public abstract class AbstractInputMethodImpl implements InputMethod {
  35. public void createSession(SessionCallback callback) {
  36. callback.sessionCreated(onCreateInputMethodSessionInterface());
  37. }
  38. 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. case MSG_START_INPUT:
  28. args = (SomeArgs)msg.obj;
  29. try {
  30. SessionState session = (SessionState)args.arg1;
  31. session.method.startInput((IInputContext)args.arg2,
  32. (EditorInfo)args.arg3);
  33. } catch (RemoteException e) {
  34. }
  35. args.recycle();
  36. return true;
  37. //IMS
  38. @Override
  39. public void startInput(IInputContext inputContext, EditorInfo attribute) {
  40. mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,
  41. inputContext, attribute));
  42. }
  43. case DO_START_INPUT: {
  44. SomeArgs args = (SomeArgs)msg.obj;
  45. // IInputContext就是输入法和文本输入view的通信接口
  46. //通过这个接口,输入法能够获取view的信息,也能够直接将文本传
  47. //送给view
  48. IInputContext inputContext = (IInputContext)args.arg1;
  49. InputConnection ic = inputContext != null
  50. ? new InputConnectionWrapper(inputContext) : null;
  51. EditorInfo info = (EditorInfo)args.arg2;
  52. inputMethod.startInput(ic, info);
  53. args.recycle();
  54. return;
  55. }
  56. public class InputMethodImpl extends AbstractInputMethodImpl {
  57. public void startInput(InputConnection ic, EditorInfo attribute) {
  58. doStartInput(ic, attribute, false);
  59. }
  60. }
  61. void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
  62. if (!restarting) {
  63. doFinishInput();
  64. }
  65. mInputStarted = true;
  66. mStartedInputConnection = ic;
  67. mInputEditorInfo = attribute;
  68. initialize();
  69. onStartInput(attribute, restarting);
  70. if (mWindowVisible) {
  71. if (mShowInputRequested) {
  72. mInputViewStarted = true;
  73. //真正的输入法需要在这个接口里实现输入法的内容
  74. onStartInputView(mInputEditorInfo, restarting);
  75. startExtractingText(true);
  76. } else if (mCandidatesVisibility == View.VISIBLE) {
  77. mCandidatesViewStarted = true;
  78. onStartCandidatesView(mInputEditorInfo, restarting);
  79. }
  80. }
  81. }

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

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

       请查看输入法框架下篇

输入法响应显示请求

       请查看输入法框架下篇

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

       请查看输入法框架下篇

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

       请查看输入法框架下篇

Android输入法框架系统(上)的更多相关文章

  1. Android输入法框架系统(下)

    程序焦点获取事件导致输入法显示 从上面可以知道程序获得焦点时,程序端会先间接的调用IMMS的startInput将焦点View绑定到输入法,然后会调用IMMS的windowGainFocus函数,这个 ...

  2. Android中获取系统上安装的APP信息

    Version:0.9 StartHTML:-1 EndHTML:-1 StartFragment:00000099 EndFragment:00003259 Android中获取系统上安装的APP信 ...

  3. Android开发之深入理解Android 7.0系统权限更改相关文档

    http://www.cnblogs.com/dazhao/p/6547811.html 摘要: Android 6.0之后的版本增加了运行时权限,应用程序在执行每个需要系统权限的功能时,需要添加权限 ...

  4. Android 8.0系统的应用图标适配

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 概述 参考资料<一起来学习Android 8.0系统的应用图标适配吧>中已经讲得很清楚了,这里我只是简单总结下.详情的内容请阅 ...

  5. Android源码剖析之Framwork层后记篇(硬件消息传递、apk管理、输入法框架、编译过程)

    本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 既然写到后记篇,就代表本系列到此为止,暂时告一段落:其他一些Manager随后有时间再补,就像源码的 ...

  6. 理解WebKit和Chromium: 调试Android系统上的Chromium

    转载请注明原文地址:http://blog.csdn.net/milado_nju 1. Android上的调试技术 在Android系统上,开发人员能够使用两种不同的语言来开发应用程序,一种是Jav ...

  7. 在Mac系统上配置Android真机调试环境

    在Mac系统上配置Android真机调试环境 mac上配置安卓环境还说挺方便的,真机调试也比win上要好一些.win上被各种软件强行安装了xxx助手. 在mac上就了一个干净的感觉. 下载Androi ...

  8. windows系统上安装与使用Android NDK r5 (转)

    windows系统上安装与使用Android NDK r5  很早就听说了android的NDK应用,只是一直没有时间去研究,今天花了点时间在windows平台搭建了NDK环境,并成功运行了第一个简单 ...

  9. 1. Android 系统上一款开源的图表库

    1. MPAndroidChart  MPAndroidChart 是 Android 系统上一款开源的图表库.目前提供线图和饼图,支持选择.缩放和拖放. 一个可以拖动缩放的图表库,包含曲线图.直方图 ...

随机推荐

  1. UVa 10723 电子人的基因(LCS)

    https://vjudge.net/problem/UVA-10723 题意: 输入两个A~Z组成的字符串,找一个最短的串,使得输入的两个串均是它的子序列,另外还需要统计长度最短的串的个数. 思路: ...

  2. POJ 3628 Bookshelf2(0-1背包)

    http://poj.org/problem?id=3628 题意:给出一个高度H和n个牛的高度,要求把牛堆叠起来达到H,求出该高度和H的最小差. 思路:首先我们计算出牛的总高度sum,sum-H就相 ...

  3. Croc Champ 2013 - Round 1 E. Copying Data 分块

    E. Copying Data time limit per test 2 seconds memory limit per test 256 megabytes input standard inp ...

  4. python 线性查找

    import random val= data=[,,,,] : find= val=int(input('请输入查找键值(1-9),输入-1离开:')) for i in data: if i==v ...

  5. c++ 查找数组或者容器元素是否存在(find)

    #include <iostream> // cout #include <algorithm> // find #include <vector> // vect ...

  6. Jmeter 4.0 扩展插件

    今天发现Jmeter4.0 也可以安装插件 而且比之前的版本的安装方法更为容易 https://jmeter-plugins.org/ https://jmeter-plugins.org/insta ...

  7. 96D - Police Stations

    96D - Police Stations 思路:bfs,从所有的警察局开始bfs,因为bfs的深度一样,而且题目给的树保证满足条件,所以不用考虑深度. 如果搜索到一个点a,他的下一个点b已经被搜索过 ...

  8. HTTP协议的请求与响应和CSS属性和定位

    HTTP协议的请求与响应和CSS属性和定位 一.HTTP协议 1.1 HTTP定义 HTTP(Hypertext Transport Protocol),超文本传输协议. 一种详细规定了浏览器和web ...

  9. 我为什么放弃使用mybatis3的mapper注解了

    原文链接 最近在使用MyBatis3做项目.在使用注解实现Mapper的时候遇到了比较奇葩的问题:在实现数据的batch insert的时候总是报错.好不容易可以正常插入了,但是又不能返回自增的主键i ...

  10. How to implement connection pool in spark streaming

    在spark streaming的文档里,有这么一段: def sendPartition(iter): # ConnectionPool is a static, lazily initialize ...