Android 輸入法窗口焦點獲取流程(2) ,輸入法窗口和應(yīng)用窗口綁定

基于Android9.x

目錄

1 ActvityRecord狀態(tài)變化
2 窗口和輸入法綁定
      2.1 updateFocusedWindowLocked
      2.2 ViewRootImpl#handleWindowFocusChanged
      2.3 imm.onPostWindowFocus
          2.3.1 checkFocusNoStartInput
          2.3.2 startInputInner()
       2.4 InputMethodMangerService#startInputOrWindowGainedFocus
       2.5 windowGainedFocus
       2.6 startInputUncheckedLocked
       2.7 attachNewInputLocked
           2.7.1 結(jié)果返回

Window和Session創(chuàng)建成功后,窗口的下一步流程為獲取焦點
我們看下焦點獲取過程,跟輸入法相關(guān)的流程

ActvityRecord狀態(tài)變化

    static enum ActivityState {
        INITIALIZING,
        RESUMED,
        PAUSING,
        PAUSED,
        STOPPING,
        STOPPED,
        FINISHING,//finish過程,只有當(dāng)顯示或者被動的去finish activity時候,才會調(diào)用
        DESTROYING,
        DESTROYED;

        private ActivityState() {
        }
    }

兩個Activity切換時,對應(yīng)的狀態(tài)變化過程為:


ActivityRecord狀態(tài)變化

窗口和輸入法綁定

以下是Activity窗口初次獲取焦點的流程

當(dāng)兩個activity 切換時,失去焦點的窗口調(diào)用過程如下:

輸入法#失去焦點的窗口變化.png

對應(yīng)的,獲取焦點的額窗口的調(diào)用過程如下:

獲取窗口的焦點變化
  if (focusMayChange) {
       //System.out.println("Focus may change: " + win.mAttrs.getTitle());
        if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
             false /*updateInputWindows*/)) {
             imMayMove = false;
       }
       //System.out.println("Relayout " + win + ": focus=" + mCurrentFocus);
  }

當(dāng)B窗口的狀態(tài)切換到RESUMED時,當(dāng)窗口的focus可能變化時,會調(diào)用updateFocusedWindowLocked

updateFocusedWindowLocked

    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
        WindowState newFocus = mRoot.computeFocusedWindow();
        if (mCurrentFocus != newFocus) {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
            // This check makes sure that we don't already have the focus
            // change message pending.
            mH.removeMessages(H.REPORT_FOCUS_CHANGE);
            mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
            // TODO(multidisplay): Focused windows on default display only.
   }

