安卓输入法源码2 -- 键盘弹出与隐藏

本篇文章聚焦于 安卓输入法面板的弹出和隐藏,除此之外,还包含了 InputConnection 的传递,你可以在阅读的过程中仔细留意,同时在我的 安卓输入法源码3 -- InputConnection 也做了详细的解析 -- 非常建议阅读。

Tips:1、建议在阅读的过程中,配置安卓15源码调试,事半功倍。

          2、代码中注释的地方,一般是调用下一个代码,需要多加留意。

          3、在阅读的过程中,使用目录进行跳转,可能有助于你的阅读。

一、输入法的弹出

我们日常使用手机的时候,输入法是这样弹出来的。我们点击EditText,EditText获得焦点,然后软键盘弹出。 EditText继承自TextView,当点击事件到达时,就会到达onTouchEvent(),我们就从这里开始学习。

1.1 TextView.onTouchEvent

请注意,onTouchEvent需要兵分两路去看:
序号1.x 追踪viewClicked -- 确认焦点和绑定输入法。
序号2.x 追踪showSoftInput -- 弹出输入法。

//frameworks/core/java/android/widget/TextView.java
public boolean onTouchEvent(MotionEvent event) {
    if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
        final InputMethodManager imm = getInputMethodManager();
        //onTouchEvent有两个关键函数 -- ViewClicked 和 showSoftInput,我们并分两路去追踪
        //用 序号1.x 追踪viewClicked -- 确认焦点和绑定输入法。
        viewClicked(imm);
        if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null
                && !showAutofillDialog()) {
            //用 序号2.x 追踪showSoftInput -- 弹出输入法
            imm.showSoftInput(this, 0);
        }
     }
    
     
//frameworks/core/java/android/widget/TextView.java     
protected void viewClicked(InputMethodManager imm) {
    if (imm != null) {
        imm.viewClicked(this);
    }
}

1.2 IMM.viewClicked

 //frameworks/core/java/android/view/inputmethod/InputMethodManager.java
 public void viewClicked(View view) {
    // Re-dispatch if there is a context mismatch.
    final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
    if (fallbackImm != null) { //自测fallbackImm == null表示一致,这里应该是为了防止View和imm不一致
        fallbackImm.viewClicked(view);
        return;
    }

    final View servedView;
    final View nextServedView;
    synchronized (mH) {
        servedView = getServedViewLocked();
        nextServedView = getNextServedViewLocked();
    }
    final boolean focusChanged = servedView != nextServedView;
    //继续
    checkFocus();
    synchronized (mH) {
        if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
                || !isImeSessionAvailableLocked()) {
            return;
        }
        if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);
        mCurBindState.mImeSession.viewClicked(focusChanged);
    }
}

1.3 IMM.checkFocus

//frameworks/core/java/android/view/inputmethod/InputMethodManager.java
public void checkFocus() {
    synchronized (mH) {
        if (mCurRootView == null) {
            return;
        }
        if (!checkFocusInternalLocked(false /* forceNewFocus */, mCurRootView)) {
            return;
        }
    }
    //继续
    startInputOnWindowFocusGainInternal(StartInputReason.CHECK_FOCUS,
            null /* focusedView */,
            0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */);
}

private boolean startInputOnWindowFocusGainInternal(@StartInputReason int startInputReason,
        View focusedView, @StartInputFlags int startInputFlags,
        @SoftInputModeFlags int softInputMode, int windowFlags) {
    synchronized (mH) {
        mCurrentEditorInfo = null;
        mCompletions = null;
        mServedConnecting = true;
    }
    //IMM的startInputInner,还有印象吗? IMS初始化的时候就是从这里开始的
    return startInputInner(startInputReason,
            focusedView != null ? focusedView.getWindowToken() : null, startInputFlags,
            softInputMode, windowFlags);
}

1.4 IMM.startInputInner

