Android P輸入法框架系統--view如何觸發綁定

    在PC時代,輸入法的原始輸入來自實體鍵盤,鼠標,然後輸入法將這些事件對應的ASCII碼轉換爲俄文,中文,當然如果是英文是不需要轉換,直接發送即可。而在Android系統裏,由於輸入法dialog永遠沒法成爲焦點window,所以輸入法永遠沒法獲取到按鍵事件,也就是說輸入法的輸入數據只能來自觸摸事件,輸入法顯示出鍵盤(大家稱之爲軟鍵盤),用戶點擊鍵盤UI, 然後輸入法將觸摸事件所在位置的字符當做原始字符輸入,最後組裝成更爲豐富的字符(多個字符組成拼音,然後轉化爲中文),然後就是發送到對應的程序。

1、輸入法整體框架圖

在這裏插入圖片描述
輸入法系統的整個框架如下:
InputMethodManagerService(下文也稱IMMS)負責管理系統的所有輸入法,包括輸入法service(InputMethodService簡稱IMS)加載及切換。程序獲得焦點時,就會通過InputMethodManager向InputMethodManagerService通知自己獲得焦點並請求綁定自己到當前輸入法上。同時,當程序的某個需要輸入法的view比如EditorView獲得焦點時就會通過InputMethodManager向InputMethodManagerService請求顯示輸入法,而這時InputMethodManagerService收到請求後,會將請求的EditText的數據通信接口發送給當前輸入法,並請求顯輸入法。輸入法收到請求後,就顯示自己的UI dialog,同時保存目標view的數據結構,當用戶實現輸入後,直接通過view的數據通信接口將字符傳遞到對應的View。接下來就來分析這些過程。

2、InputMethodManager創建

每個應用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;  
    }  
}

3、APP程序的Window獲得焦點

程序的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;  
}

3.1 焦點window計算

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

3.2 焦點window切換

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

4、焦點View向IMMS請求綁定輸入法

焦點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綁定輸入法事件。未完待續。。。