ViewRootImpl#handleWindowFocusChanged

 private void handleWindowFocusChanged() {
        final boolean hasWindowFocus;
        final boolean inTouchMode;
        synchronized (this) {
            if (!mWindowFocusChanged) {
                return;
            }
            mWindowFocusChanged = false;
            hasWindowFocus = mUpcomingWindowFocus;
            inTouchMode = mUpcomingInTouchMode;
        }

        if (mAdded) {
            profileRendering(hasWindowFocus);

            if (hasWindowFocus) {//獲取焦點后,會初始化mThreadedRenderer相關(guān)參數(shù)
                ensureTouchModeLocally(inTouchMode);
                if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) {
                    mFullRedrawNeeded = true;
                    try {
                        final WindowManager.LayoutParams lp = mWindowAttributes;
                        final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
                        //初始化mThreadedRenderer相關(guān)參數(shù)
                        mAttachInfo.mThreadedRenderer.initializeIfNeeded(
                                mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
                    } catch (OutOfResourcesException e) {
                        Log.e(mTag, "OutOfResourcesException locking surface", e);
                        try {
                            if (!mWindowSession.outOfMemory(mWindow)) {
                                Slog.w(mTag, "No processes killed for memory;"
                                        + " killing self");
                                Process.killProcess(Process.myPid());
                            }
                        } catch (RemoteException ex) {
                        }
                        // Retry in a bit.
                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
                                MSG_WINDOW_FOCUS_CHANGED), 500);
                        return;
                    }
                }
            }

            mAttachInfo.mHasWindowFocus = hasWindowFocus;

            mLastWasImTarget = WindowManager.LayoutParams
                    .mayUseInputMethod(mWindowAttributes.flags);

            InputMethodManager imm = InputMethodManager.peekInstance();
            if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
                imm.onPreWindowFocus(mView, hasWindowFocus);
            }
            if (mView != null) {
                mAttachInfo.mKeyDispatchState.reset();
                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) {
                //FLAG_LOCAL_FOCUS_MODE允許獨立于window manager來控制焦點事件
                //通常該模式的窗口不能從window manager獲取觸摸/按鍵事件, 但能夠通過Window#injectInputEvent(InputEvent)來獲取本地注入事件
                if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
                    imm.onPostWindowFocus(mView, mView.findFocus(),
                            mWindowAttributes.softInputMode,
                            !mHasHadWindowFocus, mWindowAttributes.flags);
                }
                // Clear the forward bit.  We can just do this directly, since
                // the window manager doesn't care about it.
                mWindowAttributes.softInputMode &=
                        ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
                ((WindowManager.LayoutParams) mView.getLayoutParams())
                        .softInputMode &=
                        ~WindowManager.LayoutParams
                                .SOFT_INPUT_IS_FORWARD_NAVIGATION;
                mHasHadWindowFocus = true;

                // Refocusing a window that has a focused view should fire a
                // focus event for the view since the global focused view changed.
                fireAccessibilityFocusEventIfHasFocusedNode();
            } else {
                if (mPointerCapture) {
                    handlePointerCaptureChanged(false);
                }
            }
        }
        mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
    }
  1. 如果是獲取窗口焦點過程,調(diào)用onPostWindowFocus:
    mAttachInfo:存儲當(dāng)前Veiw和綁定的窗口相關(guān)的信息
    mAttachInfo.mThreadedRenderer:渲染線程
    當(dāng)前窗口與獲取焦點時,初始化渲染線程相關(guān)參數(shù),保存當(dāng)前窗口狀態(tài)到mAttachInfo(例如,是否獲取了焦點);然后調(diào)用imm.onPostWindowFocus去執(zhí)行綁定輸入法的操作

  2. 如果是失去窗口焦點過程,則直接回調(diào)onWindowFocusChanged;也即是說,失去焦點的窗口,不再跟輸入法發(fā)生關(guān)聯(lián)

imm.onPostWindowFocus

