Android 输入法窗口焦点获取流程(2) ,输入法窗口和应用窗口绑定

基于Android9.x

目录

1 ActvityRecord状态变化
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 结果返回

Window和Session创建成功后,窗口的下一步流程为获取焦点
我们看下焦点获取过程,跟输入法相关的流程

ActvityRecord状态变化

    static enum ActivityState {
        INITIALIZING,
        RESUMED,
        PAUSING,
        PAUSED,
        STOPPING,
        STOPPED,
        FINISHING,//finish过程,只有当显示或者被动的去finish activity时候,才会调用
        DESTROYING,
        DESTROYED;

        private ActivityState() {
        }
    }

两个Activity切换时,对应的状态变化过程为:


ActivityRecord状态变化

窗口和输入法绑定

以下是Activity窗口初次获取焦点的流程

当两个activity 切换时,失去焦点的窗口调用过程如下:

输入法#失去焦点的窗口变化.png

对应的,获取焦点的额窗口的调用过程如下:

获取窗口的焦点变化
  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);
  }

当B窗口的状态切换到RESUMED时,当窗口的focus可能变化时,会调用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相关参数
                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相关参数
                        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. 如果是获取窗口焦点过程,调用onPostWindowFocus:
    mAttachInfo:存储当前Veiw和绑定的窗口相关的信息
    mAttachInfo.mThreadedRenderer:渲染线程
    当前窗口与获取焦点时,初始化渲染线程相关参数,保存当前窗口状态到mAttachInfo(例如,是否获取了焦点);然后调用imm.onPostWindowFocus去执行绑定输入法的操作

  2. 如果是失去窗口焦点过程,则直接回调onWindowFocusChanged;也即是说,失去焦点的窗口,不再跟输入法发生关联

imm.onPostWindowFocus

在该方法中,判断,如果还没有执行startInputInner方法,则执行startInputInner方法,否则,直接执行startInputOrWindowGainedFocus方法

  • rootView:启动的activity的DecorView

  • focusedView:获取焦点的view,一般是一个EditorText;对于初次进入一个Activity,并且没有EditorText自动获取焦点,它为null
    WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
    软键盘直接覆盖Activity,通常这是默认值
    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:第一次进入时为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) {//第一次进入改activity窗口,设置为CONTROL_WINDOW_FIRST
            controlFlags |= CONTROL_WINDOW_FIRST;
        }
        //检查是否执行过startInput,只有在第一次获取焦点时,才会执行startinput;例如:在该activity弹出一个弹框,弹框再消失会重新获取焦点,此
        //中场景不会重复执行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");
                //不执行startInputInner的情况下,携带参数START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY执行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:设置controlFlags的flag为CONTROL_WINDOW_FIRST
2:检查是否已经执行过startInputInner,没有的话执行startInputInner-->startInputOrWindowGainedFocus;否则,直接执行startInputOrWindowGainedFocus
两条路径,携带的startInputReason参数不一样

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;
        }
        //负责输入法应用和客户端应用的通信
        final ControlledInputConnectionWrapper ic;
        synchronized (mH) {
            //当前后两次获取的焦点相同,并且不强制重新获取焦点的时候,不执行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,只有当获取焦点的窗口是EditorText时,该对象才会创建;当前场景为Activity 
            ic = mServedInputConnectionWrapper;

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

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

        return true;//返回true,接着执行startInputInner
    }

