Android输入法弹出流程
基于Android 9.x
目录
1 viewClicked流程
1.1 viewClicked
1.2 checkFocus
1.3 startInputInner
1.4 startInputOrWindowGainedFocus
1.5 startInputLocked
1.6 startInputUncheckedLocked
1.7 attachNewInputLocked
1.7.1 处理返回的结果
2 showSoftInput流程
2.1 showSoftInput
2.2 IMMS#showSoftInput
2.3 showCurrentInputLocked
2.4 IMS$InputMethodImpl$showSoftInput
2.5 dispatchOnShowInputRequested
2.6 IMS$showWindow
2.7 showWindowInner
输入法弹出流程
输入法#拉起流程#输入框弹出流程.pngviewClicked流程
viewClicked
IMM.JAVA
public void viewClicked(View view) {attachNewInputLocked
//服务的view是否相同,当两次点击在同一个输入框时,两者相同;否则不同
//我们首次点击某个输入框为例
final boolean focusChanged = mServedView != mNextServedView;
checkFocus();
synchronized (mH) {
if ((mServedView != view && (mServedView == null
|| !mServedView.checkInputConnectionProxy(view)))
|| mCurrentTextBoxAttribute == null || mCurMethod == null) {
return;
}
try {
if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);
mCurMethod.viewClicked(focusChanged);
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
}
}
checkFocus
public void checkFocus() {
//检查该view是否已经执行过startInputInner
//主要是比较最后一个mServedView和当前mNextServedView是否相同
//该方法逻辑已经在"输入法窗口和应用窗口绑定."讲过,本章节不在介绍
if (checkFocusNoStartInput(false)) {
startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);
}
}
startInputInner
携带的参数:
-startInputReason:START_INPUT_REASON_CHECK_FOCUS,标明本次调用的目的
-windowGainingFocus:会影响IMMS中startInputOrWindowGainedFocus的调用逻辑
-controlFlags,softInputMode,windowFlags = 0
boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
IBinder windowGainingFocus, int controlFlags, int softInputMode,
int windowFlags) {
final View view;
synchronized (mH) {
//mServedView已经在checkFocusNoStartInput中赋值为mNextServedView,因此这里为当前要获取输入法焦点的view
view = mServedView;
// 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;
}
}
// Now we need to get an input connection from the served view.
// This is complicated in a couple ways: we can't be holding our lock
// when calling out to the view, and we need to make sure we call into
// the view on the same thread that is driving its view hierarchy.
Handler vh = view.getHandler();
if (vh == null) {
// If the view doesn't have a handler, something has changed out
// from under us, so just close the current input.
// If we don't close the current input, the current input method can remain on the
// screen without a connection.
if (DEBUG) Log.v(TAG, "ABORT input: no handler for view! Close current input.");
closeCurrentInput();
return false;
}
//判断view是否在UI主线程
if (vh.getLooper() != Looper.myLooper()) {
// The view is running on a different thread than our own, so
// we need to reschedule our work for over there.
if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
vh.post(() -> startInputInner(startInputReason, null, 0, 0, 0));
return false;
}
// Okay we are now ready to call into the served view and have it
// do its stuff.
// Life is good: let's hook everything up!
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();
//通过TextView的onCreateInputConnection方法初始化,EditorInfo,主要是获取inputType和imeOptions
//ic是一个EditableInputConnection,用来跟IMS通信
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.
//正常情况下不会出现mServedView != view,多线程操作会。
//mServedConnecting在checkFocusNoStartInput过程中,被置位true,为false表示startInputInner被多次执行
if (mServedView != view || !mServedConnecting) {
// 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) {
controlFlags |= CONTROL_START_INITIAL;
}
// Hook 'em up and let 'er rip.
mCurrentTextBoxAttribute = tba;
//置位false,与checkFocusNoStartInput同步该参数状态
mServedConnecting = false;
if (mServedInputConnectionWrapper != null) {
mServedInputConnectionWrapper.deactivate();
mServedInputConnectionWrapper = null;
}
ControlledInputConnectionWrapper servedContext;
final int missingMethodFlags;
//对于EditorText,不为null
if (ic != null) {
mCursorSelStart = tba.initialSelStart;
mCursorSelEnd = tba.initialSelEnd;
mCursorCandStart = -1;
mCursorCandEnd = -1;
mCursorRect.setEmpty();
mCursorAnchorInfo = null;
final Handler icHandler;
missingMethodFlags = InputConnectionInspector.getMissingMethodFlags(ic);
if ((missingMethodFlags & InputConnectionInspector.MissingMethodFlags.GET_HANDLER)
!= 0) {
// InputConnection#getHandler() is not implemented.
icHandler = null;
} else {
icHandler = ic.getHandler();
}
//创建ControlledInputConnectionWrapper
servedContext = new ControlledInputConnectionWrapper(
icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);
} else {
servedContext = null;
missingMethodFlags = 0;
}
//The InputConnection that was last retrieved from the served view.
mServedInputConnectionWrapper = servedContext;
try {
if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+ ic + " tba=" + tba + " controlFlags=#"
+ Integer.toHexString(controlFlags));
final InputBindResult res = mService.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
windowFlags, tba, servedContext, missingMethodFlags,
view.getContext().getApplicationInfo().targetSdkVersion);
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
if (res == null) {
Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
+ " null. startInputReason="
+ InputMethodClient.getStartInputReason(startInputReason)
+ " editorInfo=" + tba
+ " controlFlags=#" + Integer.toHexString(controlFlags));
return false;
}
if (res.id != null) {
setInputChannelLocked(res.channel);
mBindSequence = res.sequence;
mCurMethod = res.method;
mCurId = res.id;
mNextUserActionNotificationSequenceNumber =
res.userActionNotificationSequenceNumber;
} else if (res.channel != null && res.channel != mCurChannel) {
res.channel.dispose();
}
switch (res.result) {
case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
mRestartOnNextWindowFocus = true;
break;
}
if (mCurMethod != null && mCompletions != null) {
try {
mCurMethod.displayCompletions(mCompletions);
} catch (RemoteException e) {
}
}
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
}
return true;
}
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) {
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;
}
该步骤,主要是同InputMethodManagerService通信,获取将要弹出的输入信息,和绑定当前焦点组件到对应的输入法上
调用时,各个参数的值为:
-
startInputReason = 4 = START_INPUT_REASON_CHECK_FOCUS
-
mClient:应用层创建的IInputMethodClient对象
-
windowGainingFocus = null:应用层的ViewRootImpl$W对象
-
softInputMode = 0
-
tba = EditorInfo对象
-
servedContext = ControlledInputConnectionWrapper{connection=com.android.internal.widget.EditableInputConnection@2a772c0 finished=false mParentInputMethodManager.mActive=true}
@NonNull
private InputBindResult startInput(
/* @InputMethodClient.StartInputReason */ final int startInputReason,
IInputMethodClient client, IInputContext inputContext,
/* @InputConnectionInspector.missingMethods */ final int missingMethods,
@Nullable EditorInfo attribute, int controlFlags) {
//用户合法性验证
if (!calledFromValidUser()) {
return InputBindResult.INVALID_USER;
}
synchronized (mMethodMap) {
final long ident = Binder.clearCallingIdentity();
try {
//直接调用startInputLocked方法
return startInputLocked(startInputReason, client, inputContext, missingMethods,
attribute, controlFlags);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
startInputLocked
@GuardedBy("mMethodMap")
@NonNull
InputBindResult startInputLocked(
/* @InputMethodClient.StartInputReason */ final int startInputReason,
IInputMethodClient client, IInputContext inputContext,
/* @InputConnectionInspector.missingMethods */ final int missingMethods,
@Nullable EditorInfo attribute, int controlFlags) {
// If no method is currently selected, do nothing.
//当前默认输入法对应的id
if (mCurMethodId == null) {
return InputBindResult.NO_IME;
}
//当前应用客户单对应的ClientState对象
ClientState cs = mClients.get(client.asBinder());
if (cs == null) {
throw new IllegalArgumentException("unknown client "
+ client.asBinder());
}
if (attribute == null) {
Slog.w(TAG, "Ignoring startInput with null EditorInfo."
+ " uid=" + cs.uid + " pid=" + cs.pid);
return InputBindResult.NULL_EDITOR_INFO;
}
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.
if (DEBUG) {
Slog.w(TAG, "Starting input on non-focused client " + cs.client
+ " (uid=" + cs.uid + " pid=" + cs.pid + ")");
}
return InputBindResult.NOT_IME_TARGET_WINDOW;
}
} catch (RemoteException e) {
}
return startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
controlFlags, startInputReason);
}
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) {
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;
}
//查看启动输入法过程,应用客户端是否发生变化
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;//应用客户端传递的serverContext
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);
}
if (mHaveConnection) {
if (mCurMethod != null) {
// Return to client, and we will get back with it when
// we have had a session made for it.
requestClientSessionLocked(cs);
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
null, null, mCurId, mCurSeq,
mCurUserActionNotificationSequenceNumber);
} else if (SystemClock.uptimeMillis()
< (mLastBindTime+TIME_TO_RECONNECT)) {
// In this case we have connected to the service, but
// don't yet have its interface. If it hasn't been too
// long since we did the connection, we'll return to
// the client and wait to get the service interface so
// we can report back. If it has been too long, we want
// to fall through so we can try a disconnect/reconnect
// to see if we can get back in touch with the service.
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
null, null, mCurId, mCurSeq,
mCurUserActionNotificationSequenceNumber);
} else {
EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
}
}
}
return startInputInnerLocked();
}
attachNewInputLocked
@GuardedBy("mMethodMap")
@NonNull
InputBindResult attachNewInputLocked(
/* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
//Have we called mCurMethod.bindInput()?,此处,我们已经调用过一次,因此不会再执行
if (!mBoundToMethod) {
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
mBoundToMethod = true;
}
//StartInputInfo对象的key值
final Binder startInputToken = new Binder();
//维护一个状态快照,避免随后调用MSG_START_INPUT,避免接下来的状态发生变化,影响
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;
//去回调IMS的startInput->doStartInput
executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
startInputToken, session, mCurInputContext, mCurAttribute));
//false,改过程不会显示输入法
if (mShowRequested) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
showCurrentInputLocked(getAppShowFlags(), null);
}
//成功返回InputBindResult结果
return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
session.session, (session.channel != null ? session.channel.dup() : null),
mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
}
IMS.InputMethodImpl.startInput
@MainThread
@Override
public void startInput(InputConnection ic, EditorInfo attribute) {
if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
doStartInput(ic, attribute, false);
}
IMS.InputMethodImpl.doStartInput
void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
if (!restarting) {
doFinishInput();
}
mInputStarted = true;//代表该IMS的startInput已经执行
mStartedInputConnection = ic;//应用客户端connenction
mInputEditorInfo = attribute;//应用顾客端EditorInfo
initialize();
if (DEBUG) Log.v(TAG, "CALL: onStartInput");
onStartInput(attribute, restarting);//子类实现
if (mWindowVisible) {//当前IMS窗口不可见
if (mShowInputRequested) {
if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
mInputViewStarted = true;
onStartInputView(mInputEditorInfo, restarting);
startExtractingText(true);
} else if (mCandidatesVisibility == View.VISIBLE) {
if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
mCandidatesViewStarted = true;
onStartCandidatesView(mInputEditorInfo, restarting);
}
}
}
处理返回的结果
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;//IMMS的输入法启动计数
mCurMethod = res.method;//用来与IMS通信的对象
mCurId = res.id;//当前输入法的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 {
//与IMS通信
mCurMethod.displayCompletions(mCompletions);
} catch (RemoteException e) {
}
}
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
}
return true;
showSoftInput流程
TextView.java
if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = InputMethodManager.peekInstance();
viewClicked(imm);
if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
imm.showSoftInput(this, 0);
}
// The above condition ensures that the mEditor is not null
mEditor.onTouchUpEvent(event);
handled = true;
}
viewClicked执行完毕后,执行showSoftInput方法,去弹出输入法
showSoftInput
public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
checkFocus();
synchronized (mH) {
if (mServedView != view && (mServedView == null
|| !mServedView.checkInputConnectionProxy(view))) {
return false;
}
try {
return mService.showSoftInput(mClient, flags, resultReceiver);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
IMMS.showSoftInput
@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) {
//当前客户端进程不为null
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);
}
}
showCurrentInputLocked
boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
mShowRequested = true;
if (mAccessibilityRequestingNoSoftKeyboard) {
return false;
}
//flags = 0;
if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
mShowExplicitlyRequested = true;
mShowForced = true;
} else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
//Set if we were explicitly told to show the input method
mShowExplicitlyRequested = true;
}
if (!mSystemReady) {
return false;
}
boolean res = false;
if (mCurMethod != null) {
if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
//调用IMS$InputMethodImpl$showSoftInput方法
executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
resultReceiver));
mInputShown = true;
//mHaveConnection:Set to true if our ServiceConnection is currently actively bound to a service (whether or not we have gotten its IBinder back yet).
//mVisibleBound = false
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;//return true
}
if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
mShowExplicitlyRequested = true;
mShowForced = true;
} else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
//Set if we were explicitly told to show the input method
mShowExplicitlyRequested = true;
}
这段代码,对传递到IMS的flag影响
private int getImeShowFlags() {
int flags = 0;
if (mShowForced) {
flags |= InputMethod.SHOW_FORCED
| InputMethod.SHOW_EXPLICIT;
} else if (mShowExplicitlyRequested) {
flags |= InputMethod.SHOW_EXPLICIT;
}
return flags;
}
实验证明,当getImeShowFlags返回为0时,会导致IMS不能正常调用showWindow方法;
也就是说mShowExplicitlyRequested和mShowExplicitlyRequested必须有其一为true;
默认情况下,TextView传递的flags为0,满足(flags&InputMethodManager.SHOW_IMPLICIT) == 0,使得mShowExplicitlyRequested = true;
保证了传递到IMS的getImeShowFlags 为1(InputMethod.SHOW_EXPLICIT)
另外感受下源码的混乱
InputMethod.SHOW_EXPLICIT = 0x00001
InputMethod.SHOW_FORCED = 0x00002
InputMethodManager.SHOW_IMPLICIT = 0x0001
InputMethodManager.SHOW_FORCED = 0x0002;
InputMethod的供IMS使用,InputMethodManager供应用客户端使用。
感觉InputMethodManager少了一个定义:InputMethodManager.SHOW_EXPLICIT = 0x00000;以为TextView调用showSoftInput的时候
传递的就是0,而且为0的情况下,正好满足(flags&InputMethodManager.SHOW_IMPLICIT) == 0,是的mShowExplicitlyRequested = true;
IMS.InputMethodImpl.showSoftInput
@MainThread
@Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "showSoftInput()");
//输入法没弹出来之前,该值为false
boolean wasVis = isInputViewShown();
if (dispatchOnShowInputRequested(flags, false)) {
try {
showWindow(true);
} catch (BadTokenException e) {
// We have ignored BadTokenException here since Jelly Bean MR-2 (API Level 18).
// We could ignore BadTokenException in InputMethodService#showWindow() instead,
// but it may break assumptions for those who override #showWindow() that we can
// detect errors in #showWindow() by checking BadTokenException.
// TODO: Investigate its feasibility. Update JavaDoc of #showWindow() of
// whether it's OK to override #showWindow() or not.
}
}
clearInsetOfPreviousIme();
// If user uses hard keyboard, IME button should always be shown.
mImm.setImeWindowStatus(mToken, mStartInputToken,
mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
if (resultReceiver != null) {
resultReceiver.send(wasVis != isInputViewShown()
? InputMethodManager.RESULT_SHOWN
: (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
: InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
}
}
dispatchOnShowInputRequested
private boolean dispatchOnShowInputRequested(int flags, boolean configChange) {
//判断是否显示输入法;正常情况返回true,去显示输入法
final boolean result = onShowInputRequested(flags, configChange);
if (result) {
//设置
mShowInputFlags = flags;//默认为1,也就是InputMethod.SHOW_EXPLICIT = 0x00001
} else {
mShowInputFlags = 0;
}
return result;
}
onShowInputRequested
判断是否应该显示输入法窗口
public boolean onShowInputRequested(int flags, boolean configChange) {
//主要判断硬键盘相关逻辑,很多输入法,会复写该方法;直接返回true
if (!onEvaluateInputViewShown()) {
return false;
}
//TextView中flag的值默认都是,也就是InputMethodManager.SHOW_EXPLICIT = 0x00000;因此,不会执行里面的相关代码
if ((flags&InputMethod.SHOW_EXPLICIT) == 0) {
//如果用户不是显式的请求显示输入法,并且应用配置没有发生变化,此时请求进入显式全屏输入法窗口,会返回false
//返回false,表示不会对现有的输入法显示与否进行改变
if (!configChange && onEvaluateFullscreenMode()) {
// Don't show if this is not explicitly requested by the user and
// the input method is fullscreen. That would be too disruptive.
// However, we skip this change for a config change, since if
// the IME is already shown we do want to go into fullscreen
// mode at this point.
return false;
}
//shouldShowImeWithHardKeyboard是否支持软键盘和硬键盘共存,默认返回false
//configure.keyboard 默认为Configuration.KEYBOARD_NOKEYS,也就是:设备没有用于文本输入的硬按键。
if (!mSettingsObserver.shouldShowImeWithHardKeyboard() &&
getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS) {
// And if the device has a hard keyboard, even if it is
// currently hidden, don't show the input method implicitly.
// These kinds of devices don't need it that much.
return false;
}
}
return true;//正常情况返回true
}
flags:有三个值,0,SHOW_EXPLICIT,SHOW_FORCED
默认为0,设置为SHOW_FORCED,IMMS会强制改成 SHOW_EXPLICIT|SHOW_FORCED
private int getImeShowFlags() {
int flags = 0;
if (mShowForced) {
flags |= InputMethod.SHOW_FORCED
| InputMethod.SHOW_EXPLICIT;
} else if (mShowExplicitlyRequested) {
flags |= InputMethod.SHOW_EXPLICIT;
}
return flags;
}
另外,改方法只能返回flags |= InputMethod.SHOW_FORCED| InputMethod.SHOW_EXPLICIT;或者flags |= InputMethod.SHOW_EXPLICIT,直接返回0,会导致输入法不显示
onEvaluateInputViewShown
该方法,没有物理键盘的时候返回true
@CallSuper
public boolean onEvaluateInputViewShown() {
//不为null
if (mSettingsObserver == null) {
Log.w(TAG, "onEvaluateInputViewShown: mSettingsObserver must not be null here.");
return false;
}
//是否支持同时显示软键盘和硬键盘
if (mSettingsObserver.shouldShowImeWithHardKeyboard()) {
return true;
}
Configuration config = getResources().getConfiguration();
//设备没有硬键盘;或者硬键盘被设置为隐藏
//HARDKEYBOARDHIDDEN_YES:value corresponding to the physical keyboard being hidden
return config.keyboard == Configuration.KEYBOARD_NOKEYS
|| config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES;
}
-
config:
{0theme1.0 ?mcc?mnc [zh_CN] ldltr sw360dp w360dp h683dp 320dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=Rect(0, 58 - 720, 1424) mWindowingMode=fullscreen mActivityType=undefined} s.8} -
config.keyboard:
nokeys:设备没有用于文本输入的硬按键。
qwerty:设备具有标准硬键盘(无论是否对用户可见)。
12key:设备具有 12 键硬键盘(无论是否对用户可见)
该方法主要流程:
检查当前将要弹出的client和上一步绑定的mCurClient是否一致,如果不一致,返回false
调用上一次绑定的输入法的方法,弹出客户端输入发的布局
IMS.showWindow
public void showWindow(boolean showInput) {
if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput
+ " mShowInputRequested=" + mShowInputRequested
+ " mWindowAdded=" + mWindowAdded
+ " mWindowCreated=" + mWindowCreated
+ " mWindowVisible=" + mWindowVisible
+ " mInputStarted=" + mInputStarted
+ " mShowInputFlags=" + mShowInputFlags);
//避免重复进入
if (mInShowWindow) {
Log.w(TAG, "Re-entrance in to showWindow");
return;
}
try {
mWindowWasVisible = mWindowVisible;//当前窗口是否可见,false
mInShowWindow = true;//标志为,避免重复显示窗口
showWindowInner(showInput);
} catch (BadTokenException e) {
// BadTokenException is a normal consequence in certain situations, e.g., swapping IMEs
// while there is a DO_SHOW_SOFT_INPUT message in the IIMethodWrapper queue.
if (DEBUG) Log.v(TAG, "BadTokenException: IME is done.");
mWindowVisible = false;
mWindowAdded = false;
// Rethrow the exception to preserve the existing behavior. Some IMEs may have directly
// called this method and relied on this exception for some clean-up tasks.
// TODO: Give developers a clear guideline of whether it's OK to call this method or
// InputMethodService#requestShowSelf(int) should always be used instead.
throw e;
} finally {
// TODO: Is it OK to set true when we get BadTokenException?
mWindowWasVisible = true;
mInShowWindow = false;
}
}
showWindowInner
void showWindowInner(boolean showInput) {
boolean doShowInput = false;
//上一个输入法窗口的状态,不可见则为0
final int previousImeWindowStatus =
(mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0);
mWindowVisible = true;
//mShowInputRequested两种情况为false,oncreate和hideSoftInput
//ViewClick流程中,IMMS调用到IMS的startInput方法,置mInputStarted = true
if (!mShowInputRequested && mInputStarted && showInput) {
doShowInput = true;
mShowInputRequested = true;//只要这一个地方可以置为true
}
if (DEBUG) Log.v(TAG, "showWindow: updating UI");
initialize();//调用初始化,startinput阶段已经调用一次,将mInitialized = true,该方法中会进行判断,不会重复执行
updateFullscreenMode();//根据当前是否全屏显示输入法窗口,更新布局的LayoutParams,mLastShowInputRequested等参数
//更新mIsInputViewShown,mIsInputViewShown在oncreate为false,输入法显示后,mIsInputViewShown为true
//调用声明周期函数:onCreateInputView
updateInputViewShown();
//mWindowAdded,只有在showWindow过程中,发生BadTokenException是,才会置位false
//mWindowCreated 初次oncreate-->initView置位false
if (!mWindowAdded || !mWindowCreated) {//本输入法,启动过一次以后,该端代码将不再执行
mWindowAdded = true;
mWindowCreated = true;
initialize();
if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView");
View v = onCreateCandidatesView();//创建候选布局,默认为null
if (DEBUG) Log.v(TAG, "showWindow: candidates=" + v);
if (v != null) {
setCandidatesView(v);
}
}
if (mShowInputRequested) {//楼上已经置位true
if (!mInputViewStarted) {
if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
mInputViewStarted = true;
onStartInputView(mInputEditorInfo, false);//获取EditText的输入类型相关信息,IMS
}
} else if (!mCandidatesViewStarted) {
if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
mCandidatesViewStarted = true;
onStartCandidatesView(mInputEditorInfo, false);
}
if (doShowInput) {
startExtractingText(false);
}
final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown());
if (previousImeWindowStatus != nextImeWindowStatus) {
mImm.setImeWindowStatus(mToken, mStartInputToken, nextImeWindowStatus,
mBackDisposition);
}
if ((previousImeWindowStatus & IME_ACTIVE) == 0) {
if (DEBUG) Log.v(TAG, "showWindow: showing!");
onWindowShown();
mWindow.show();//真正显示窗口
// Put here rather than in onWindowShown() in case people forget to call
// super.onWindowShown().
mShouldClearInsetOfPreviousIme = false;
}
}
onCreateInputView获取输入法的主布局
onCreateCandidatesView:获取输入的候选词布局
onStartInputView:传递Editext相关信息到IMS的具体实现