在該方法中,判斷,如果還沒有執(zhí)行startInputInner方法,則執(zhí)行startInputInner方法,否則,直接執(zhí)行startInputOrWindowGainedFocus方法

  • rootView:啟動的activity的DecorView

  • focusedView:獲取焦點的view,一般是一個EditorText;對于初次進(jìn)入一個Activity,并且沒有EditorText自動獲取焦點,它為null
    WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
    軟鍵盤直接覆蓋Activity,通常這是默認(rèn)值
    WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
    Activity高度會變化,讓出軟鍵盤的空間。和WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN 為2選1的值
    WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
    Activity一打開就直接顯示軟鍵盤窗口,如果該窗口需要的話(即有EditText,或有ditable的控件)
    WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
    Activity打開后并不直接顯示軟鍵盤窗口,直到用戶自己touch文本框。

  • first:第一次進(jìn)入時為true

  /**
     * Called by ViewAncestor when its window gets input focus.
     * @hide
     */
    public void onPostWindowFocus(View rootView, View focusedView,
            @SoftInputModeFlags int softInputMode, boolean first, int windowFlags) {
        boolean forceNewFocus = false;
        synchronized (mH) {
            if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
                    + " softInputMode=" + InputMethodClient.softInputModeToString(softInputMode)
                    + " first=" + first + " flags=#"
                    + Integer.toHexString(windowFlags));
            if (mRestartOnNextWindowFocus) {
                if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus");
                mRestartOnNextWindowFocus = false;
                forceNewFocus = true;
            }
            focusInLocked(focusedView != null ? focusedView : rootView);
        }

        int controlFlags = 0;
        if (focusedView != null) {//窗口獲取焦點過程,它為null
            controlFlags |= CONTROL_WINDOW_VIEW_HAS_FOCUS;
            if (focusedView.onCheckIsTextEditor()) {
                controlFlags |= CONTROL_WINDOW_IS_TEXT_EDITOR;
            }
        }
        if (first) {//第一次進(jìn)入改activity窗口,設(shè)置為CONTROL_WINDOW_FIRST
            controlFlags |= CONTROL_WINDOW_FIRST;
        }
        //檢查是否執(zhí)行過startInput,只有在第一次獲取焦點時,才會執(zhí)行startinput;例如:在該activity彈出一個彈框,彈框再消失會重新獲取焦點,此
        //中場景不會重復(fù)執(zhí)行startInputInner
        if (checkFocusNoStartInput(forceNewFocus)) {
            // We need to restart input on the current focus view.  This
            // should be done in conjunction with telling the system service
            // about the window gaining focus, to help make the transition
            // smooth.
            if (startInputInner(InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN,
                    rootView.getWindowToken(), controlFlags, softInputMode, windowFlags)) {
                return;
            }
        }

        // For some reason we didn't do a startInput + windowFocusGain, so
        // we'll just do a window focus gain and call it a day.
        synchronized (mH) {
            try {
                if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
                //不執(zhí)行startInputInner的情況下,攜帶參數(shù)START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY執(zhí)行startInputOrWindowGainedFocus
                mService.startInputOrWindowGainedFocus(
                        InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
                        rootView.getWindowToken(), controlFlags, softInputMode, windowFlags, null,
                        null, 0 /* missingMethodFlags */,
                        rootView.getContext().getApplicationInfo().targetSdkVersion);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

主要流程:
1:設(shè)置controlFlags的flag為CONTROL_WINDOW_FIRST
2:檢查是否已經(jīng)執(zhí)行過startInputInner,沒有的話執(zhí)行startInputInner-->startInputOrWindowGainedFocus;否則,直接執(zhí)行startInputOrWindowGainedFocus
兩條路徑,攜帶的startInputReason參數(shù)不一樣

checkFocusNoStartInput

    private boolean checkFocusNoStartInput(boolean forceNewFocus) {
        // This is called a lot, so short-circuit before locking.
        //mServedView:將要退出的activity
        //將要啟動的ActivitymNextServedView
        //forceNewFocus false
        if (mServedView == mNextServedView && !forceNewFocus) {
            return false;
        }
        //負(fù)責(zé)輸入法應(yīng)用和客戶端應(yīng)用的通信
        final ControlledInputConnectionWrapper ic;
        synchronized (mH) {
            //當(dāng)前后兩次獲取的焦點相同,并且不強制重新獲取焦點的時候,不執(zhí)行startInput
            if (mServedView == mNextServedView && !forceNewFocus) {
                return false;
            }
            if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
                    + " next=" + mNextServedView
                    + " forceNewFocus=" + forceNewFocus
                    + " package="
                    + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>"));

            if (mNextServedView == null) {
                finishInputLocked();
                // In this case, we used to have a focused view on the window,
                // but no longer do.  We should make sure the input method is
                // no longer shown, since it serves no purpose.
                closeCurrentInput();
                return false;
            }
            //mServedInputConnectionWrapper為null,只有當(dāng)獲取焦點的窗口是EditorText時,該對象才會創(chuàng)建;當(dāng)前場景為Activity 
            ic = mServedInputConnectionWrapper;

            mServedView = mNextServedView;
            mCurrentTextBoxAttribute = null;
            mCompletions = null;
            mServedConnecting = true;
        }

        if (ic != null) {
            ic.finishComposingText();
        }

        return true;//返回true,接著執(zhí)行startInputInner
    }

主要流程:
1:檢查要啟動和退出的ServedView是否為同一個,如果為同一個,則表示已經(jīng)執(zhí)行過startInputInner,則返回false,表示不再執(zhí)行startInputInner
2:如果獲取焦點的是EditorText,會創(chuàng)建跟IMS通信的mServedInputConnectionWrapper對象

startInputInner()

 boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
            IBinder windowGainingFocus, int controlFlags, int softInputMode,
            int windowFlags) {
        final View view;
        synchronized (mH) {
            view = mServedView;//賦值為mServedView,也就是DecorView

            // Make sure we have a window token for the served view.
            if (DEBUG) {
                Log.v(TAG, "Starting input: view=" + dumpViewInfo(view) +
                        " reason=" + InputMethodClient.getStartInputReason(startInputReason));
            }
            if (view == null) {
                if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
                return false;
            }
        }
        ...
        //創(chuàng)建一個EditorInfo,存儲應(yīng)用層,跟輸入法相關(guān)的Edit對象,典型的對象是EditorText
        EditorInfo tba = new EditorInfo();
        // Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the
        // system can verify the consistency between the uid of this process and package name passed
        // from here. See comment of Context#getOpPackageName() for details.
        //存儲應(yīng)用程序包名
        tba.packageName = view.getContext().getOpPackageName();
        tba.fieldId = view.getId();
       //用于輸入法應(yīng)用和輸入框通信的InputConnection對象,因為此時的view為DecorView,并沒有復(fù)寫onCreateInputConnection,因此返回null
        InputConnection ic = view.onCreateInputConnection(tba);
        if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);

        synchronized (mH) {
            // Now that we are locked again, validate that our state hasn't
            // changed.
            if (mServedView != view || !mServedConnecting) {//多任務(wù)操作場景,例如輸入法還沒彈出前,啟動了其他創(chuàng)庫哦,導(dǎo)致mServedView != view
                // Something else happened, so abort.
                if (DEBUG) Log.v(TAG,
                        "Starting input: finished by someone else. view=" + dumpViewInfo(view)
                        + " mServedView=" + dumpViewInfo(mServedView)
                        + " mServedConnecting=" + mServedConnecting);
                return false;
            }

            // If we already have a text box, then this view is already
            // connected so we want to restart it.
            if (mCurrentTextBoxAttribute == null) {
                //表示window剛獲取focus焦點
                controlFlags |= CONTROL_START_INITIAL;
            }

            // Hook 'em up and let 'er rip.
            mCurrentTextBoxAttribute = tba;
            mServedConnecting = false;
            if (mServedInputConnectionWrapper != null) {
                mServedInputConnectionWrapper.deactivate();
                mServedInputConnectionWrapper = null;
            }
            ControlledInputConnectionWrapper servedContext;
            final int missingMethodFlags;
            if (ic != null) {
               //...
            } else {
                servedContext = null;
                missingMethodFlags = 0;
            }
            mServedInputConnectionWrapper = servedContext;//null

                try {
                if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
                        + ic + " tba=" + tba + " controlFlags=#"
                        + Integer.toHexString(controlFlags));
                //  從IMMS返回獲取窗口焦點的結(jié)果
                final InputBindResult res = mService.startInputOrWindowGainedFocus(
                        startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
                        windowFlags, tba, servedContext, missingMethodFlags,
                        view.getContext().getApplicationInfo().targetSdkVersion);
                if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
                if (res == null) {
                    Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
                            + " null. startInputReason="
                            + InputMethodClient.getStartInputReason(startInputReason)
                            + " editorInfo=" + tba
                            + " controlFlags=#" + Integer.toHexString(controlFlags));
                    return false;
                }
                if (res.id != null) {
                    //根據(jù)返回的結(jié)果,初始化本地輸入法相關(guān)變量,記錄當(dāng)前系統(tǒng)中默認(rèn)的輸入法信息
                    setInputChannelLocked(res.channel);
                    mBindSequence = res.sequence;
                    mCurMethod = res.method;
                    mCurId = res.id;
                    mNextUserActionNotificationSequenceNumber =
                            res.userActionNotificationSequenceNumber;
                } else if (res.channel != null && res.channel != mCurChannel) {
                    res.channel.dispose();
                }
                switch (res.result) {
                    case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
                        mRestartOnNextWindowFocus = true;
                        break;
                }
                if (mCurMethod != null && mCompletions != null) {
                    try {
                        mCurMethod.displayCompletions(mCompletions);
                    } catch (RemoteException e) {
                    }
                }
            } catch (RemoteException e) {
                Log.w(TAG, "IME died: " + mCurId, e);
            }
        }

        return true;
    }