//frameworks/core/java/android/view/inputmethod/InputMethodManager.java
private boolean startInputInner(@StartInputReason int startInputReason,
        @Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
        @SoftInputModeFlags int softInputMode, int windowFlags) {
        ...
        if (Flags.useZeroJankProxy()) {
            // async result delivered via MSG_START_INPUT_RESULT.
            //安卓15 需要用这个 异步 的方法,但是FrameWork层没有源码,按照下面的 同步 代码进行跟踪
            startInputSeq = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocusAsync(
                    startInputReason, mClient, windowGainingFocus, startInputFlags,
                    softInputMode, windowFlags, editorInfo, servedInputConnection,
                    servedInputConnection == null ? null
                            : servedInputConnection.asIRemoteAccessibilityInputConnection(),
                    view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
                    mImeDispatcher, ASYNC_SHOW_HIDE_METHOD_ENABLED);
        } else {
        //之前说过这个Invoker是IMMS的封装,我们直接去IMMS看startInputOrWindowGainedFocus
         res = InputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
            startInputReason, mClient, windowGainingFocus, startInputFlags,
            softInputMode, windowFlags, editorInfo, servedInputConnection,
            servedInputConnection == null ? null
                    : servedInputConnection.asIRemoteAccessibilityInputConnection(),
            view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
            mImeDispatcher);
        }
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);

1.5 IMMS.startInputOrWindowGainedFocus

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
public InputBindResult startInputOrWindowGainedFocus(
        @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
        @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,...)
        {
        ...
        //继续
        result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
        client, windowToken, startInputFlags, softInputMode, windowFlags,
        editorInfo, inputConnection, remoteAccessibilityInputConnection,
        unverifiedTargetSdkVersion, bindingController, imeDispatcher, cs);
        
        } 

1.6 IMMS.startInputOrWindowGainedFocusInternalLocked

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
private InputBindResult startInputOrWindowGainedFocusInternalLocked(
        @StartInputReason int startInputReason, IInputMethodClient client,
        @NonNull IBinder windowToken, @StartInputFlags int startInputFlags,..){
        ...
        if (sameWindowFocused && isTextEditor) {
            if (DEBUG) {
                Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
                        + " editorInfo=" + editorInfo + ", token = " + windowToken
                        + ", startInputReason="
                        + InputMethodDebug.startInputReasonToString(startInputReason));
            }
            if (editorInfo != null) {
                //继续
                return startInputUncheckedLocked(cs, inputContext,
                        remoteAccessibilityInputConnection, editorInfo, startInputFlags,
                        startInputReason, unverifiedTargetSdkVersion, imeDispatcher,
                        bindingController);
            }
        }

