在PC時代,輸入法的原始輸入來自實體鍵盤,鼠標,然後輸入法將這些事件對應的ASCII碼轉換爲俄文,中文,當然如果是英文是不需要轉換,直接發送即可。而在Android系統裏,由於輸入法dialog永遠沒法成爲焦點window,所以輸入法永遠沒法獲取到按鍵事件,也就是說輸入法的輸入數據只能來自觸摸事件,輸入法顯示出鍵盤(大家稱之爲軟鍵盤),用戶點擊鍵盤UI, 然後輸入法將觸摸事件所在位置的字符當做原始字符輸入,最後組裝成更爲豐富的字符(多個字符組成拼音,然後轉化爲中文),然後就是發送到對應的程序。
輸入法系統的整個框架如下:
InputMethodManagerService(下文也稱IMMS)負責管理系統的所有輸入法,包括輸入法service(InputMethodService簡稱IMS)加載及切換。程序獲得焦點時,就會通過InputMethodManager向InputMethodManagerService通知自己獲得焦點並請求綁定自己到當前輸入法上。同時,當程序的某個需要輸入法的view比如EditorView獲得焦點時就會通過InputMethodManager向InputMethodManagerService請求顯示輸入法,而這時InputMethodManagerService收到請求後,會將請求的EditText的數據通信接口發送給當前輸入法,並請求顯輸入法。輸入法收到請求後,就顯示自己的UI dialog,同時保存目標view的數據結構,當用戶實現輸入後,直接通過view的數據通信接口將字符傳遞到對應的View。接下來就來分析這些過程。
每個應用APP程序有一個InputMethodManager實例,這個是應用APP程序和InputMethodManagerService通信的接口,該實例在ViewRootImpl初始化的時候創建。
//frameworks/base/core/java/android/view/ViewRootImpl.java public ViewRootImpl(Context context, Display display) { mContext = context; mWindowSession = WindowManagerGlobal.getWindowSession(); } //frameworks/base/core/java/android/view/WindowManagerGlobal.java public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { //這個進程的InputMethodManager實例就生成了 InputMethodManager imm = InputMethodManager.getInstance(); IWindowManager windowManager = getWindowManagerService(); } catch (RemoteException e) { Log.e(TAG, "Failed to open window session", e); } } return sWindowSession; } } public static InputMethodManager getInstance() { synchronized (InputMethodManager.class) { if (sInstance == null) { // InputMethodManager其實就是一個Binder service的proxy IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE); IInputMethodManager service = IInputMethodManager.Stub.asInterface(b); sInstance = new InputMethodManager(service, Looper.getMainLooper()); } return sInstance; } }
程序的window獲得焦點的時序圖如下
哪個程序獲得焦點是由系統決定的,是由WindowManagerService決定的,當系統的window狀態發生變化時(比如window新增,刪除)就會調用函數updateFocusedWindowLocked來更新焦點window。
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { //計算焦點window ----見3.1 WindowState newFocus = computeFocusedWindowLocked(); if (mCurrentFocus != newFocus) { //焦點window發生變化,post一個message來通知程序焦點發生變化了 mH.removeMessages(H.REPORT_FOCUS_CHANGE); mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE); //見3.2 //省略..... return true; } return false; }
//frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java WindowState computeFocusedWindow() { // While the keyguard is showing, we must focus anything besides the main display. // Otherwise we risk input not going to the keyguard when the user expects it to. final boolean forceDefaultDisplay = mService.isKeyguardShowingAndNotOccluded(); for (int i = mChildren.size() - 1; i >= 0; i--) { final DisplayContent dc = mChildren.get(i); final WindowState win = dc.findFocusedWindow(); if (win != null) { if (forceDefaultDisplay && !dc.isDefaultDisplay) { EventLog.writeEvent(0x534e4554, "71786287", win.mOwnerUid, ""); continue; } return win; } } return null; } //frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java WindowState findFocusedWindow() { mTmpWindow = null; forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */); if (mTmpWindow == null) { if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: No focusable windows."); return null; } return mTmpWindow; } //frameworks/base/services/core/java/com/android/server/wm/WindowState.java @Override boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) { if (mChildren.isEmpty()) { // The window has no children so we just return it. return applyInOrderWithImeWindows(callback, traverseTopToBottom); } if (traverseTopToBottom) {//因爲traverseTopToBottom爲true,故走這個分支 return forAllWindowTopToBottom(callback); } else { return forAllWindowBottomToTop(callback); } } private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) { if (traverseTopToBottom) { //此處的callback實際上是傳入的mFindFocusedWindow,故callback.apply(this)爲回調 if (applyImeWindowsIfNeeded(callback, traverseTopToBottom) || callback.apply(this)) { return true; } } else { if (callback.apply(this) || applyImeWindowsIfNeeded(callback, traverseTopToBottom)) { return true; } } return false; } //callback.apply(this)爲回調函數實現 //frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> { final AppWindowToken focusedApp = mService.mFocusedApp; if (DEBUG_FOCUS) Slog.v(TAG_WM, "Looking for focus: " + w + ", flags=" + w.mAttrs.flags + ", canReceive=" + w.canReceiveKeys()); if (!w.canReceiveKeys()) { return false; } final AppWindowToken wtoken = w.mAppToken; // If this window's application has been removed, just skip it. if (wtoken != null && (wtoken.removed || wtoken.sendingToBottom)) { if (DEBUG_FOCUS) Slog.v(TAG_WM, "Skipping " + wtoken + " because " + (wtoken.removed ? "removed" : "sendingToBottom")); return false; } if (focusedApp == null) { if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp=null" + " using new focus @ " + w); mTmpWindow = w; return true; } if (!focusedApp.windowsAreFocusable()) { // Current focused app windows aren't focusable... if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp windows not" + " focusable using new focus @ " + w); mTmpWindow = w; return true; } // Descend through all of the app tokens and find the first that either matches // win.mAppToken (return win) or mFocusedApp (return null). if (wtoken != null && w.mAttrs.type != TYPE_APPLICATION_STARTING) { if (focusedApp.compareTo(wtoken) > 0) { // App stack below focused app stack. No focus for you!!! if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: Reached focused app=" + focusedApp); mTmpWindow = null; return true; } } if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: Found new focus @ " + w); mTmpWindow = w; return true; };
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java //接下來系統開始通知程序端哪個window獲得了焦點。 final class H extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case REPORT_FOCUS_CHANGE: { WindowState lastFocus; WindowState newFocus; //省略。。。 if (newFocus != null) { //通知新的焦點程序其獲得了焦點 newFocus.reportFocusChangedSerialized(true, mInTouchMode); notifyFocusChanged(); } if (lastFocus != null) { //通知老的焦點程序其獲得了焦點 lastFocus.reportFocusChangedSerialized(false, mInTouchMode); } } break; } //frameworks/base/services/core/java/com/android/server/wm/WindowState.java public void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) { try { //這個就是通過Binder告知client其獲得或失去了焦點 //mClient.windowFocusChanged會調回到ViewRootImpl中的W實例 mClient.windowFocusChanged(focused, inTouchMode); } catch (RemoteException e) { } //省略。。。 }
通知應用APP程序獲得焦點改變事件
//ViewRootImpl.java static class W extends IWindow.Stub { @Override public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.windowFocusChanged(hasFocus, inTouchMode); } } //ViewRootImpl.java public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { Message msg = Message.obtain(); msg.what = MSG_WINDOW_FOCUS_CHANGED; msg.arg1 = hasFocus ? 1 : 0; msg.arg2 = inTouchMode ? 1 : 0; mHandler.sendMessage(msg); } //ViewRootImpl.java //程序獲得焦點會通過調用mView.dispatchWindowFocusChanged和 //imm.onWindowFocus來通知IMMS焦點信息發生改變,需要更新輸入法了 final class ViewRootHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_WINDOW_FOCUS_CHANGED: { handleWindowFocusChanged(); } break; } private void handleWindowFocusChanged() { //省略... if (mView != null) { mAttachInfo.mKeyDispatchState.reset(); //調用根view的dispatchWindowFocusChanged函數通知view //程序獲得焦點 mView.dispatchWindowFocusChanged(hasWindowFocus); mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); if (mAttachInfo.mTooltipHost != null) { mAttachInfo.mTooltipHost.hideTooltip(); } } // Note: must be done after the focus change callbacks, // so all of the view state is set up correctly. if (hasWindowFocus) { if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { //通知imm該window獲得焦點 imm.onPostWindowFocus(mView, mView.findFocus(), mWindowAttributes.softInputMode, !mHasHadWindowFocus, mWindowAttributes.flags); } //省略... } //省略... } //上面的根view就是DecorView,它只是調用父類ViewGroup //的dispatchWindowFocusChanged //ViewGroup.java @Override public void dispatchWindowFocusChanged(boolean hasFocus) { super.dispatchWindowFocusChanged(hasFocus); final int count = mChildrenCount; final View[] children = mChildren; //讓每個子view處理window焦點改變時間 //但是隻有獲得焦點的view纔會處理這個時間 for (int i = 0; i < count; i++) { children[i].dispatchWindowFocusChanged(hasFocus); } } //View.java public void dispatchWindowFocusChanged(boolean hasFocus) { onWindowFocusChanged(hasFocus); } public void onWindowFocusChanged(boolean hasWindowFocus) { InputMethodManager imm = InputMethodManager.peekInstance(); if (!hasWindowFocus) { } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) { //獲得焦點的view通過 InputMethodManager向service通知自己獲得焦點 imm.focusIn(this); } }
焦點view請求綁定輸入法是通過調用InputMethodManager. focusIn實現的
//frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java public void focusIn(View view) { synchronized (mH) { focusInLocked(view); } } void focusInLocked(View view) { //保存焦點view變量 mNextServedView = view; scheduleCheckFocusLocked(view); } static void scheduleCheckFocusLocked(View view) { ViewRootImpl viewRootImpl = view.getViewRootImpl(); if (viewRootImpl != null) { viewRootImpl.dispatchCheckFocus(); } } //frameworks/base/core/java/android/view/ViewRootImpl.java public void dispatchCheckFocus() { if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) { // This will result in a call to checkFocus() below. mHandler.sendEmptyMessage(MSG_CHECK_FOCUS); } } public void handleMessage(Message msg) { switch (msg.what) { //省略。。。 case MSG_CHECK_FOCUS: { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { imm.checkFocus(); } } break; //省略。。。 } //frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java public void checkFocus() { if (checkFocusNoStartInput(false)) { startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0); } } //frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason, IBinder windowGainingFocus, int controlFlags, int softInputMode, int windowFlags) { final View view; synchronized (mH) { //獲得上面的焦點view view = mServedView; } //省略。。。 //創建數據通信連接接口,這個會傳送到InputMethodService //InputMethodService後面就通過這個connection將輸入法的字符傳遞給該view InputConnection ic = view.onCreateInputConnection(tba); if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic); synchronized (mH) { //省略。。。 ControlledInputConnectionWrapper servedContext; final int missingMethodFlags; if (ic != null) { mCursorSelStart = tba.initialSelStart; mCursorSelEnd = tba.initialSelEnd; mCursorCandStart = -1; mCursorCandEnd = -1; mCursorRect.setEmpty(); mCursorAnchorInfo = null; final Handler icHandler; missingMethodFlags = InputConnectionInspector.getMissingMethodFlags(ic); if ((missingMethodFlags & InputConnectionInspector.MissingMethodFlags.GET_HANDLER) != 0) { // InputConnection#getHandler() is not implemented. icHandler = null; } else { icHandler = ic.getHandler(); } //將InputConnection封裝爲binder對象,這個是真正可以實現跨進程通 //信的封裝類 servedContext = new ControlledInputConnectionWrapper( icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this); } else { servedContext = null; missingMethodFlags = 0; } mServedInputConnectionWrapper = servedContext; try { //通知IMMS處理view綁定輸入法事件 final InputBindResult res = mService.startInputOrWindowGainedFocus( startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode, windowFlags, tba, servedContext, missingMethodFlags, view.getContext().getApplicationInfo().targetSdkVersion); //省略。。。 } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId, e); } } return true; }
到此view是如何獲取焦點,並通知IMMS去綁定輸入法的流程已經分析完成了。下一步將分析IMMS是如何處理view綁定輸入法事件。未完待續。。。