美文网首页
Android输入法显示流程

Android输入法显示流程

作者: android_coder | 来源:发表于2020-09-17 11:12 被阅读0次

    Android输入法显示方式大概分为两种:用户手动点击输入框和应用程序设置了输入法自动显示
    本文基于Android9.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
    

    2:从用户点击输入框开始

    EditText本身是TextView的子类,触摸事件的起点在TextView的onTouchEvent方法中

     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;
                }
    
    2.1:viewClicked
        protected void viewClicked(InputMethodManager imm) {
            if (imm != null) {
                imm.viewClicked(this);
            }
        }
    
    2:2:InputMethodManager::viewClicked
        public void viewClicked(View 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);
                }
            }
        }
    

    mCurMethod代表的是一个binder代理对象,对应的是
    IInputMethodSessionWrapper

    2.3:IInputMethodSessionWrapper::viewClicked
       @Override
        public void viewClicked(boolean focusChanged) {
            mCaller.executeOrSendMessage(
                    mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
        }
       case DO_VIEW_CLICKED: {
                    mInputMethodSession.viewClicked(msg.arg1 == 1);
                    return;
                }
    

    InputMethodSession对应的是一个接口,其实现类为AbstractInputMethodSessionImpl,AbstractInputMethodSessionImpl本身是一个抽象类,InputMethodSessionImpl有继承它,所以最终调用的是InputMethodSessionImpl的viewClicked

    2.4::InputMethodSessionImpl::viewClicked
      public void viewClicked(boolean focusChanged) {
                if (!isEnabled()) {
                    return;
                }
                InputMethodService.this.onViewClicked(focusChanged);
            }
    
    2.5:InputMethodService::onViewClicked
    public void onViewClicked(boolean focusChanged) {
            // Intentionally empty
        }
    
    2.6:TextView::checkFocus
        public void checkFocus() {
            if (checkFocusNoStartInput(false)) {
                startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);
            }
        }
    
    2.7:startInputInner

    这个方法主要是和输入法服务建立连接以及绑定

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

    mService表示的是Imms在客户端的代理对象

    2.8:IMMS::startInputOrWindowGainedFocus
            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;
            }
    

    windowToken这个地方不为null,代码逻辑执行的是windowToken

    2.9:windowGainedFocus
                 ClientState cs = mClients.get(client.asBinder());
                 //在窗口添加的时候会创建一个会话session,在会话创建的时候添加的
                 //   if (mService.mInputMethodManager != null) {
                //     mService.mInputMethodManager.addClient(client, inputContext,
                 //    mUid, mPid);
                //Session.java的构造方法中
                } else {
                    client.setUsingInputMethod(false);
                }
                  if (cs == null) {----->
                        throw new IllegalArgumentException("unknown client "
                                + client.asBinder());
                   }
                    try {
                        if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
                            return InputBindResult.NOT_IME_TARGET_WINDOW;
                        }
                    } catch (RemoteException e) {
                    }
                    if (!calledFromValidUser) {
                        hideCurrentInputLocked(0, null);
                        return InputBindResult.INVALID_USER;
                    }
                    if (mCurFocusedWindow == 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);
                    }
    
    2.10:startInputUncheckedLocked
            if (mCurId != null && mCurId.equals(mCurMethodId)) {
                if (cs.curSession != null) {
                    return attachNewInputLocked(startInputReason,
    (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
                }
                if (mHaveConnection) {
                    if (mCurMethod != null) {
                        requestClientSessionLocked(cs);
                        return new InputBindResult(                InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
                                null, null, mCurId, mCurSeq,
                                mCurUserActionNotificationSequenceNumber);
                    } else if (SystemClock.uptimeMillis()
                            < (mLastBindTime+TIME_TO_RECONNECT)) {
                        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();
    
    2.11:attachNewInputLocked
      final SessionState session = mCurClient.curSession;
            executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
                    MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
                    startInputToken, session, mCurInputContext, mCurAttribute));
            if (mShowRequested) {----------->此时mShowRequested为false
                showCurrentInputLocked(getAppShowFlags(), null);
            }
            return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                    session.session, (session.channel != null ? session.channel.dup() : null),
                    mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
    

    第一部分主要是应用程序app和输入法服务IMMS建立连接

    3:应用程序请求输入法显示

     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;
                }
    
    3.1: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);---------->请求输入法服务来显示showSoftInput
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
        }
    
    3:2:IMMS::showSoftInput
     try {
                synchronized (mMethodMap) {
                    if (mCurClient == null || client == null
                            || mCurClient.client.asBinder() != client.asBinder()) {
                        try {
                            if (!mIWindowManager.inputMethodClientHasFocus(client)) {
                                return false;
                            }
                        } catch (RemoteException e) {
                            return false;
                        }
                    }
                    return showCurrentInputLocked(flags, resultReceiver);
                }
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
    
    3:3:showCurrentInputLocked
      boolean res = false;
            if (mCurMethod != null) {
                executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
                        MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
                        resultReceiver));
                mInputShown = true;
                if (mHaveConnection && !mVisibleBound) {
                    bindCurrentInputMethodServiceLocked(
                            mCurIntent, mVisibleConnection, IME_VISIBLE_BIND_FLAGS);
                    mVisibleBound = true;
                }
                res = true;
            } else if (mHaveConnection && SystemClock.uptimeMillis()
                    >= (mLastBindTime+TIME_TO_RECONNECT)) {
    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 {
            }
    
    3.4::MSG_SHOW_SOFT_INPUT消息处理
            case MSG_SHOW_SOFT_INPUT:
                    args = (SomeArgs)msg.obj;
                    try {
                        if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
                                + msg.arg1 + ", " + args.arg2 + ")");
                        ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
                    } catch (RemoteException e) {
                    }
                    args.recycle();
                    return true;
    

    ((IInputMethod)args.arg1)代表的是arg2

        public Message obtainMessageIOO(int what, int arg1, Object arg2, Object arg3) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = arg2;
            args.arg2 = arg3;
            return mH.obtainMessage(what, arg1, 0, args);
        }
    

    arg2代表是IInputMethod mCurMethod;是一个binder代理对象,表示的是输入法在IMMS的代理对象,通过这个对象我们可以访问输入法进程里面的方法

    3.5:IInputMethodWrapper::showSoftInput
        public void showSoftInput(int flags, ResultReceiver resultReceiver) {
    mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
                    flags, resultReceiver));
        }
       //最终调用的是inputMethod的showSoftInput,inputMethod是InputMethod,InputMethod是一个接口,需要找到其实现类
        case DO_SHOW_SOFT_INPUT:
           inputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj);
           return;
    
    3.6:InputMethodServic.InputMethodImpl.java
     public void showSoftInput(int flags, ResultReceiver resultReceiver) {
                boolean wasVis = isInputViewShown();
                if (dispatchOnShowInputRequested(flags, false)) {
                    try {
                        showWindow(true);------>显示输入法窗口
                    } catch (BadTokenException e) {
                    }
                }
                clearInsetOfPreviousIme();
                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);
                }
            }
    

    InputMethodService是输入法服务,工作在输入法应用的进程中

    3.7:showWindow
     try {
                mWindowWasVisible = mWindowVisible;
                mInShowWindow = true;
                showWindowInner(showInput);
            } catch (BadTokenException e) {
                mWindowVisible = false;
                mWindowAdded = false;
                throw e;
            } finally {
                // TODO: Is it OK to set true when we get BadTokenException?
                mWindowWasVisible = true;
                mInShowWindow = false;
            }
    
    3.8:showWindowInner
     void showWindowInner(boolean showInput) {
            boolean doShowInput = false;
            final int previousImeWindowStatus =
                    (mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0);
            mWindowVisible = true;
            if (!mShowInputRequested && mInputStarted && showInput) {
                doShowInput = true;
                mShowInputRequested = true;
            }
            initialize();
            updateFullscreenMode();
            updateInputViewShown();
            if (!mWindowAdded || !mWindowCreated) {
                mWindowAdded = true;
                mWindowCreated = true;
                initialize();
                View v = onCreateCandidatesView();
                if (v != null) {
                    setCandidatesView(v);
                }
            }
            if (mShowInputRequested) {
                if (!mInputViewStarted) {
                    if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                    mInputViewStarted = true;
                    onStartInputView(mInputEditorInfo, false);
                }
            } 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();---->输入法窗口是一个对话框
                mShouldClearInsetOfPreviousIme = false;
            }
        }
    

    最终输入法窗口被显示出来

    相关文章

      网友评论

          本文标题:Android输入法显示流程

          本文链接:https://www.haomeiwen.com/subject/rtqqyktx.html