主要流程:
1:創(chuàng)建EditorInfo對象tba,這個參數(shù)對TextView布局才有意義,它的初始化是在mServedView的onCreateInputConnection完成實例化的
2:根據(jù)EditorInfo創(chuàng)建一個InputConnection對象,輸入法應(yīng)用通過該對象,完成輸入內(nèi)容到輸入框的傳遞;ACTIVITY獲取焦點場景,該對象
為null,因為沒有要輸入的對象

startInputOrWindowGainedFocus攜帶的參數(shù)

 mService.startInputOrWindowGainedFocus(
                        startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
                        windowFlags, tba, servedContext, missingMethodFlags,
                        view.getContext().getApplicationInfo().targetSdkVersion);

startInputReason = 1

表示,該流程是窗口獲取焦點過程

    public static final int START_INPUT_REASON_UNSPECIFIED = 0;
    public static final int START_INPUT_REASON_WINDOW_FOCUS_GAIN = 1;
    public static final int START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY = 2;
    public static final int START_INPUT_REASON_APP_CALLED_RESTART_INPUT_API = 3;
    public static final int START_INPUT_REASON_CHECK_FOCUS = 4;
    public static final int START_INPUT_REASON_BOUND_TO_IMMS = 5;
    public static final int START_INPUT_REASON_UNBOUND_FROM_IMMS = 6;
    public static final int START_INPUT_REASON_ACTIVATED_BY_IMMS = 7;
    public static final int START_INPUT_REASON_DEACTIVATED_BY_IMMS = 8;
    public static final int START_INPUT_REASON_SESSION_CREATED_BY_IME = 9;