1.7 IMMS.startInputUncheckedLocked

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
private InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
        IRemoteInputConnection inputConnection,
        @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,..){
        ...
        
        final String curId = bindingController.getCurId();
        final int displayIdToShowIme = bindingController.getDisplayIdToShowIme();
        if (curId != null && curId.equals(bindingController.getSelectedMethodId())
                && displayIdToShowIme == getCurTokenDisplayIdLocked()) {
            if (cs.mCurSession != null) {
               //这里的这些if 都是为了判断是否已经存在可以复用的session
                cs.mSessionRequestedForAccessibility = false;
                requestClientSessionForAccessibilityLocked(cs);
                attachNewAccessibilityLocked(startInputReason,
                        (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
                //继续
                return attachNewInputLocked(startInputReason,
                        (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
            }
    
            InputBindResult bindResult = tryReuseConnectionLocked(bindingController, cs);
            if (bindResult != null) {
                return bindResult;
            }
        }
        //这里熟悉不?  如果 之前没有连接过输入法 或 切换输入法 这里就会去bindService(IMS).
        bindingController.unbindCurrentMethod();
        return bindingController.bindCurrentMethod();
    }
            

1.8 IMMS.attachNewInputLocked

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
    final int userId = mCurrentUserId;
    final var bindingController = getInputMethodBindingController(userId);
    if (!mBoundToMethod) { 
        //将 当前的应用的client 绑定到  讯飞输入法(IMS) 上
        //之前bindController已经和 IMS 绑定上了
        bindingController.getCurMethod().bindInput(mCurClient.mBinding);
        mBoundToMethod = true;
    }
    ...
    ...
    //mCurClient 在 startInputUncheckedLocked 被绑定(赋值) 为IMM(应用)的Client
    final SessionState session = mCurClient.mCurSession;
    setEnabledSessionLocked(session);
    //mCurInputConnection 在 startInputUncheckedLocked 绑定为IMM的IRemoteInputConnection 的AIDL的具体实现  
    //之后这个startInput 就会去 IMS 类中,将上面的client和ic传递过去
    session.mMethod.startInput(startInputToken, mCurInputConnection, mCurEditorInfo, restarting,
            navButtonFlags, mCurImeDispatcher);

1.9 IInputMethodInvoker.startInput

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
//我们先看session这个类SessionState
static class SessionState {
    final ClientState mClient;
    //这个mMethod是一个IInputMethodInvoker,我们要去找IInputMethodInvoker.start
    final IInputMethodInvoker mMethod;
    IInputMethodSession mSession;
    InputChannel mChannel;
}


//frameworks/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
void startInput(IBinder startInputToken, IRemoteInputConnection remoteInputConnection,
        EditorInfo editorInfo, boolean restarting,
        @InputMethodNavButtonFlags int navButtonFlags,
        @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
    //这个params将要用到的对象存入,我认为最重要的是这个remoteInputConnection,后面IMS可以直接用这个和应用的View通信
    final IInputMethod.StartInputParams params = new IInputMethod.StartInputParams();
    params.startInputToken = startInputToken;
    params.remoteInputConnection = remoteInputConnection;
    params.editorInfo = editorInfo;
    params.restarting = restarting;
    params.navigationBarFlags = navButtonFlags;
    params.imeDispatcher = imeDispatcher;
    try {
    //private final IInputMethod mTarget;  mTarget是一个 AIDL 接口的远程代理(Binder 代理),我们看看哪里实现了他
        mTarget.startInput(params);
    } catch (RemoteException e) {
        logRemoteException(e);
    }
}

1.10 IInputMethodWrapper

//frameworks/core/java/android/inputmethodservice/IInputMethodWrapper.java
//IInputMethodWrapper 继承 IInputMethod.Stub,是 IInputMethod 接口的具体实现。
class IInputMethodWrapper extends IInputMethod.Stub{
    ....
    public void startInput(@NonNull IInputMethod.StartInputParams params) {
        if (mCancellationGroup == null) {
            Log.e(TAG, "startInput must be called after bindInput.");
            mCancellationGroup = new CancellationGroup();
        }
    
        params.editorInfo.makeCompatible(mTargetSdkVersion);
    
        final InputConnection ic = params.remoteInputConnection == null ? null
                : new RemoteInputConnection(mTarget, params.remoteInputConnection,
                        mCancellationGroup);
         
        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT, ic, params));
    }
    
    

//frameworks/core/java/com/android/internal/os/HandlerCaller.java
//我们看一下mCaller的handleMessage
public void handleMessage(Message msg) {
    //这个是需要override的,我们下一步看具体的实现类    
    mCallback.executeMessage(msg);
}


//frameworks/core/java/android/inputmethodservice/IInputMethodWrapper.java
//具体的实现类如下
@MainThread
@Override
public void executeMessage(Message msg) {
    final InputMethod inputMethod = mInputMethod.get();
    final InputMethodServiceInternal target = mTarget.get();
    switch (msg.what) {
        ...
        case DO_START_INPUT: {
            final SomeArgs args = (SomeArgs) msg.obj;
            if (isValid(inputMethod, target, "DO_START_INPUT")) {
                //取出了ic
                final InputConnection inputConnection = (InputConnection) args.arg1;
                final IInputMethod.StartInputParams params =
                        (IInputMethod.StartInputParams) args.arg2;
                inputMethod.dispatchStartInput(inputConnection, params);
            }
            args.recycle();
            return;
        }
        
//frameworks/core/java/android/view/inputmethod/InputMethod.java        
default void dispatchStartInput(@Nullable InputConnection inputConnection,
        @NonNull IInputMethod.StartInputParams params) {
    if (params.restarting) {
        restartInput(inputConnection, params.editorInfo);
    } else {
        //这个函数是需要override的。IMS继承之后,进行了实现
        startInput(inputConnection, params.editorInfo);
    }
}

1.11 IMS.startInput

//frameworks/core/java/android/inputmethodservice/InputMethodService.java
@Override
public void startInput(InputConnection ic, EditorInfo editorInfo) {
    if (DEBUG) Log.v(TAG, "startInput(): editor=" + editorInfo);
    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.startInput");
    doStartInput(ic, editorInfo, false);
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}

1.12 IMS.doStartInput

这里将 mStartedInputConnection进行了赋值,之后软键盘打字的时候,就会通过这个mStartedInputConnection去调用commitText / sendKeyEvent 来传递到应用侧。具体的讲解在   安卓输入法源码3 -- InputConnection

//frameworks/core/java/android/inputmethodservice/InputMethodService.java
void doStartInput(InputConnection ic, EditorInfo editorInfo, boolean restarting) {
    if (!restarting && mInputStarted) {
        doFinishInput();
    }
    ImeTracing.getInstance().triggerServiceDump("InputMethodService#doStartInput", mDumper,
            null /* icProto */);
    mInputStarted = true;
    //这里将ic 和 editorInfo 都绑定了,和输入法弹出没关系,但是和打字有关系
    mStartedInputConnection = ic;
    mInputEditorInfo = editorInfo;
    initialize();
    mInlineSuggestionSessionController.notifyOnStartInput(
            editorInfo == null ? null : editorInfo.packageName,
            editorInfo == null ? null : editorInfo.autofillId);
    if (DEBUG) Log.v(TAG, "CALL: onStartInput");
    onStartInput(editorInfo, restarting);
    if (mDecorViewVisible) {
        if (mShowInputRequested) {
            if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
            mInputViewStarted = true;
            mInlineSuggestionSessionController.notifyOnStartInputView();
            //点进去是空的,需要继承IMS的类(如:讯飞输入法)去实现,作用是设置输入法的UI
            onStartInputView(mInputEditorInfo, restarting);
            startExtractingText(true);
        } else if (mCandidatesVisibility == View.VISIBLE) {
            if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
            mCandidatesViewStarted = true;
            onStartCandidatesView(mInputEditorInfo, restarting);
        }
    }
}
 

现在焦点确认和输入法绑定已经完成了,下面是输入法弹出的具体流程。

2.0 TextView.onTouchEvent

//frameworks/core/java/android/widget/TextView.java
public boolean onTouchEvent(MotionEvent event) {
    if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
        final InputMethodManager imm = getInputMethodManager();
        //这个我们在上面已经追踪过了
        viewClicked(imm);
        if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null
                && !showAutofillDialog()) {
            //用 序号2.x 追踪showSoftInput -- 弹出输入法
            imm.showSoftInput(this, 0);
        }
     }

2.1 IMM.showSoftInput

//frameworks/core/java/android/view/inputmethod/InputMethodManager.java
public boolean showSoftInput(View view, @ShowFlags int flags) {
    // 判断 view 的 Context 是否与当前 IMM 绑定的 Context 匹配。
    final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
    if (fallbackImm != null) {
        return fallbackImm.showSoftInput(view, flags);
    }
    //有很多同名的函数,需要一个个跳转过去
    return showSoftInput(view, flags, null);
}

public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) {
    return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT);
}

private boolean showSoftInput(View view, @ShowFlags int flags,
        @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
     final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW,
            ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view));
    return showSoftInput(view, statsToken, flags, resultReceiver, reason);
}

