基于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
输入法弹出流程
输入法#拉起流程#输入框弹出流程.png
viewClicked流程
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));