mClient
應(yīng)用層創(chuàng)建的IInputMethodClient對象,為服務(wù)層提供應(yīng)用層的各個回調(diào)方法
該方法跟應(yīng)用進(jìn)程首次創(chuàng)建時Session時,傳遞到IMMS的對象是同一個對象

 final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
        @Override
        protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
           //...
        }

        @Override
        public void setUsingInputMethod(boolean state) {
        }

        @Override
        public void onBindMethod(InputBindResult res) {
        }

        @Override
        public void onUnbindMethod(int sequence, @InputMethodClient.UnbindReason int unbindReason) {
        }

        @Override
        public void setActive(boolean active, boolean fullscreen) {
        }

        @Override
        public void setUserActionNotificationSequenceNumber(int sequenceNumber) {
        }

        @Override
        public void reportFullscreenMode(boolean fullscreen) {
        }

    };

windowGainingFocus:
應(yīng)用層的ViewRootImpl$W對象

controlFlags |= CONTROL_START_INITIAL;
表示window窗口剛開始獲取焦點

softInputMode = SOFT_INPUT_ADJUST_RESIZE , 允許調(diào)整輸入法窗口,避免被其他窗口遮擋
tba, EditorInfo對象

        EditorInfo tba = new EditorInfo();
        // Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the
        // system can verify the consistency between the uid of this process and package name passed
        // from here. See comment of Context#getOpPackageName() for details.
        tba.packageName = view.getContext().getOpPackageName();
        tba.fieldId = view.getId();

servedContext
null
missingMethodFlags

InputConnection ic = view.onCreateInputConnection(tba);
此時的view是指activity的DecorView,該view沒有復(fù)寫onCreateInputConnection方法,因此返回null

ic等于null的情況下,為0

InputMethodMangerService#startInputOrWindowGainedFocus

  @NonNull
    @Override
    public InputBindResult startInputOrWindowGainedFocus(
            /* @InputMethodClient.StartInputReason */ final int startInputReason,
            IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
            int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
            /* @InputConnectionInspector.missingMethods */ final int missingMethods,
            int unverifiedTargetSdkVersion) {

        final InputBindResult result;
        if (windowToken != null) {//對應(yīng)的獲取焦點的Activity的窗口
            result = windowGainedFocus(startInputReason, client, windowToken, controlFlags,
                    softInputMode, windowFlags, attribute, inputContext, missingMethods,
                    unverifiedTargetSdkVersion);
        } else {
            result = startInput(startInputReason, client, inputContext, missingMethods, attribute,
                    controlFlags);
        }
        if (result == null) {
            // This must never happen, but just in case.
            Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
                    + InputMethodClient.getStartInputReason(startInputReason)
                    + " windowFlags=#" + Integer.toHexString(windowFlags)
                    + " editorInfo=" + attribute);
            return InputBindResult.NULL;
        }
        return result;
    }

當(dāng)應(yīng)用層傳遞的W對象windowToken不為null的時候,則創(chuàng)建windowGainedFocus對象,返回給app