private boolean showSoftInput(View view, @NonNull ImeTracker.Token statsToken,
        @ShowFlags int flags, @Nullable ResultReceiver resultReceiver,
        @SoftInputShowHideReason int reason) {
        ...
        mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
        Log.d(TAG, "showSoftInput() view=" + view + " flags=" + flags + " reason="
            + InputMethodDebug.softInputDisplayReasonToString(reason));
        //又是GlobalInvoker,上一篇文章已经讲过了,我们直接跳到IMMS.showSoftInput
        return IInputMethodManagerGlobalInvoker.showSoftInput(
            mClient,
            view.getWindowToken(),
            statsToken,
            flags,
            mCurRootView.getLastClickToolType(),
            resultReceiver,
            reason,
            ASYNC_SHOW_HIDE_METHOD_ENABLED);
}

2.2 IMMS.showSoftInput

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodMap.java
public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
        @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
        int lastClickToolType, ResultReceiver resultReceiver,
        @SoftInputShowHideReason int reason, boolean async) {
        ...
        } else {
        return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType,
            resultReceiver, reason);
}
        
        

2.3 IMMS.showCurrentInputLocked

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
boolean showCurrentInputLocked(IBinder windowToken,
        @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,...){
        ...
        if (Flags.useHandwritingListenerForTooltype()) {
            maybeReportToolType();
        } else if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
            onUpdateEditorToolType(lastClickToolType);
        }
        //private final DefaultImeVisibilityApplier mVisibilityApplier;
        mVisibilityApplier.performShowIme(windowToken, statsToken,
                mVisibilityStateComputer.getShowFlagsForInputMethodServiceOnly(),
                resultReceiver, reason);

