Android输入法弹出流程

基于Android 9.x

目录

   1 viewClicked流程
       1.1 viewClicked
       1.2 checkFocus
       1.3 startInputInner
       1.4 startInputOrWindowGainedFocus
       1.5 startInputLocked
       1.6 startInputUncheckedLocked
       1.7 attachNewInputLocked
           1.7.1 处理返回的结果
   2 showSoftInput流程
       2.1 showSoftInput
       2.2 IMMS#showSoftInput
       2.3 showCurrentInputLocked
       2.4 IMS$InputMethodImpl$showSoftInput
       2.5 dispatchOnShowInputRequested
       2.6 IMS$showWindow
       2.7 showWindowInner

输入法弹出流程

输入法#拉起流程#输入框弹出流程.png

viewClicked流程

viewClicked

IMM.JAVA
    public void viewClicked(View view) {attachNewInputLocked
        //服务的view是否相同,当两次点击在同一个输入框时,两者相同;否则不同
        //我们首次点击某个输入框为例
        final boolean focusChanged = mServedView != mNextServedView;
        checkFocus();
        synchronized (mH) {
            if ((mServedView != view && (mServedView == null
                    || !mServedView.checkInputConnectionProxy(view)))
                    || mCurrentTextBoxAttribute == null || mCurMethod == null) {
                return;
            }
            try {
                if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);
                mCurMethod.viewClicked(focusChanged);
            } catch (RemoteException e) {
                Log.w(TAG, "IME died: " + mCurId, e);
            }
        }
    }

checkFocus

    public void checkFocus() {
        //检查该view是否已经执行过startInputInner
        //主要是比较最后一个mServedView和当前mNextServedView是否相同
        //该方法逻辑已经在"输入法窗口和应用窗口绑定."讲过,本章节不在介绍
        if (checkFocusNoStartInput(false)) {
            startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);
        }
    }

startInputInner

携带的参数:
-startInputReason:START_INPUT_REASON_CHECK_FOCUS,标明本次调用的目的
-windowGainingFocus:会影响IMMS中startInputOrWindowGainedFocus的调用逻辑
-controlFlags,softInputMode,windowFlags = 0

boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
            IBinder windowGainingFocus, int controlFlags, int softInputMode,
            int windowFlags) {
        final View view;
        synchronized (mH) {
            //mServedView已经在checkFocusNoStartInput中赋值为mNextServedView,因此这里为当前要获取输入法焦点的view
            view = mServedView;

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

        // Now we need to get an input connection from the served view.
        // This is complicated in a couple ways: we can't be holding our lock
        // when calling out to the view, and we need to make sure we call into
        // the view on the same thread that is driving its view hierarchy.
        Handler vh = view.getHandler();
        if (vh == null) {
            // If the view doesn't have a handler, something has changed out
            // from under us, so just close the current input.
            // If we don't close the current input, the current input method can remain on the
            // screen without a connection.
            if (DEBUG) Log.v(TAG, "ABORT input: no handler for view! Close current input.");
            closeCurrentInput();
            return false;
        }
        //判断view是否在UI主线程
        if (vh.getLooper() != Looper.myLooper()) {
            // The view is running on a different thread than our own, so
            // we need to reschedule our work for over there.
            if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
            vh.post(() -> startInputInner(startInputReason, null, 0, 0, 0));
            return false;
        }

        // Okay we are now ready to call into the served view and have it
        // do its stuff.
        // Life is good: let's hook everything up!
        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();
        //通过TextView的onCreateInputConnection方法初始化,EditorInfo,主要是获取inputType和imeOptions
        //ic是一个EditableInputConnection,用来跟IMS通信
        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.
            //正常情况下不会出现mServedView != view,多线程操作会。
            //mServedConnecting在checkFocusNoStartInput过程中,被置位true,为false表示startInputInner被多次执行
            if (mServedView != view || !mServedConnecting) {
                // 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) {
                controlFlags |= CONTROL_START_INITIAL;
            }

            // Hook 'em up and let 'er rip.
            mCurrentTextBoxAttribute = tba;
            //置位false,与checkFocusNoStartInput同步该参数状态
            mServedConnecting = false;
            if (mServedInputConnectionWrapper != null) {
                mServedInputConnectionWrapper.deactivate();
                mServedInputConnectionWrapper = null;
            }
            ControlledInputConnectionWrapper servedContext;
            final int missingMethodFlags;
            //对于EditorText,不为null
            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();
                }
                //创建ControlledInputConnectionWrapper
                servedContext = new ControlledInputConnectionWrapper(
                        icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);
            } else {
                servedContext = null;
                missingMethodFlags = 0;
            }
           //The InputConnection that was last retrieved from the served view.
            mServedInputConnectionWrapper = servedContext;

            try {
                if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
                        + ic + " tba=" + tba + " controlFlags=#"
                        + Integer.toHexString(controlFlags));
                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;
    }

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