windowGainedFocus

 @NonNull
    private InputBindResult windowGainedFocus(
            /* @InputMethodClient.StartInputReason */ final int startInputReason,
            IInputMethodClient client, IBinder windowToken, int controlFlags,
            /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
            int windowFlags, EditorInfo attribute, IInputContext inputContext,
            /* @InputConnectionInspector.missingMethods */  final int missingMethods,
            int unverifiedTargetSdkVersion) {
        // Needs to check the validity before clearing calling identity
        final boolean calledFromValidUser = calledFromValidUser();//檢查用戶權(quán)限,是否為當(dāng)前用戶,跨用戶是否有權(quán)限等
        InputBindResult res = null;
        long ident = Binder.clearCallingIdentity();
        try {
            synchronized (mMethodMap) {
                //因為client在進(jìn)程第一次創(chuàng)建Session的時候,已經(jīng)傳遞給IMMS了,因此這里不為null
                ClientState cs = mClients.get(client.asBinder());
                if (cs == null) {
                    throw new IllegalArgumentException("unknown client "
                            + client.asBinder());
                }

                try {
                    if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
                        // Check with the window manager to make sure this client actually
                        // has a window with focus.  If not, reject.  This is thread safe
                        // because if the focus changes some time before or after, the
                        // next client receiving focus that has any interest in input will
                        // be calling through here after that change happens.
                        //為了保證線程安全,會檢查改client對應(yīng)的窗口,是否是獲取焦點的窗口,避免調(diào)用該處代碼是,焦點窗口發(fā)生改變
                        if (DEBUG) {
                            Slog.w(TAG, "Focus gain on non-focused client " + cs.client
                                    + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
                        }
                        return InputBindResult.NOT_IME_TARGET_WINDOW;
                    }
                } catch (RemoteException e) {
                }
                //
                if (!calledFromValidUser) {
                    Slog.w(TAG, "A background user is requesting window. Hiding IME.");
                    Slog.w(TAG, "If you want to interect with IME, you need "
                            + "android.permission.INTERACT_ACROSS_USERS_FULL");
                    hideCurrentInputLocked(0, null);
                    return InputBindResult.INVALID_USER;
                }
                //檢查當(dāng)前獲取焦點窗口的公司和已獲取焦點窗口的公式,是否為同一個窗口對象,如果為同一個,則表明窗口已經(jīng)獲取獲取了焦點,返回SUCCESS_REPORT_WINDOW_FOCUS_ONLY
                //改代碼,在上圖流程“當(dāng)該窗口第二次獲取焦點時,會跳過startInputInner過程”會走改段代碼邏輯
                if (mCurFocusedWindow == windowToken) {
                    if (DEBUG) {
                        Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
                                + " attribute=" + attribute + ", token = " + windowToken);
                    }
                    if (attribute != null) {
                        return startInputUncheckedLocked(cs, inputContext, missingMethods,
                                attribute, controlFlags, startInputReason);
                    }
                    return new InputBindResult(
                            InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
                            null, null, null, -1, -1);
                }
                mCurFocusedWindow = windowToken;
                mCurFocusedWindowSoftInputMode = softInputMode;
                mCurFocusedWindowClient = cs;

                // Should we auto-show the IME even if the caller has not
                // specified what should be done with it?
                // We only do this automatically if the window can resize
                // to accommodate the IME (so what the user sees will give
                // them good context without input information being obscured
                // by the IME) or if running on a large screen where there
                // is more room for the target window + IME.
                //窗口大小是否去適應(yīng)輸入框窗口,避免窗口被輸入法遮擋
                final boolean doAutoShow =
                        (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
                                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
                        || mRes.getConfiguration().isLayoutSizeAtLeast(
                                Configuration.SCREENLAYOUT_SIZE_LARGE);
                //焦點時否在一個Editor text上
                final boolean isTextEditor =
                        (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;

                // We want to start input before showing the IME, but after closing
                // it.  We want to do this after closing it to help the IME disappear
                // more quickly (not get stuck behind it initializing itself for the
                // new focused input, even if its window wants to hide the IME).
                boolean didStart = false;
                //根據(jù)傳入的不同的softInputMode值,控制不同的輸入法顯示狀態(tài)
                //SOFT_INPUT_STATE_UNSPECIFIED:沒有指定軟鍵盤輸入?yún)^(qū)域的顯示狀態(tài)
                //SOFT_INPUT_STATE_UNCHANGED:不要改變軟鍵盤輸入?yún)^(qū)域的顯示狀態(tài)
                //SOFT_INPUT_STATE_HIDDEN:在合適的時候隱藏軟鍵盤輸入?yún)^(qū)域,例如,當(dāng)用戶導(dǎo)航到當(dāng)前窗口時
                //SOFT_INPUT_STATE_ALWAYS_HIDDEN:當(dāng)窗口獲得焦點時,總是隱藏軟鍵盤輸入?yún)^(qū)域
                //SOFT_INPUT_STATE_VISIBLE:在合適的時候顯示軟鍵盤輸入?yún)^(qū)域,例如,當(dāng)用戶導(dǎo)航到當(dāng)前窗口時
                //SOFT_INPUT_STATE_ALWAYS_VISIBLE:當(dāng)窗口獲得焦點時,總是顯示軟鍵盤輸入?yún)^(qū)域
                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
                   //因為,此時是進(jìn)入activity的場景,softInputMode滿足 SOFT_INPUT_STATE_UNSPECIFIED的場景,因此調(diào)用hideCurrentInputLocked
                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
                        if (!isTextEditor || !doAutoShow) {
                            if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
                                // There is no focus view, and this window will
                                // be behind any soft input window, so hide the
                                // soft input window if it is shown.
                                if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
                                //窗口上沒有獲取焦點的view,因此隱藏輸入法窗口
                                hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
                            }
                        } else if (isTextEditor && doAutoShow && (softInputMode &
                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
                            // There is a focus view, and we are navigating forward
                            // into the window, so show the input window for the user.
                            // We only do this automatically if the window can resize
                            // to accommodate the IME (so what the user sees will give
                            // them good context without input information being obscured
                            // by the IME) or if running on a large screen where there
                            // is more room for the target window + IME.
                            if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
                            if (attribute != null) {
                                res = startInputUncheckedLocked(cs, inputContext,
                                        missingMethods, attribute, controlFlags, startInputReason);
                                didStart = true;
                            }
                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
                        }
                        break;
                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
                        // Do nothing.
                        break;
                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
                        if ((softInputMode &
                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
                            if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
                            hideCurrentInputLocked(0, null);
                        }
                        break;
                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
                        if (DEBUG) Slog.v(TAG, "Window asks to hide input");
                        hideCurrentInputLocked(0, null);
                        break;
                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
                        if ((softInputMode &
                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
                            if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
                            if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
                                    unverifiedTargetSdkVersion, controlFlags)) {
                                if (attribute != null) {
                                    res = startInputUncheckedLocked(cs, inputContext,
                                            missingMethods, attribute, controlFlags,
                                            startInputReason);
                                    didStart = true;
                                }
                                showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
                            } else {
                                Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
                                        + " there is no focused view that also returns true from"
                                        + " View#onCheckIsTextEditor()");
                            }
                        }
                        break;
                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
                        if (DEBUG) Slog.v(TAG, "Window asks to always show input");
                        if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
                                unverifiedTargetSdkVersion, controlFlags)) {
                            if (attribute != null) {
                                res = startInputUncheckedLocked(cs, inputContext, missingMethods,
                                        attribute, controlFlags, startInputReason);
                                didStart = true;
                            }
                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
                        } else {
                            Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
                                    + " there is no focused view that also returns true from"
                                    + " View#onCheckIsTextEditor()");
                        }
                        break;
                }
                //隱藏輸入法場景,因此didStart = false
                if (!didStart) {
                    if (attribute != null) {
                        if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
                                || (controlFlags
                                & InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) {
                            //inputContext應(yīng)用層傳遞的servedContext對象,activity獲取焦點場景為null
                            res = startInputUncheckedLocked(cs, inputContext, missingMethods,
                                    attribute,
                                    controlFlags, startInputReason);
                        } else {
                            res = InputBindResult.NO_EDITOR;
                        }
                    } else {
                        res = InputBindResult.NULL_EDITOR_INFO;
                    }
                }
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }

        return res;
    }