2.4 DefaultImeVisibilityApplier.performShowIme

//frameworks/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
public void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
        @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
        @SoftInputShowHideReason int reason) {
    //Invoker是IMS的封装类   
    final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
    if (curMethod != null) {
        if (DEBUG) {
            Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
                    + ", " + showFlags + ", " + resultReceiver + ") for reason: "
                    + InputMethodDebug.softInputDisplayReasonToString(reason));
        }
    ...
    
    if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
        mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
        statsToken);
          

2.5 IMS.showSoftInput

//frameworks/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
//先走到Invoker.showSoftInput
boolean showSoftInput(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
        @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
    try {
        //private final IInputMethod mTarget;  这是定义的地方 
        mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver);
    } catch (RemoteException e) {
        logRemoteException(e);
        return false;
    }
    return true;
}

//frameworks/core/java/android/inputmethodservice/IInputMethodWrapper.java
//根据定义继续寻找到 IInputMethodWrapper
class IInputMethodWrapper extends IInputMethod.Stub{
    ...
    public void showSoftInput(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
        @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
        ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
        mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
                flags, showInputToken, resultReceiver, statsToken));
}

//frameworks/core/java/android/inputmethodservice/IInputMethodWrapper.java
@MainThread
@Override
public void executeMessage(Message msg) {
    ...
    case DO_SHOW_SOFT_INPUT: {
    final SomeArgs args = (SomeArgs) msg.obj;
    final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
    if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
        ImeTracker.forLogging().onProgress(
                statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
        //这个函数继续调用ims
        inputMethod.showSoftInputWithToken(
                msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1, statsToken);
    }

//frameworks/core/java/android/inputmethodservice/InputMethodService.java
public void showSoftInputWithToken(@InputMethod.ShowFlags int flags,
        ResultReceiver resultReceiver, IBinder showInputToken,
        @NonNull ImeTracker.Token statsToken) {
    mSystemCallingShowSoftInput = true;
    mCurShowInputToken = showInputToken;
    mCurStatsToken = statsToken;
    try {
        showSoftInput(flags, resultReceiver);
    } finally {
        mCurShowInputToken = null;
        mSystemCallingShowSoftInput = false;
    }
}

//frameworks/core/java/android/inputmethodservice/InputMethodService.java
public void showSoftInput(@InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
    ...
    if (dispatchOnShowInputRequested(flags, false)) {
    ImeTracker.forLogging().onProgress(statsToken,
            ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
    mCurStatsToken = statsToken;
    showWindow(true /* showInput */);
}

2.6 IMS.showWindow

//frameworks/core/java/android/inputmethodservice/InputMethodService.java
public void showWindow(boolean showInput) {
    ...

    mNavigationBarController.onWindowShown();
    onWindowShown();
    mWindowVisible = true;

    // request draw for the IME surface.
    if (DEBUG) Log.v(TAG, "showWindow: draw decorView!");
    //SoftInputWindow mWindow;  查看定义,这就是键盘的界面,继承自一个Dialog
    mWindow.show();
    mDecorViewWasVisible = true;
    applyVisibilityInInsetsConsumerIfNecessary(true /* setVisible */, statsToken);
    cancelImeSurfaceRemoval();
    mInShowWindow = false;
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    registerDefaultOnBackInvokedCallback();
}

二、输入法的隐藏

当我们点击界面空白的地方的时候,应用会收起软键盘,此时应用需要自己调用IMM.hideSoftInputFromWindow。 而不是像输入法弹出的时候,一个onTouchEvent就会帮我们把所有的事情处理好。

3.1 IMM.hideSoftInputFromWindow

//frameworks/core/java/android/view/inputmethod/InputMethodManager.java
private boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags,
        ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
        ...
        if (Flags.refactorInsetsController()) {
            servedView.getWindowInsetsController().hide(WindowInsets.Type.ime());
            return true;
        } else {
        //之前已经说过了这个GlobalInvoker了,直接跳到IMMS.hideSoftInput
            return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken,
                    statsToken, flags, resultReceiver, reason, ASYNC_SHOW_HIDE_METHOD_ENABLED);
        }

3.2 IMMS.hideSoftInput

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
        @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
        ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, boolean async) {
        ...
         return InputMethodManagerService.this.hideCurrentInputLocked(windowToken,
            statsToken, flags, resultReceiver, reason);
}

3.3 IMMS.hideCurrentInputLocked

boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
        @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver,
        @SoftInputShowHideReason int reason) {
        ...
        IInputMethodInvoker curMethod = getCurMethodLocked();
        final boolean shouldHideSoftInput = curMethod != null
        && (isInputShownLocked() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
        
        mVisibilityStateComputer.requestImeVisibility(windowToken, false);
        if (shouldHideSoftInput) {
            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
            mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason);  
        }

3.4 DefaultImeVisibilityApplier.performHideIme

//frameworks/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
public void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
        ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
    final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
    if (curMethod != null) {
         ...
        //去IMS.hideSoftInput
        if (curMethod.hideSoftInput(hideInputToken, statsToken, 0, resultReceiver)) {
            if (DEBUG_IME_VISIBILITY) {
                EventLog.writeEvent(IMF_HIDE_IME,
                        statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
                        Objects.toString(mService.mImeBindingState.mFocusedWindow),
                        InputMethodDebug.softInputDisplayReasonToString(reason),
                        InputMethodDebug.softInputModeToString(
                                mService.mImeBindingState.mFocusedWindowSoftInputMode));
            }
            mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
                    statsToken);
        }

3.5 IInputMethodInvoker.hideSoftInput

//frameworks/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
boolean hideSoftInput(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
        int flags, ResultReceiver resultReceiver) {
    try {
        //private final IInputMethod mTarget; IInputMethodWrapper实现了IInputMethod.aidl
        mTarget.hideSoftInput(hideInputToken, statsToken, flags, resultReceiver);
    } catch (RemoteException e) {
        logRemoteException(e);
        return false;
    }
    return true;
}

3.6 IInputMethodWrapper.hideSoftInput

//frameworks/core/java/android/inputmethodservice/IInputMethodWrapper.java
public void hideSoftInput(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
        int flags, ResultReceiver resultReceiver) {
    ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
    //我们看哪里处理了这个函数
    mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT,
            flags, hideInputToken, resultReceiver, statsToken));
}

