本篇文章聚焦于 安卓输入法面板的弹出和隐藏,除此之外,还包含了 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