主要流程:
1:检查要启动和退出的ServedView是否为同一个,如果为同一个,则表示已经执行过startInputInner,则返回false,表示不再执行startInputInner
2:如果获取焦点的是EditorText,会创建跟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;
            }
        }
        ...
        //创建一个EditorInfo,存储应用层,跟输入法相关的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.
        //存储应用程序包名
        tba.packageName = view.getContext().getOpPackageName();
        tba.fieldId = view.getId();
       //用于输入法应用和输入框通信的InputConnection对象,因为此时的view为DecorView,并没有复写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) {//多任务操作场景,例如输入法还没弹出前,启动了其他创库哦,导致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返回获取窗口焦点的结果
                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) {
                    //根据返回的结果,初始化本地输入法相关变量,记录当前系统中默认的输入法信息
                    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:创建EditorInfo对象tba,这个参数对TextView布局才有意义,它的初始化是在mServedView的onCreateInputConnection完成实例化的
2:根据EditorInfo创建一个InputConnection对象,输入法应用通过该对象,完成输入内容到输入框的传递;ACTIVITY获取焦点场景,该对象
为null,因为没有要输入的对象

startInputOrWindowGainedFocus携带的参数

 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
应用层创建的IInputMethodClient对象,为服务层提供应用层的各个回调方法
该方法跟应用进程首次创建时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:
应用层的ViewRootImpl$W对象

controlFlags |= CONTROL_START_INITIAL;
表示window窗口刚开始获取焦点

softInputMode = SOFT_INPUT_ADJUST_RESIZE , 允许调整输入法窗口,避免被其他窗口遮挡
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没有复写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) {//对应的获取焦点的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;
    }

当应用层传递的W对象windowToken不为null的时候,则创建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();//检查用户权限,是否为当前用户,跨用户是否有权限等
        InputBindResult res = null;
        long ident = Binder.clearCallingIdentity();
        try {
            synchronized (mMethodMap) {
                //因为client在进程第一次创建Session的时候,已经传递给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对应的窗口,是否是获取焦点的窗口,避免调用该处代码是,焦点窗口发生改变
                        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;
                }
                //检查当前获取焦点窗口的公司和已获取焦点窗口的公式,是否为同一个窗口对象,如果为同一个,则表明窗口已经获取获取了焦点,返回SUCCESS_REPORT_WINDOW_FOCUS_ONLY
                //改代码,在上图流程“当该窗口第二次获取焦点时,会跳过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.
                //窗口大小是否去适应输入框窗口,避免窗口被输入法遮挡
                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;
                //根据传入的不同的softInputMode值,控制不同的输入法显示状态
                //SOFT_INPUT_STATE_UNSPECIFIED:没有指定软键盘输入区域的显示状态
                //SOFT_INPUT_STATE_UNCHANGED:不要改变软键盘输入区域的显示状态
                //SOFT_INPUT_STATE_HIDDEN:在合适的时候隐藏软键盘输入区域,例如,当用户导航到当前窗口时
                //SOFT_INPUT_STATE_ALWAYS_HIDDEN:当窗口获得焦点时,总是隐藏软键盘输入区域
                //SOFT_INPUT_STATE_VISIBLE:在合适的时候显示软键盘输入区域,例如,当用户导航到当前窗口时
                //SOFT_INPUT_STATE_ALWAYS_VISIBLE:当窗口获得焦点时,总是显示软键盘输入区域
                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
                   //因为,此时是进入activity的场景,softInputMode满足 SOFT_INPUT_STATE_UNSPECIFIED的场景,因此调用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应用层传递的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) {//当前设置的默认输入法的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进程代表的ClientState,如果是冷启动,进入一个新的应用,则mCurClient != cs;如果是在同一个进程中进行的窗口切换,这mCurClient == cs
        //我们验证的场景是同一个进程的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;//应用层传递的EditorInfo:tba 对象


        // Check if the input method is changing.
        //检查切换过程中,默认输入法是否发生了改变,我们的场景没有发生变化,因此返回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();
        //创建跟窗口关联的输入法的信息类
        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);
        }
        //返回窗口焦点获取结果类
        return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                session.session, (session.channel != null ? session.channel.dup() : null),
                mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
    }

结果返回

结果返回后,会对IMM的对象进行赋值

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) {
                    }
                }

如此,进入一个窗口,获取窗口焦点过程,窗口与输入法相关的流程,就结束了。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容