3.7 IInputMethodWrapper.executeMessage

//frameworks/core/java/android/inputmethodservice/IInputMethodWrapper.java
public void executeMessage(Message msg) {
    final InputMethod inputMethod = mInputMethod.get();
    final InputMethodServiceInternal target = mTarget.get();
    switch (msg.what) {
        ...
        case DO_HIDE_SOFT_INPUT: {
            final SomeArgs args = (SomeArgs) msg.obj;
            final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
            if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) {
                ImeTracker.forLogging().onProgress(
                        statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
                //调用IMS的hideSoftInputWithToken
                inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
                        (IBinder) args.arg1, statsToken);
            } else {
                ImeTracker.forLogging().onFailed(
                        statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
            }
            args.recycle();
            return;
        }
    

3.8 IMS.hideSoftInputWithToken

//frameworks/core/java/android/inputmethodservice/InputMethodService.java
public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver,
        IBinder hideInputToken, @NonNull ImeTracker.Token statsToken) {
    mSystemCallingHideSoftInput = true;
    mCurHideInputToken = hideInputToken;
    mCurStatsToken = statsToken;
    try {
        hideSoftInput(flags, resultReceiver);
    } finally {
        mCurHideInputToken = null;
        mSystemCallingHideSoftInput = false;
    }
}

3.9 IMS.hideSoftInput

//frameworks/core/java/android/inputmethodservice/InputMethodService.java
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
    ...
    final boolean wasVisible = isInputViewShown();

    mShowInputFlags = 0;
    mShowInputRequested = false;
    mCurStatsToken = statsToken;
    hideWindow();
    final boolean isVisible = isInputViewShown();
    final boolean visibilityChanged = isVisible != wasVisible;
    ...
    }