startInputUncheckedLocked

    @GuardedBy("mMethodMap")
    @NonNull
    InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
            /* @InputConnectionInspector.missingMethods */ final int missingMethods,
            @NonNull EditorInfo attribute, int controlFlags,
            /* @InputMethodClient.StartInputReason */ final int startInputReason) {
        // If no method is currently selected, do nothing.
        if (mCurMethodId == null) {//當(dāng)前設(shè)置的默認(rèn)輸入法的id
            return InputBindResult.NO_IME;
        }
        //檢查uid和包名的一致性
        if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
                attribute.packageName)) {
            Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
                    + " uid=" + cs.uid + " package=" + attribute.packageName);
            return InputBindResult.INVALID_PACKAGE_NAME;
        }
        //mCurClient進(jìn)程代表的ClientState,如果是冷啟動,進(jìn)入一個新的應(yīng)用,則mCurClient != cs;如果是在同一個進(jìn)程中進(jìn)行的窗口切換,這mCurClient == cs
        //我們驗證的場景是同一個進(jìn)程的A-B切換,因此mCurClient==cs
        if (mCurClient != cs) {//切換activity的同時,被鎖屏了的場景
            // Was the keyguard locked when switching over to the new client?
            mCurClientInKeyguard = isKeyguardLocked();
            // If the client is changing, we need to switch over to the new
            // one.
            unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);
            if (DEBUG) Slog.v(TAG, "switching to client: client="
                    + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);

            // If the screen is on, inform the new client it is active
            if (mIsInteractive) {
                executeOrSendMessage(cs.client, mCaller.obtainMessageIO(
                        MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, cs));
            }
        }

        // Bump up the sequence for this client and attach it.
        mCurSeq++;
        if (mCurSeq <= 0) mCurSeq = 1;
        mCurClient = cs;
        mCurInputContext = inputContext;//非EditorText,為nulll
        mCurInputContextMissingMethods = missingMethods;
        mCurAttribute = attribute;//應(yīng)用層傳遞的EditorInfo:tba 對象


        // Check if the input method is changing.
        //檢查切換過程中,默認(rèn)輸入法是否發(fā)生了改變,我們的場景沒有發(fā)生變化,因此返回true
        if (mCurId != null && mCurId.equals(mCurMethodId)) {
            if (cs.curSession != null) {
                // Fast case: if we are already connected to the input method,
                // then just return it.
                return attachNewInputLocked(startInputReason,
                        (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
            }
           ....
    }

attachNewInputLocked


 @GuardedBy("mMethodMap")
    @NonNull
    InputBindResult attachNewInputLocked(
            /* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
        if (!mBoundToMethod) {
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
            mBoundToMethod = true;
        }

        final Binder startInputToken = new Binder();
        //創(chuàng)建跟窗口關(guān)聯(lián)的輸入法的信息類
        final StartInputInfo info = new StartInputInfo(mCurToken, mCurId, startInputReason,
                !initial, mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode,
                mCurSeq);
        mStartInputMap.put(startInputToken, info);
        mStartInputHistory.addEntry(info);

        final SessionState session = mCurClient.curSession;
        executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
                MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
                startInputToken, session, mCurInputContext, mCurAttribute));
        if (mShowRequested) {
            if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
            showCurrentInputLocked(getAppShowFlags(), null);
        }
        //返回窗口焦點獲取結(jié)果類
        return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                session.session, (session.channel != null ? session.channel.dup() : null),
                mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
    }

結(jié)果返回

結(jié)果返回后,會對IMM的對象進(jìn)行賦值

IMM.java
                if (res.id != null) {
                    setInputChannelLocked(res.channel);
                    mBindSequence = res.sequence;
                    mCurMethod = res.method;
                    mCurId = res.id;
                    mNextUserActionNotificationSequenceNumber =
                            res.userActionNotificationSequenceNumber;
                } else if (res.channel != null && res.channel != mCurChannel) {
                    res.channel.dispose();
                }
                switch (res.result) {
                    case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
                        mRestartOnNextWindowFocus = true;
                        break;
                }
                if (mCurMethod != null && mCompletions != null) {
                    try {
                        mCurMethod.displayCompletions(mCompletions);
                    } catch (RemoteException e) {
                    }
                }

如此,進(jìn)入一個窗口,獲取窗口焦點過程,窗口與輸入法相關(guān)的流程,就結(jié)束了。

下一篇:輸入法在輸入框彈出流程
Android輸入法(3),彈出流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內(nèi)容