该步骤,主要是同InputMethodManagerService通信,获取将要弹出的输入信息,和绑定当前焦点组件到对应的输入法上
调用时,各个参数的值为:

  • startInputReason = 4 = START_INPUT_REASON_CHECK_FOCUS

  • mClient:应用层创建的IInputMethodClient对象

  • windowGainingFocus = null:应用层的ViewRootImpl$W对象

  • softInputMode = 0

  • tba = EditorInfo对象

  • servedContext = ControlledInputConnectionWrapper{connection=com.android.internal.widget.EditableInputConnection@2a772c0 finished=false mParentInputMethodManager.mActive=true}

   @NonNull
    private InputBindResult startInput(
            /* @InputMethodClient.StartInputReason */ final int startInputReason,
            IInputMethodClient client, IInputContext inputContext,
            /* @InputConnectionInspector.missingMethods */ final int missingMethods,
            @Nullable EditorInfo attribute, int controlFlags) {
        //用户合法性验证
        if (!calledFromValidUser()) {
            return InputBindResult.INVALID_USER;
        }
        synchronized (mMethodMap) {
            final long ident = Binder.clearCallingIdentity();
            try {
                //直接调用startInputLocked方法
                return startInputLocked(startInputReason, client, inputContext, missingMethods,
                        attribute, controlFlags);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }

startInputLocked

    @GuardedBy("mMethodMap")
    @NonNull
    InputBindResult startInputLocked(
            /* @InputMethodClient.StartInputReason */ final int startInputReason,
            IInputMethodClient client, IInputContext inputContext,
            /* @InputConnectionInspector.missingMethods */ final int missingMethods,
            @Nullable EditorInfo attribute, int controlFlags) {
        // If no method is currently selected, do nothing.
        //当前默认输入法对应的id
        if (mCurMethodId == null) {
            return InputBindResult.NO_IME;
        }
        //当前应用客户单对应的ClientState对象
        ClientState cs = mClients.get(client.asBinder());
        if (cs == null) {
            throw new IllegalArgumentException("unknown client "
                    + client.asBinder());
        }

        if (attribute == null) {
            Slog.w(TAG, "Ignoring startInput with null EditorInfo."
                    + " uid=" + cs.uid + " pid=" + cs.pid);
            return InputBindResult.NULL_EDITOR_INFO;
        }

        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.
                if (DEBUG) {
                    Slog.w(TAG, "Starting input on non-focused client " + cs.client
                            + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
                }
                return InputBindResult.NOT_IME_TARGET_WINDOW;
            }
        } catch (RemoteException e) {
        }

        return startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
                controlFlags, startInputReason);
    }

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) {
            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;
        }
        //查看启动输入法过程,应用客户端是否发生变化
        if (mCurClient != cs) {
            // 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;//应用客户端传递的serverContext
        mCurInputContextMissingMethods = missingMethods;
        mCurAttribute = attribute;

      
        // Check if the input method is changing.
        //检查输入法是否发生变化,没有发生变化的情况下,调用
        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);
            }
            if (mHaveConnection) {
                if (mCurMethod != null) {
                    // Return to client, and we will get back with it when
                    // we have had a session made for it.
                    requestClientSessionLocked(cs);
                    return new InputBindResult(
                            InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
                            null, null, mCurId, mCurSeq,
                            mCurUserActionNotificationSequenceNumber);
                } else if (SystemClock.uptimeMillis()
                        < (mLastBindTime+TIME_TO_RECONNECT)) {
                    // In this case we have connected to the service, but
                    // don't yet have its interface.  If it hasn't been too
                    // long since we did the connection, we'll return to
                    // the client and wait to get the service interface so
                    // we can report back.  If it has been too long, we want
                    // to fall through so we can try a disconnect/reconnect
                    // to see if we can get back in touch with the service.
                    return new InputBindResult(
                            InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
                            null, null, mCurId, mCurSeq,
                            mCurUserActionNotificationSequenceNumber);
                } else {
                    EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
                            mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
                }
            }
        }

        return startInputInnerLocked();
    }

