Android 输入法窗口焦点获取流程(2) ,输入法窗口和应用窗口绑定
目录
1 ActvityRecord状态变化
2 窗口和输入法绑定
2.1 updateFocusedWindowLocked
2.2 ViewRootImpl#handleWindowFocusChanged
2.3 imm.onPostWindowFocus
2.3.1 checkFocusNoStartInput
2.3.2 startInputInner()
2.4 InputMethodMangerService#startInputOrWindowGainedFocus
2.5 windowGainedFocus
2.6 startInputUncheckedLocked
2.7 attachNewInputLocked
2.7.1 结果返回
Window和Session创建成功后,窗口的下一步流程为获取焦点
我们看下焦点获取过程,跟输入法相关的流程
ActvityRecord状态变化
static enum ActivityState {
INITIALIZING,
RESUMED,
PAUSING,
PAUSED,
STOPPING,
STOPPED,
FINISHING,//finish过程,只有当显示或者被动的去finish activity时候,才会调用
DESTROYING,
DESTROYED;
private ActivityState() {
}
}
窗口和输入法绑定
以下是Activity窗口初次获取焦点的流程
当两个activity 切换时,失去焦点的窗口调用过程如下:
输入法#失去焦点的窗口变化.png
对应的,获取焦点的额窗口的调用过程如下:
获取窗口的焦点变化
if (focusMayChange) {
//System.out.println("Focus may change: " + win.mAttrs.getTitle());
if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
false /*updateInputWindows*/)) {
imMayMove = false;
}
//System.out.println("Relayout " + win + ": focus=" + mCurrentFocus);
}
当B窗口的状态切换到RESUMED时,当窗口的focus可能变化时,会调用updateFocusedWindowLocked
updateFocusedWindowLocked
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
WindowState newFocus = mRoot.computeFocusedWindow();
if (mCurrentFocus != newFocus) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
// This check makes sure that we don't already have the focus
// change message pending.
mH.removeMessages(H.REPORT_FOCUS_CHANGE);
mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
// TODO(multidisplay): Focused windows on default display only.
}
ViewRootImpl#handleWindowFocusChanged
private void handleWindowFocusChanged() {
final boolean hasWindowFocus;
final boolean inTouchMode;
synchronized (this) {
if (!mWindowFocusChanged) {
return;
}
mWindowFocusChanged = false;
hasWindowFocus = mUpcomingWindowFocus;
inTouchMode = mUpcomingInTouchMode;
}
if (mAdded) {
profileRendering(hasWindowFocus);
if (hasWindowFocus) {//获取焦点后,会初始化mThreadedRenderer相关参数
ensureTouchModeLocally(inTouchMode);
if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) {
mFullRedrawNeeded = true;
try {
final WindowManager.LayoutParams lp = mWindowAttributes;
final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
//初始化mThreadedRenderer相关参数
mAttachInfo.mThreadedRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
Log.e(mTag, "OutOfResourcesException locking surface", e);
try {
if (!mWindowSession.outOfMemory(mWindow)) {
Slog.w(mTag, "No processes killed for memory;"
+ " killing self");
Process.killProcess(Process.myPid());
}
} catch (RemoteException ex) {
}
// Retry in a bit.
mHandler.sendMessageDelayed(mHandler.obtainMessage(
MSG_WINDOW_FOCUS_CHANGED), 500);
return;
}
}
}
mAttachInfo.mHasWindowFocus = hasWindowFocus;
mLastWasImTarget = WindowManager.LayoutParams
.mayUseInputMethod(mWindowAttributes.flags);
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
imm.onPreWindowFocus(mView, hasWindowFocus);
}
if (mView != null) {
mAttachInfo.mKeyDispatchState.reset();
mView.dispatchWindowFocusChanged(hasWindowFocus);
mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
if (mAttachInfo.mTooltipHost != null) {
mAttachInfo.mTooltipHost.hideTooltip();
}
}
// Note: must be done after the focus change callbacks,
// so all of the view state is set up correctly.
if (hasWindowFocus) {
//FLAG_LOCAL_FOCUS_MODE允许独立于window manager来控制焦点事件
//通常该模式的窗口不能从window manager获取触摸/按键事件, 但能够通过Window#injectInputEvent(InputEvent)来获取本地注入事件
if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
imm.onPostWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
// Clear the forward bit. We can just do this directly, since
// the window manager doesn't care about it.
mWindowAttributes.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
((WindowManager.LayoutParams) mView.getLayoutParams())
.softInputMode &=
~WindowManager.LayoutParams
.SOFT_INPUT_IS_FORWARD_NAVIGATION;
mHasHadWindowFocus = true;
// Refocusing a window that has a focused view should fire a
// focus event for the view since the global focused view changed.
fireAccessibilityFocusEventIfHasFocusedNode();
} else {
if (mPointerCapture) {
handlePointerCaptureChanged(false);
}
}
}
mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
}
-
如果是获取窗口焦点过程,调用onPostWindowFocus:
mAttachInfo:存储当前Veiw和绑定的窗口相关的信息
mAttachInfo.mThreadedRenderer:渲染线程
当前窗口与获取焦点时,初始化渲染线程相关参数,保存当前窗口状态到mAttachInfo(例如,是否获取了焦点);然后调用imm.onPostWindowFocus去执行绑定输入法的操作 -
如果是失去窗口焦点过程,则直接回调onWindowFocusChanged;也即是说,失去焦点的窗口,不再跟输入法发生关联
imm.onPostWindowFocus
在该方法中,判断,如果还没有执行startInputInner方法,则执行startInputInner方法,否则,直接执行startInputOrWindowGainedFocus方法
-
rootView:启动的activity的DecorView
-
focusedView:获取焦点的view,一般是一个EditorText;对于初次进入一个Activity,并且没有EditorText自动获取焦点,它为null
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
软键盘直接覆盖Activity,通常这是默认值
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
Activity高度会变化,让出软键盘的空间。和WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN 为2选1的值
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
Activity一打开就直接显示软键盘窗口,如果该窗口需要的话(即有EditText,或有ditable的控件)
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
Activity打开后并不直接显示软键盘窗口,直到用户自己touch文本框。 -
first:第一次进入时为true
/**
* 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;
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);
}
int controlFlags = 0;
if (focusedView != null) {//窗口获取焦点过程,它为null
controlFlags |= CONTROL_WINDOW_VIEW_HAS_FOCUS;
if (focusedView.onCheckIsTextEditor()) {
controlFlags |= CONTROL_WINDOW_IS_TEXT_EDITOR;
}
}
if (first) {//第一次进入改activity窗口,设置为CONTROL_WINDOW_FIRST
controlFlags |= CONTROL_WINDOW_FIRST;
}
//检查是否执行过startInput,只有在第一次获取焦点时,才会执行startinput;例如:在该activity弹出一个弹框,弹框再消失会重新获取焦点,此
//中场景不会重复执行startInputInner
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;
}
}
// For some reason we didn't do a startInput + windowFocusGain, so
// we'll just do a window focus gain and call it a day.
synchronized (mH) {
try {
if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
//不执行startInputInner的情况下,携带参数START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY执行startInputOrWindowGainedFocus
mService.startInputOrWindowGainedFocus(
InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
rootView.getWindowToken(), controlFlags, softInputMode, windowFlags, null,
null, 0 /* missingMethodFlags */,
rootView.getContext().getApplicationInfo().targetSdkVersion);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
主要流程:
1:设置controlFlags的flag为CONTROL_WINDOW_FIRST
2:检查是否已经执行过startInputInner,没有的话执行startInputInner-->startInputOrWindowGainedFocus;否则,直接执行startInputOrWindowGainedFocus
两条路径,携带的startInputReason参数不一样
checkFocusNoStartInput
private boolean checkFocusNoStartInput(boolean forceNewFocus) {
// This is called a lot, so short-circuit before locking.
//mServedView:将要退出的activity
//将要启动的ActivitymNextServedView
//forceNewFocus false
if (mServedView == mNextServedView && !forceNewFocus) {
return false;
}
//负责输入法应用和客户端应用的通信
final ControlledInputConnectionWrapper ic;
synchronized (mH) {
//当前后两次获取的焦点相同,并且不强制重新获取焦点的时候,不执行startInput
if (mServedView == mNextServedView && !forceNewFocus) {
return false;
}
if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
+ " next=" + mNextServedView
+ " forceNewFocus=" + forceNewFocus
+ " package="
+ (mServedView != null ? mServedView.getContext().getPackageName() : "<none>"));
if (mNextServedView == null) {
finishInputLocked();
// In this case, we used to have a focused view on the window,
// but no longer do. We should make sure the input method is
// no longer shown, since it serves no purpose.
closeCurrentInput();
return false;
}
//mServedInputConnectionWrapper为null,只有当获取焦点的窗口是EditorText时,该对象才会创建;当前场景为Activity
ic = mServedInputConnectionWrapper;
mServedView = mNextServedView;
mCurrentTextBoxAttribute = null;
mCompletions = null;
mServedConnecting = true;
}
if (ic != null) {
ic.finishComposingText();
}
return true;//返回true,接着执行startInputInner
}
主要流程:
1:检查要启动和退出的ServedView是否为同一个,如果为同一个,则表示已经执行过startInputInner,则返回false,表示不再执行startInputInner
2:如果获取焦点的是EditorText,会创建跟IMS通信的mServedInputConnectionWrapper对象
startInputInner()
boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
IBinder windowGainingFocus, int controlFlags, int softInputMode,
int windowFlags) {
final View view;
synchronized (mH) {
view = mServedView;//赋值为mServedView,也就是DecorView
// Make sure we have a window token for the served view.
if (DEBUG) {
Log.v(TAG, "Starting input: view=" + dumpViewInfo(view) +
" reason=" + InputMethodClient.getStartInputReason(startInputReason));
}
if (view == null) {
if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
return false;
}
}
...
//创建一个EditorInfo,存储应用层,跟输入法相关的Edit对象,典型的对象是EditorText
EditorInfo tba = new EditorInfo();
// Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the
// system can verify the consistency between the uid of this process and package name passed
// from here. See comment of Context#getOpPackageName() for details.
//存储应用程序包名
tba.packageName = view.getContext().getOpPackageName();
tba.fieldId = view.getId();
//用于输入法应用和输入框通信的InputConnection对象,因为此时的view为DecorView,并没有复写onCreateInputConnection,因此返回null
InputConnection ic = view.onCreateInputConnection(tba);
if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
synchronized (mH) {
// Now that we are locked again, validate that our state hasn't
// changed.
if (mServedView != view || !mServedConnecting) {//多任务操作场景,例如输入法还没弹出前,启动了其他创库哦,导致mServedView != view
// Something else happened, so abort.
if (DEBUG) Log.v(TAG,
"Starting input: finished by someone else. view=" + dumpViewInfo(view)
+ " mServedView=" + dumpViewInfo(mServedView)
+ " mServedConnecting=" + mServedConnecting);
return false;
}
// If we already have a text box, then this view is already
// connected so we want to restart it.
if (mCurrentTextBoxAttribute == null) {
//表示window刚获取focus焦点
controlFlags |= CONTROL_START_INITIAL;
}
// Hook 'em up and let 'er rip.
mCurrentTextBoxAttribute = tba;
mServedConnecting = false;
if (mServedInputConnectionWrapper != null) {
mServedInputConnectionWrapper.deactivate();
mServedInputConnectionWrapper = null;
}
ControlledInputConnectionWrapper servedContext;
final int missingMethodFlags;
if (ic != null) {
//...
} else {
servedContext = null;
missingMethodFlags = 0;
}
mServedInputConnectionWrapper = servedContext;//null
try {
if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+ ic + " tba=" + tba + " controlFlags=#"
+ Integer.toHexString(controlFlags));
// 从IMMS返回获取窗口焦点的结果
final InputBindResult res = mService.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
windowFlags, tba, servedContext, missingMethodFlags,
view.getContext().getApplicationInfo().targetSdkVersion);
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
if (res == null) {
Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
+ " null. startInputReason="
+ InputMethodClient.getStartInputReason(startInputReason)
+ " editorInfo=" + tba
+ " controlFlags=#" + Integer.toHexString(controlFlags));
return false;
}
if (res.id != null) {
//根据返回的结果,初始化本地输入法相关变量,记录当前系统中默认的输入法信息
setInputChannelLocked(res.channel);
mBindSequence = res.sequence;
mCurMethod = res.method;
mCurId = res.id;
mNextUserActionNotificationSequenceNumber =
res.userActionNotificationSequenceNumber;
} else if (res.channel != null && res.channel != mCurChannel) {
res.channel.dispose();
}
switch (res.result) {
case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
mRestartOnNextWindowFocus = true;
break;
}
if (mCurMethod != null && mCompletions != null) {
try {
mCurMethod.displayCompletions(mCompletions);
} catch (RemoteException e) {
}
}
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
}
return true;
}
主要流程:
1:创建EditorInfo对象tba,这个参数对TextView布局才有意义,它的初始化是在mServedView的onCreateInputConnection完成实例化的
2:根据EditorInfo创建一个InputConnection对象,输入法应用通过该对象,完成输入内容到输入框的传递;ACTIVITY获取焦点场景,该对象
为null,因为没有要输入的对象
startInputOrWindowGainedFocus携带的参数
mService.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
windowFlags, tba, servedContext, missingMethodFlags,
view.getContext().getApplicationInfo().targetSdkVersion);
startInputReason = 1
表示,该流程是窗口获取焦点过程
public static final int START_INPUT_REASON_UNSPECIFIED = 0;
public static final int START_INPUT_REASON_WINDOW_FOCUS_GAIN = 1;
public static final int START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY = 2;
public static final int START_INPUT_REASON_APP_CALLED_RESTART_INPUT_API = 3;
public static final int START_INPUT_REASON_CHECK_FOCUS = 4;
public static final int START_INPUT_REASON_BOUND_TO_IMMS = 5;
public static final int START_INPUT_REASON_UNBOUND_FROM_IMMS = 6;
public static final int START_INPUT_REASON_ACTIVATED_BY_IMMS = 7;
public static final int START_INPUT_REASON_DEACTIVATED_BY_IMMS = 8;
public static final int START_INPUT_REASON_SESSION_CREATED_BY_IME = 9;
mClient
应用层创建的IInputMethodClient对象,为服务层提供应用层的各个回调方法
该方法跟应用进程首次创建时Session时,传递到IMMS的对象是同一个对象
final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
//...
}
@Override
public void setUsingInputMethod(boolean state) {
}
@Override
public void onBindMethod(InputBindResult res) {
}
@Override
public void onUnbindMethod(int sequence, @InputMethodClient.UnbindReason int unbindReason) {
}
@Override
public void setActive(boolean active, boolean fullscreen) {
}
@Override
public void setUserActionNotificationSequenceNumber(int sequenceNumber) {
}
@Override
public void reportFullscreenMode(boolean fullscreen) {
}
};
windowGainingFocus:
应用层的ViewRootImpl$W对象
controlFlags |= CONTROL_START_INITIAL;
表示window窗口刚开始获取焦点
softInputMode = SOFT_INPUT_ADJUST_RESIZE , 允许调整输入法窗口,避免被其他窗口遮挡
tba, EditorInfo对象
EditorInfo tba = new EditorInfo();
// Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the
// system can verify the consistency between the uid of this process and package name passed
// from here. See comment of Context#getOpPackageName() for details.
tba.packageName = view.getContext().getOpPackageName();
tba.fieldId = view.getId();
servedContext
null
missingMethodFlags
InputConnection ic = view.onCreateInputConnection(tba);
此时的view是指activity的DecorView,该view没有复写onCreateInputConnection方法,因此返回null
ic等于null的情况下,为0
InputMethodMangerService#startInputOrWindowGainedFocus
@NonNull
@Override
public InputBindResult startInputOrWindowGainedFocus(
/* @InputMethodClient.StartInputReason */ final int startInputReason,
IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
/* @InputConnectionInspector.missingMethods */ final int missingMethods,
int unverifiedTargetSdkVersion) {
final InputBindResult result;
if (windowToken != null) {//对应的获取焦点的Activity的窗口
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;
}
当应用层传递的W对象windowToken不为null的时候,则创建windowGainedFocus对象,返回给app
windowGainedFocus
@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) {
//因为client在进程第一次创建Session的时候,已经传递给IMMS了,因此这里不为null
ClientState cs = mClients.get(client.asBinder());
if (cs == null) {
throw new IllegalArgumentException("unknown client "
+ client.asBinder());
}
try {
if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
// Check with the window manager to make sure this client actually
// has a window with focus. If not, reject. This is thread safe
// because if the focus changes some time before or after, the
// next client receiving focus that has any interest in input will
// be calling through here after that change happens.
//为了保证线程安全,会检查改client对应的窗口,是否是获取焦点的窗口,避免调用该处代码是,焦点窗口发生改变
if (DEBUG) {
Slog.w(TAG, "Focus gain on non-focused client " + cs.client
+ " (uid=" + cs.uid + " pid=" + cs.pid + ")");
}
return InputBindResult.NOT_IME_TARGET_WINDOW;
}
} catch (RemoteException e) {
}
//
if (!calledFromValidUser) {
Slog.w(TAG, "A background user is requesting window. Hiding IME.");
Slog.w(TAG, "If you want to interect with IME, you need "
+ "android.permission.INTERACT_ACROSS_USERS_FULL");
hideCurrentInputLocked(0, null);
return InputBindResult.INVALID_USER;
}
//检查当前获取焦点窗口的公司和已获取焦点窗口的公式,是否为同一个窗口对象,如果为同一个,则表明窗口已经获取获取了焦点,返回SUCCESS_REPORT_WINDOW_FOCUS_ONLY
//改代码,在上图流程“当该窗口第二次获取焦点时,会跳过startInputInner过程”会走改段代码逻辑
if (mCurFocusedWindow == windowToken) {
if (DEBUG) {
Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
+ " attribute=" + attribute + ", token = " + 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);
}
mCurFocusedWindow = windowToken;
mCurFocusedWindowSoftInputMode = softInputMode;
mCurFocusedWindowClient = cs;
// Should we auto-show the IME even if the caller has not
// specified what should be done with it?
// 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.
//窗口大小是否去适应输入框窗口,避免窗口被输入法遮挡
final boolean doAutoShow =
(softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
== WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|| mRes.getConfiguration().isLayoutSizeAtLeast(
Configuration.SCREENLAYOUT_SIZE_LARGE);
//焦点时否在一个Editor text上
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;
//根据传入的不同的softInputMode值,控制不同的输入法显示状态
//SOFT_INPUT_STATE_UNSPECIFIED:没有指定软键盘输入区域的显示状态
//SOFT_INPUT_STATE_UNCHANGED:不要改变软键盘输入区域的显示状态
//SOFT_INPUT_STATE_HIDDEN:在合适的时候隐藏软键盘输入区域,例如,当用户导航到当前窗口时
//SOFT_INPUT_STATE_ALWAYS_HIDDEN:当窗口获得焦点时,总是隐藏软键盘输入区域
//SOFT_INPUT_STATE_VISIBLE:在合适的时候显示软键盘输入区域,例如,当用户导航到当前窗口时
//SOFT_INPUT_STATE_ALWAYS_VISIBLE:当窗口获得焦点时,总是显示软键盘输入区域
switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
//因为,此时是进入activity的场景,softInputMode满足 SOFT_INPUT_STATE_UNSPECIFIED的场景,因此调用hideCurrentInputLocked
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");
//窗口上没有获取焦点的view,因此隐藏输入法窗口
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;
}
//隐藏输入法场景,因此didStart = false
if (!didStart) {
if (attribute != null) {
if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
|| (controlFlags
& InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) {
//inputContext应用层传递的servedContext对象,activity获取焦点场景为null
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;
}
startInputUncheckedLocked
@GuardedBy("mMethodMap")
@NonNull
InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
/* @InputConnectionInspector.missingMethods */ final int missingMethods,
@NonNull EditorInfo attribute, int controlFlags,
/* @InputMethodClient.StartInputReason */ final int startInputReason) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {//当前设置的默认输入法的id
return InputBindResult.NO_IME;
}
//检查uid和包名的一致性
if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
attribute.packageName)) {
Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
+ " uid=" + cs.uid + " package=" + attribute.packageName);
return InputBindResult.INVALID_PACKAGE_NAME;
}
//mCurClient进程代表的ClientState,如果是冷启动,进入一个新的应用,则mCurClient != cs;如果是在同一个进程中进行的窗口切换,这mCurClient == cs
//我们验证的场景是同一个进程的A-B切换,因此mCurClient==cs
if (mCurClient != cs) {//切换activity的同时,被锁屏了的场景
// 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;//非EditorText,为nulll
mCurInputContextMissingMethods = missingMethods;
mCurAttribute = attribute;//应用层传递的EditorInfo:tba 对象
// Check if the input method is changing.
//检查切换过程中,默认输入法是否发生了改变,我们的场景没有发生变化,因此返回true
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);
}
....
}
attachNewInputLocked
@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);
}
结果返回
结果返回后,会对IMM的对象进行赋值
IMM.java
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) {
}
}
如此,进入一个窗口,获取窗口焦点过程,窗口与输入法相关的流程,就结束了。
下一篇:输入法在输入框弹出流程