通常显示或者隐藏输入法有以下三个场景
当window获得焦点时会调用imm::onPostWindowFocus方法
/**
* Called by ViewAncestor when its window gets input focus.
* @hide
*/
public void onPostWindowFocus(View rootView, View focusedView,
@SoftInputModeFlags int softInputMode, boolean first, int windowFlags) {
boolean forceNewFocus = false;
// 对焦点view的控制,后续分析
synchronized (mH) {
if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
+ " softInputMode=" + InputMethodClient.softInputModeToString(softInputMode)
+ " first=" + first + " flags=#"
+ Integer.toHexString(windowFlags));
if (mRestartOnNextWindowFocus) {
if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus");
mRestartOnNextWindowFocus = false;
forceNewFocus = true;
}
focusInLocked(focusedView != null ? focusedView : rootView);
}
.......
// 该方法是对焦点view的判断,这里默认返回true
if (checkFocusNoStartInput(forceNewFocus)) {
// We need to restart input on the current focus view. This
// should be done in conjunction with telling the system service
// about the window gaining focus, to help make the transition
// smooth.
if (startInputInner(InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN,
rootView.getWindowToken(), controlFlags, softInputMode, windowFlags)) {
return;
}
}
......
}
中间会有一些焦点view的判断,后续分析,这里会进入startInputInner方法
boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
IBinder windowGainingFocus, int controlFlags, int softInputMode,
int windowFlags) {
......
// 在这里创建了焦点View的EditorInfo和InputConnection
EditorInfo tba = new EditorInfo();
tba.packageName = view.getContext().getOpPackageName();
tba.fieldId = view.getId();
InputConnection ic = view.onCreateInputConnection(tba);
if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
synchronized (mH) {
......
// InputConnection被封装成ControlledInputConnectionWrapper,然后被赋值给mServedInputConnectionWrapper对象
ControlledInputConnectionWrapper servedContext;
final int missingMethodFlags;
if (ic != null) {
......
servedContext = new ControlledInputConnectionWrapper(
icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);
} else {
servedContext = null;
missingMethodFlags = 0;
}
mServedInputConnectionWrapper = servedContext;
try {
// 调用imms方法
final InputBindResult res = mService.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
windowFlags, tba, servedContext, missingMethodFlags,
view.getContext().getApplicationInfo().targetSdkVersion);
......
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
}
return true;
}
在startInputInner中创建当前焦点View的EditorInfo和获取焦点View的InputConnection,在传递给imms前会被封装成ControlledInputConnectionWrapper,然后就从app进程进入了system进程
@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;
}
@NonNull
private InputBindResult windowGainedFocus(
/* @InputMethodClient.StartInputReason */ final int startInputReason,
IInputMethodClient client, IBinder windowToken, int controlFlags,
/* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
int windowFlags, EditorInfo attribute, IInputContext inputContext,
/* @InputConnectionInspector.missingMethods */ final int missingMethods,
int unverifiedTargetSdkVersion) {
// Needs to check the validity before clearing calling identity
final boolean calledFromValidUser = calledFromValidUser();
InputBindResult res = null;
long ident = Binder.clearCallingIdentity();
try {
.......
synchronized (mMethodMap) {
// is more room for the target window + IME.
final boolean doAutoShow =
(softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
== WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|| mRes.getConfiguration().isLayoutSizeAtLeast(
Configuration.SCREENLAYOUT_SIZE_LARGE);
final boolean isTextEditor =
(controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
// We want to start input before showing the IME, but after closing
// it. We want to do this after closing it to help the IME disappear
// more quickly (not get stuck behind it initializing itself for the
// new focused input, even if its window wants to hide the IME).
boolean didStart = false;
switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
if (!isTextEditor || !doAutoShow) {
if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
// There is no focus view, and this window will
// be behind any soft input window, so hide the
// soft input window if it is shown.
if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
}
} else if (isTextEditor && doAutoShow && (softInputMode &
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
// There is a focus view, and we are navigating forward
// into the window, so show the input window for the user.
// We only do this automatically if the window can resize
// to accommodate the IME (so what the user sees will give
// them good context without input information being obscured
// by the IME) or if running on a large screen where there
// is more room for the target window + IME.
if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext,
missingMethods, attribute, controlFlags, startInputReason);
didStart = true;
}
showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
// Do nothing.
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
if ((softInputMode &
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
hideCurrentInputLocked(0, null);
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
if (DEBUG) Slog.v(TAG, "Window asks to hide input");
hideCurrentInputLocked(0, null);
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
if ((softInputMode &
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
unverifiedTargetSdkVersion, controlFlags)) {
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext,
missingMethods, attribute, controlFlags,
startInputReason);
didStart = true;
}
showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
} else {
Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
+ " there is no focused view that also returns true from"
+ " View#onCheckIsTextEditor()");
}
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
if (DEBUG) Slog.v(TAG, "Window asks to always show input");
if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
unverifiedTargetSdkVersion, controlFlags)) {
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext, missingMethods,
attribute, controlFlags, startInputReason);
didStart = true;
}
showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
} else {
Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
+ " there is no focused view that also returns true from"
+ " View#onCheckIsTextEditor()");
}
break;
}
if (!didStart) {
if (attribute != null) {
if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
|| (controlFlags
& InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) {
res = startInputUncheckedLocked(cs, inputContext, missingMethods,
attribute,
controlFlags, startInputReason);
} else {
res = InputBindResult.NO_EDITOR;
}
} else {
res = InputBindResult.NULL_EDITOR_INFO;
}
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
return res;
}
startInputOrWindowGainedFocus和windowGainedFocus方法最重要的地方是判断window的softInputMode
这里有两个重要的变量
isTextEditor 通过imm传过来的controlFlags参数判断当前焦点view是否是编辑框
doAutoShow 配置的softInputMode是否带有WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE参数
Window的softInputMode我们可以在manifest中注册或者动态通过代码设置
AndroidManifest android:windowSoftInputMode="stateHidden"
Java getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE );
不作任何处理
隐藏输入法且带有SOFT_INPUT_IS_FORWARD_NAVIGATION参数
隐藏输入法,和上面的区别是不会判断SOFT_INPUT_IS_FORWARD_NAVIGATION参数
显示输入法且带有SOFT_INPUT_IS_FORWARD_NAVIGATION参数
显示输入法,和上面的区别是不会判断SOFT_INPUT_STATE_ALWAYS_VISIBLE
注意: WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION 是根据AMS中这个方法的返回值决定的
boolean isNextTransitionForward() {
int transit = mWindowManager.getPendingAppTransition();
return transit == TRANSIT_ACTIVITY_OPEN
|| transit == TRANSIT_TASK_OPEN
|| transit == TRANSIT_TASK_TO_FRONT;
}
接着首先分析显示流程
如果当前EditorInfo不为null(通常不为null) 则会先走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) {
......
// 如果是从其他app跳转过来的 先解绑之前的client,然后再绑定新的cleint
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;
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);
}
......
}
return startInputInnerLocked();
}
在imms内部每一个app对应一个client,如果从不同的app切换跳转过来会先解绑之前的client,解绑过程会回调ims的unbindInput方法和之前client的imm的onUnbindMethod方法和setActive(false)方法,然后再调用新的client的setActive(true)方法。
接着会调用attachNewInputLocked方法
如果imms没有绑定当前client,即从其他app跳转过来的,则会在这个方法里绑定一次
@GuardedBy("mMethodMap")
@NonNull
InputBindResult attachNewInputLocked(
/* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
if (!mBoundToMethod) {
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
mBoundToMethod = true;
}
final Binder startInputToken = new Binder();
final StartInputInfo info = new StartInputInfo(mCurToken, mCurId, startInputReason,
!initial, mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode,
mCurSeq);
mStartInputMap.put(startInputToken, info);
mStartInputHistory.addEntry(info);
final SessionState session = mCurClient.curSession;
executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
startInputToken, session, mCurInputContext, mCurAttribute));
if (mShowRequested) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
showCurrentInputLocked(getAppShowFlags(), null);
}
return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
session.session, (session.channel != null ? session.channel.dup() : null),
mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
}
然后会回调ims的bindInput方法和onStartInput方法,注意这个有一个属性mShowRequested,如果之前输入法是显示状态,则这个值为true,否则为false。
在window焦点改变且显示输入法的情况下,即使这里会fasle了,后面也会再次调用showCurrentInputLocked方法
boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
mShowRequested = true;
if (mAccessibilityRequestingNoSoftKeyboard) {
return false;
}
if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
mShowExplicitlyRequested = true;
mShowForced = true;
} else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
mShowExplicitlyRequested = true;
}
if (!mSystemReady) {
return false;
}
boolean res = false;
if (mCurMethod != null) {
if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
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)) {
// 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;
}
这个方法比较简单,正常情况下这里会回调ims的showSoftInput方法,只不过需要注意mShowForced和mShowExplicitlyRequested这两个属性
如果flag带有InputMethodManager.SHOW_FORCED,则这里mShowForced和mShowExplicitlyRequested均置为true。
如果flag带有InputMethodManager.SHOW_IMPLICIT,则这里mShowExplicitlyRequested置为true。
显示输入法的流程在此就结束了,ims的相关流程会在之后分析。
接下来分析隐藏输入法的流程,比较简单
boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mShowExplicitlyRequested || mShowForced)) {
if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
return false;
}
if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
return false;
}
// There is a chance that IMM#hideSoftInput() is called in a transient state where
// IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
// to be updated with the new value sent from IME process. Even in such a transient state
// historically we have accepted an incoming call of IMM#hideSoftInput() from the
// application process as a valid request, and have even promised such a behavior with CTS
// since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only
// IMMS#InputShown indicates that the software keyboard is shown.
// TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
final boolean shouldHideSoftInput = (mCurMethod != null) && (mInputShown ||
(mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
boolean res;
if (shouldHideSoftInput) {
// The IME will report its visible state again after the following message finally
// delivered to the IME process as an IPC. Hence the inconsistency between
// IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
// the final state.
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
res = true;
} else {
res = false;
}
if (mHaveConnection && mVisibleBound) {
mContext.unbindService(mVisibleConnection);
mVisibleBound = false;
}
mInputShown = false;
mShowRequested = false;
mShowExplicitlyRequested = false;
mShowForced = false;
return res;
}
这里看到如果mShowForced或者mShowExplicitlyRequested为true,传入的flag必须具有对应的flag,否则会直接return。
正常流程下会调用ims的hideSoftInput方法
imm::showSoftInput -> imms:showSoftinput -> imms:showCurrentInputLocked 流程比较简单,同之前的流程
imm
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
@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) {
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);
}
}
imm::hideSoftInputFromWinow -> imms::hideSoftinput -> imms::hideCurrentInputLocked 同之前的流程
imm
public boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
ResultReceiver resultReceiver) {
checkFocus();
synchronized (mH) {
if (mServedView == null || mServedView.getWindowToken() != windowToken) {
return false;
}
try {
return mService.hideSoftInput(mClient, flags, resultReceiver);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
imms
@Override
public boolean hideSoftInput(IInputMethodClient client, int flags,
ResultReceiver resultReceiver) {
if (!calledFromValidUser()) {
return false;
}
int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
synchronized (mMethodMap) {
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)) {
if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
+ uid + ": " + client);
return false;
}
} catch (RemoteException e) {
return false;
}
}
if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
return hideCurrentInputLocked(flags, resultReceiver);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
ims:requestShowSelf -> mImm.showSoftInputFromInputMethodInternal -> imms:showMySoftInput
ims
public final void requestShowSelf(int flags) {
mImm.showSoftInputFromInputMethodInternal(mToken, flags);
}
imm
public void showSoftInputFromInputMethodInternal(IBinder token, int flags) {
try {
mService.showMySoftInput(token, flags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
imms
@Override
public void showMySoftInput(IBinder token, int flags) {
if (!calledFromValidUser()) {
return;
}
synchronized (mMethodMap) {
if (!calledWithValidToken(token)) {
return;
}
long ident = Binder.clearCallingIdentity();
try {
showCurrentInputLocked(flags, null);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
除了以上三个方法,还有其他的比如toggelSoftInput或者restartInput等等,实际流程都差不多~