attachNewInputLocked

    @GuardedBy("mMethodMap")
    @NonNull
    InputBindResult attachNewInputLocked(
            /* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
        //Have we called mCurMethod.bindInput()?,此处,我们已经调用过一次,因此不会再执行
        if (!mBoundToMethod) {
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
            mBoundToMethod = true;
        }

        //StartInputInfo对象的key值
        final Binder startInputToken = new Binder();
        //维护一个状态快照,避免随后调用MSG_START_INPUT,避免接下来的状态发生变化,影响
        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;
        //去回调IMS的startInput->doStartInput
        executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
                MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
                startInputToken, session, mCurInputContext, mCurAttribute));
        //false,改过程不会显示输入法
        if (mShowRequested) {
            if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
            showCurrentInputLocked(getAppShowFlags(), null);
        }
        //成功返回InputBindResult结果
        return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                session.session, (session.channel != null ? session.channel.dup() : null),
                mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
    }

IMS.InputMethodImpl.startInput

        @MainThread
        @Override
        public void startInput(InputConnection ic, EditorInfo attribute) {
            if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
            doStartInput(ic, attribute, false);
        }

IMS.InputMethodImpl.doStartInput

    void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
        if (!restarting) {
            doFinishInput();
        }
        mInputStarted = true;//代表该IMS的startInput已经执行
        mStartedInputConnection = ic;//应用客户端connenction
        mInputEditorInfo = attribute;//应用顾客端EditorInfo
        initialize();
        if (DEBUG) Log.v(TAG, "CALL: onStartInput");
        onStartInput(attribute, restarting);//子类实现
        if (mWindowVisible) {//当前IMS窗口不可见
            if (mShowInputRequested) {
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                mInputViewStarted = true;
                onStartInputView(mInputEditorInfo, restarting);
                startExtractingText(true);
            } else if (mCandidatesVisibility == View.VISIBLE) {
                if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
                mCandidatesViewStarted = true;
                onStartCandidatesView(mInputEditorInfo, restarting);
            }
        }
    }

处理返回的结果

 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;//IMMS的输入法启动计数
                    mCurMethod = res.method;//用来与IMS通信的对象
                    mCurId = res.id;//当前输入法的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 {
                        //与IMS通信
                        mCurMethod.displayCompletions(mCompletions);
                    } catch (RemoteException e) {
                    }
                }
            } catch (RemoteException e) {
                Log.w(TAG, "IME died: " + mCurId, e);
            }
        }

        return true;

showSoftInput流程

TextView.java

            if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
                // Show the IME, except when selecting in read-only text.
                final InputMethodManager imm = InputMethodManager.peekInstance();
                viewClicked(imm);
                if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
                    imm.showSoftInput(this, 0);
                }

                // The above condition ensures that the mEditor is not null
                mEditor.onTouchUpEvent(event);

                handled = true;
            }

viewClicked执行完毕后,执行showSoftInput方法,去弹出输入法

showSoftInput

    public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
        checkFocus();
        synchronized (mH) {
            if (mServedView != view && (mServedView == null
                    || !mServedView.checkInputConnectionProxy(view))) {
                return false;
            }

            try {
                return mService.showSoftInput(mClient, flags, resultReceiver);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

IMMS.showSoftInput

    @Override
    public boolean showSoftInput(IInputMethodClient client, int flags,
            ResultReceiver resultReceiver) {
        if (!calledFromValidUser()) {
            return false;
        }
        int uid = Binder.getCallingUid();
        long ident = Binder.clearCallingIdentity();
        try {
            synchronized (mMethodMap) {
                //当前客户端进程不为null
                if (mCurClient == null || client == null
                        || mCurClient.client.asBinder() != client.asBinder()) {
                    try {
                        // We need to check if this is the current client with
                        // focus in the window manager, to allow this call to
                        // be made before input is started in it.
                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
                            Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
                            return false;
                        }
                    } catch (RemoteException e) {
                        return false;
                    }
                }

                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
                 
                return showCurrentInputLocked(flags, resultReceiver);
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

showCurrentInputLocked


    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
        mShowRequested = true;
        if (mAccessibilityRequestingNoSoftKeyboard) {
            return false;
        }
        //flags = 0;

        if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
            mShowExplicitlyRequested = true;
            mShowForced = true;
        } else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
            //Set if we were explicitly told to show the input method
            mShowExplicitlyRequested = true;
        }

        if (!mSystemReady) {
            return false;
        }

        boolean res = false;
        if (mCurMethod != null) {
            if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
            //调用IMS$InputMethodImpl$showSoftInput方法
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
                    resultReceiver));
            mInputShown = true;
            //mHaveConnection:Set to true if our ServiceConnection is currently actively bound to a service (whether or not we have gotten its IBinder back yet).
            //mVisibleBound = false
            if (mHaveConnection && !mVisibleBound) {
                bindCurrentInputMethodServiceLocked(
                        mCurIntent, mVisibleConnection, IME_VISIBLE_BIND_FLAGS);
                mVisibleBound = true;
            }
            res = true;
        } else if (mHaveConnection && SystemClock.uptimeMillis()
                >= (mLastBindTime+TIME_TO_RECONNECT)) {
            // The client has asked to have the input method shown, but
            // we have been sitting here too long with a connection to the
            // service and no interface received, so let's disconnect/connect
            // to try to prod things along.
            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
                    SystemClock.uptimeMillis()-mLastBindTime,1);
            Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
            mContext.unbindService(this);
            bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS);
        } else {
            if (DEBUG) {
                Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
                        + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
            }
        }

        return res;//return true
    }
        if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
            mShowExplicitlyRequested = true;
            mShowForced = true;
        } else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
            //Set if we were explicitly told to show the input method
            mShowExplicitlyRequested = true;
        }

这段代码,对传递到IMS的flag影响

  private int getImeShowFlags() {
        int flags = 0;
        if (mShowForced) {
            flags |= InputMethod.SHOW_FORCED
                    | InputMethod.SHOW_EXPLICIT;
        } else if (mShowExplicitlyRequested) {
            flags |= InputMethod.SHOW_EXPLICIT;
        }
        return flags;
    }

实验证明,当getImeShowFlags返回为0时,会导致IMS不能正常调用showWindow方法;
也就是说mShowExplicitlyRequested和mShowExplicitlyRequested必须有其一为true;
默认情况下,TextView传递的flags为0,满足(flags&InputMethodManager.SHOW_IMPLICIT) == 0,使得mShowExplicitlyRequested = true;
保证了传递到IMS的getImeShowFlags 为1(InputMethod.SHOW_EXPLICIT)

另外感受下源码的混乱

InputMethod.SHOW_EXPLICIT = 0x00001
InputMethod.SHOW_FORCED = 0x00002

InputMethodManager.SHOW_IMPLICIT = 0x0001
InputMethodManager.SHOW_FORCED = 0x0002;

InputMethod的供IMS使用,InputMethodManager供应用客户端使用。
感觉InputMethodManager少了一个定义:InputMethodManager.SHOW_EXPLICIT = 0x00000;以为TextView调用showSoftInput的时候
传递的就是0,而且为0的情况下,正好满足(flags&InputMethodManager.SHOW_IMPLICIT) == 0,是的mShowExplicitlyRequested = true;

IMS.InputMethodImpl.showSoftInput

@MainThread
        @Override
        public void showSoftInput(int flags, ResultReceiver resultReceiver) {
            if (DEBUG) Log.v(TAG, "showSoftInput()");
            //输入法没弹出来之前,该值为false
            boolean wasVis = isInputViewShown();
            if (dispatchOnShowInputRequested(flags, false)) {
                try {
                    showWindow(true);
                } catch (BadTokenException e) {
                    // We have ignored BadTokenException here since Jelly Bean MR-2 (API Level 18).
                    // We could ignore BadTokenException in InputMethodService#showWindow() instead,
                    // but it may break assumptions for those who override #showWindow() that we can
                    // detect errors in #showWindow() by checking BadTokenException.
                    // TODO: Investigate its feasibility.  Update JavaDoc of #showWindow() of
                    // whether it's OK to override #showWindow() or not.
                }
            }
            clearInsetOfPreviousIme();
            // If user uses hard keyboard, IME button should always be shown.
            mImm.setImeWindowStatus(mToken, mStartInputToken,
                    mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
            if (resultReceiver != null) {
                resultReceiver.send(wasVis != isInputViewShown()
                        ? InputMethodManager.RESULT_SHOWN
                        : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
                                : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
            }
        }

dispatchOnShowInputRequested

    private boolean  dispatchOnShowInputRequested(int flags, boolean configChange) {
        //判断是否显示输入法;正常情况返回true,去显示输入法
        final boolean result = onShowInputRequested(flags, configChange);
        if (result) {
            //设置
            mShowInputFlags = flags;//默认为1,也就是InputMethod.SHOW_EXPLICIT = 0x00001
        } else {
            mShowInputFlags = 0;
        }
        return result;
    }

onShowInputRequested
判断是否应该显示输入法窗口

 public boolean onShowInputRequested(int flags, boolean configChange) {
         //主要判断硬键盘相关逻辑,很多输入法,会复写该方法;直接返回true
        if (!onEvaluateInputViewShown()) {
            return false;
        }
        //TextView中flag的值默认都是,也就是InputMethodManager.SHOW_EXPLICIT = 0x00000;因此,不会执行里面的相关代码
        if ((flags&InputMethod.SHOW_EXPLICIT) == 0) {
            //如果用户不是显式的请求显示输入法,并且应用配置没有发生变化,此时请求进入显式全屏输入法窗口,会返回false
            //返回false,表示不会对现有的输入法显示与否进行改变
            if (!configChange && onEvaluateFullscreenMode()) {
                // Don't show if this is not explicitly requested by the user and
                // the input method is fullscreen.  That would be too disruptive.
                // However, we skip this change for a config change, since if
                // the IME is already shown we do want to go into fullscreen
                // mode at this point.
                return false;
            }
            //shouldShowImeWithHardKeyboard是否支持软键盘和硬键盘共存,默认返回false
            //configure.keyboard 默认为Configuration.KEYBOARD_NOKEYS,也就是:设备没有用于文本输入的硬按键。
            if (!mSettingsObserver.shouldShowImeWithHardKeyboard() &&
                    getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS) {
                // And if the device has a hard keyboard, even if it is
                // currently hidden, don't show the input method implicitly.
                // These kinds of devices don't need it that much.
                return false;
            }
        }
        return true;//正常情况返回true
    }

flags:有三个值,0,SHOW_EXPLICIT,SHOW_FORCED
默认为0,设置为SHOW_FORCED,IMMS会强制改成 SHOW_EXPLICIT|SHOW_FORCED

    private int getImeShowFlags() {
        int flags = 0;
        if (mShowForced) {
            flags |= InputMethod.SHOW_FORCED
                    | InputMethod.SHOW_EXPLICIT;
        } else if (mShowExplicitlyRequested) {
            flags |= InputMethod.SHOW_EXPLICIT;
        }
        return flags;
    }

另外,改方法只能返回flags |= InputMethod.SHOW_FORCED| InputMethod.SHOW_EXPLICIT;或者flags |= InputMethod.SHOW_EXPLICIT,直接返回0,会导致输入法不显示

onEvaluateInputViewShown

该方法,没有物理键盘的时候返回true

    @CallSuper
    public boolean onEvaluateInputViewShown() {
        //不为null
        if (mSettingsObserver == null) {
            Log.w(TAG, "onEvaluateInputViewShown: mSettingsObserver must not be null here.");
            return false;
        }
        //是否支持同时显示软键盘和硬键盘
        if (mSettingsObserver.shouldShowImeWithHardKeyboard()) {
            return true;
        }
        Configuration config = getResources().getConfiguration();
        //设备没有硬键盘;或者硬键盘被设置为隐藏
        //HARDKEYBOARDHIDDEN_YES:value corresponding to the physical keyboard being hidden
        return config.keyboard == Configuration.KEYBOARD_NOKEYS
                || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES;
    }
  • config:
    {0theme1.0 ?mcc?mnc [zh_CN] ldltr sw360dp w360dp h683dp 320dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=Rect(0, 58 - 720, 1424) mWindowingMode=fullscreen mActivityType=undefined} s.8}

  • config.keyboard:
    nokeys:设备没有用于文本输入的硬按键。
    qwerty:设备具有标准硬键盘(无论是否对用户可见)。
    12key:设备具有 12 键硬键盘(无论是否对用户可见)

该方法主要流程:
检查当前将要弹出的client和上一步绑定的mCurClient是否一致,如果不一致,返回false
调用上一次绑定的输入法的方法,弹出客户端输入发的布局

IMS.showWindow

    public void showWindow(boolean showInput) {
        if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput
                + " mShowInputRequested=" + mShowInputRequested
                + " mWindowAdded=" + mWindowAdded
                + " mWindowCreated=" + mWindowCreated
                + " mWindowVisible=" + mWindowVisible
                + " mInputStarted=" + mInputStarted
                + " mShowInputFlags=" + mShowInputFlags);
        //避免重复进入
        if (mInShowWindow) {
            Log.w(TAG, "Re-entrance in to showWindow");
            return;
        }
        
        try {
            mWindowWasVisible = mWindowVisible;//当前窗口是否可见,false
            mInShowWindow = true;//标志为,避免重复显示窗口
            showWindowInner(showInput);
        } catch (BadTokenException e) {
            // BadTokenException is a normal consequence in certain situations, e.g., swapping IMEs
            // while there is a DO_SHOW_SOFT_INPUT message in the IIMethodWrapper queue.
            if (DEBUG) Log.v(TAG, "BadTokenException: IME is done.");
            mWindowVisible = false;
            mWindowAdded = false;
            // Rethrow the exception to preserve the existing behavior.  Some IMEs may have directly
            // called this method and relied on this exception for some clean-up tasks.
            // TODO: Give developers a clear guideline of whether it's OK to call this method or
            // InputMethodService#requestShowSelf(int) should always be used instead.
            throw e;
        } finally {
            // TODO: Is it OK to set true when we get BadTokenException?
            mWindowWasVisible = true;
            mInShowWindow = false;
        }
    }

showWindowInner

    void showWindowInner(boolean showInput) {
        boolean doShowInput = false;
        //上一个输入法窗口的状态,不可见则为0
        final int previousImeWindowStatus =
                (mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0);
        mWindowVisible = true;
        //mShowInputRequested两种情况为false,oncreate和hideSoftInput
        //ViewClick流程中,IMMS调用到IMS的startInput方法,置mInputStarted = true
        if (!mShowInputRequested && mInputStarted && showInput) {
            doShowInput = true;
            mShowInputRequested = true;//只要这一个地方可以置为true
        }

        if (DEBUG) Log.v(TAG, "showWindow: updating UI");
        initialize();//调用初始化,startinput阶段已经调用一次,将mInitialized = true,该方法中会进行判断,不会重复执行
        updateFullscreenMode();//根据当前是否全屏显示输入法窗口,更新布局的LayoutParams,mLastShowInputRequested等参数
        //更新mIsInputViewShown,mIsInputViewShown在oncreate为false,输入法显示后,mIsInputViewShown为true
        //调用声明周期函数:onCreateInputView
        updateInputViewShown();
        
        //mWindowAdded,只有在showWindow过程中,发生BadTokenException是,才会置位false
        //mWindowCreated 初次oncreate-->initView置位false
        if (!mWindowAdded || !mWindowCreated) {//本输入法,启动过一次以后,该端代码将不再执行
            mWindowAdded = true;
            mWindowCreated = true;
            initialize();
            if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView");
            View v = onCreateCandidatesView();//创建候选布局,默认为null
            if (DEBUG) Log.v(TAG, "showWindow: candidates=" + v);
            if (v != null) {
                setCandidatesView(v);
            }
        }
        if (mShowInputRequested) {//楼上已经置位true
            if (!mInputViewStarted) {
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                mInputViewStarted = true;
                onStartInputView(mInputEditorInfo, false);//获取EditText的输入类型相关信息,IMS
            }
        } else if (!mCandidatesViewStarted) {
            if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
            mCandidatesViewStarted = true;
            onStartCandidatesView(mInputEditorInfo, false);
        }
        
        if (doShowInput) {
            startExtractingText(false);
        }

        final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown());
        if (previousImeWindowStatus != nextImeWindowStatus) {
            mImm.setImeWindowStatus(mToken, mStartInputToken, nextImeWindowStatus,
                    mBackDisposition);
        }
        if ((previousImeWindowStatus & IME_ACTIVE) == 0) {
            if (DEBUG) Log.v(TAG, "showWindow: showing!");
            onWindowShown();
            mWindow.show();//真正显示窗口
            // Put here rather than in onWindowShown() in case people forget to call
            // super.onWindowShown().
            mShouldClearInsetOfPreviousIme = false;
        }
    }

onCreateInputView获取输入法的主布局
onCreateCandidatesView:获取输入的候选词布局
onStartInputView:传递Editext相关信息到IMS的具体实现

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