3.10 IMS..hideWindow

public void hideWindow() {
    final var statsToken = mCurStatsToken != null ? mCurStatsToken
            : createStatsToken(false /* show */,
                    SoftInputShowHideReason.HIDE_WINDOW_LEGACY_DIRECT,
                    ImeTracker.isFromUser(mRootView));
    mCurStatsToken = null;

    ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_HIDE_WINDOW);
    ImeTracing.getInstance().triggerServiceDump("InputMethodService#hideWindow", mDumper,
            null /* icProto */);
    setImeWindowStatus(0, mBackDisposition);
    // 该方法会通知 WMS 更新窗口可见性,从而隐藏输入法窗口,至此软键盘隐藏
    applyVisibilityInInsetsConsumerIfNecessary(false /* setVisible */, statsToken);
    mWindowVisible = false;
    finishViews(false /* finishingInput */);
    if (mDecorViewVisible) {
        if (mInputView != null) {
            mInputView.dispatchWindowVisibilityChanged(View.GONE);
        }
        mDecorViewVisible = false;
        onWindowHidden();
        mDecorViewWasVisible = false;
    }
    mLastWasInFullscreenMode = mIsFullscreen;
    updateFullscreenMode();我们日常使用手机的时候,输入法是这样弹出来的。我们点击EditText,EditText获得焦点,然后软键盘弹出。​
EditText继承自TextView,当点击事件到达时,就会到达onTouchEvent(),我们就从这里开始学习。
    unregisterDefaultOnBackInvokedCallback();
}

至此输入法的弹出和隐藏流程就介绍完毕了。其中输入法的弹出

下一章节介绍:输入法和应用 通信的AIDL接口:  安卓输入法源码3 -- InputConnection

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值