Android IMS框架流程
模块简图:
输入法配置:
1.1 控制可用输入法和默认输入法:
文件:frameworks/base/packages/SettingsProvider/res/values/defaults.xml
字段:<string name="enabled_input_methods" translatable="false">com.sogou.inputmethod.iot/com.sogou.iot.SogouInputMethodService:com.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME</string>
<string name="default_input_method" translatable="false">com.sogou.inputmethod.iot/com.sogou.iot.SogouInputMethodService</string>
<string name="default_input_method" translatable="false">com.sogou.inputmethod.iot/com.sogou.iot.SogouInputMethodService</string>
代码流程:
2.1 客户端应用注册到输入法系统服务
2.1.1 创建LocationManger
1)客户端应用创建时,调用ViewRoot(…);
2)调用ViewRoot .getWindowSession(…);
3)通过调用InputMethodManager.getInstance()创建LocationManager对象,一个客户端应用只会创建一个
LocationManager对象;
4)LocationManager对象创建时,会创建一个IInputMethodClient对象,同时创建一个IInputContext对象;
2.1.2 注册到输入法系统服务
1)调用WindowMangerService. openSession(…);
2)调用WindowMangerService. Session(…);
3)调用InputMethodMangerService.addClient(IInputMethodClientclient, IInputContext inputContext, int uid, int
pid),将InputMethodManager中创建的IInputMethodClient对象以及InputMethodManager中创建的IInputContext
对象传入进去,其中uid为客户端应用用户ID,pid为客户端应用进程ID;
4)加入到InputMethodManagerService维护的一个列表HashMap<IBinder, ClientState>中,其中IBinder对应
IInputMethodClient,一个InputMethodManager只有一个IInputMethodCliend。
2.2 客户端应用发起调用输入法的请求
2.2.1 按下输入框触发焦点请求
1)TextView.performAccessibilityActionClick(); //按下输入框,触发点击
2) View.requestFocus();//请求获取焦点
2.2.2 判断是否需要显示输入法
1)调用InputMethodManager. startInput() ——> 调用InputMethodManager. startInputInner();
//InputMethodManager.java
boolean startInputInner(@StartInputReason int startInputReason,
@Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
@SoftInputModeFlags int softInputMode, int windowFlags) {
...
if (windowGainingFocus == null) {
windowGainingFocus = view.getWindowToken();
if (windowGainingFocus == null) {
Log.e(TAG, "ABORT input: ServedView must be attached to a Window");
return false;
}
startInputFlags = getStartInputFlags(view, startInputFlags);
softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode;
windowFlags = view.getViewRootImpl().mWindowAttributes.flags;
}
...
// 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.autofillId = view.getAutofillId();
tba.fieldId = view.getId();
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.
final View servedView = getServedViewLocked();
if (servedView != view || !mServedConnecting) {
// Something else happened, so abort.
if (DEBUG) Log.v(TAG,
"Starting input: finished by someone else. view=" + dumpViewInfo(view)
+ " servedView=" + dumpViewInfo(servedView)
+ " 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) {
startInputFlags |= StartInputFlags.INITIAL_CONNECTION;
}
// Hook 'em up and let 'er rip.
mCurrentTextBoxAttribute = tba;
maybeCallServedViewChangedLocked(tba);
mServedConnecting = false;
if (mServedInputConnectionWrapper != null) {
mServedInputConnectionWrapper.deactivate();
mServedInputConnectionWrapper = null;
}
ControlledInputConnectionWrapper servedContext;
final int missingMethodFlags;
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();
}
servedContext = new ControlledInputConnectionWrapper(
icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this, view);
} else {
servedContext = null;
missingMethodFlags = 0;
}
mServedInputConnectionWrapper = servedContext;
try {
if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+ ic + " tba=" + tba + " startInputFlags="
+ InputMethodDebug.startInputFlagsToString(startInputFlags));
final InputBindResult res = mService.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, startInputFlags,
softInputMode, windowFlags, tba, servedContext, missingMethodFlags,
view.getContext().getApplicationInfo().targetSdkVersion);
...
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
}
return true;
-
-
startInputFlags = getStartInputFlags(view, startInputFlags);
,这行代码对获取焦点的View的类型做了判断,如果View是一个TextEditor,则startInputFlags会增加一个StartInputFlags.IS_TEXT_EDITOR
的标志,这个标志十分重要,它决定了IME的显示与隐藏的行为。 -
在创建完
InputConnection
后,调用了IMMS的startInputOrWindowGainedFocus
方法,控制IME App的显示或隐藏。其函数内(startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0)
这段代码是非EditText获取到焦点无法弹出IME的关键逻辑。
-
2.2.3 完成系统IMMS与应用IME的绑定
1)InputMethodManagerService.startInputUncheckedLocked
//InputMethodManagerService.java
InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
@MissingMethodFlags int missingMethods, @NonNull EditorInfo attribute,
@StartInputFlags int startInputFlags, @StartInputReason int startInputReason) {
...
mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
mCurIntent.setComponent(info.getComponent());
mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.input_method_binding_label);
mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS),
PendingIntent.FLAG_IMMUTABLE));
if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
mLastBindTime = SystemClock.uptimeMillis();
mHaveConnection = true;
mCurId = info.getId();
mCurToken = new Binder();
mCurTokenDisplayId = displayIdToShowIme;
try {
if (DEBUG) {
Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
+ mCurTokenDisplayId);
}
mIWindowManager.addWindowToken(mCurToken, LayoutParams.TYPE_INPUT_METHOD,
mCurTokenDisplayId);
} catch (RemoteException e) {
}
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
null, null, mCurId, mCurSeq, null);
}
mCurIntent = null;
Slog.w(TAG, "Failure connecting to input method service: " + mCurIntent);
return InputBindResult.IME_NOT_CONNECTED;
}
上述代码将IMMS 与实际输入法服务建立了绑定关系,Intent的具体参数如下:
-
-
Action:
InputMethod.SERVICE_INTERFACE
: "android.view.InputMethod" -
Component:默认输入法App的Component信息,它是动态查询的;当系统存在多个输入法App时,由
Settings.Secure.DEFAULT_INPUT_METHOD
属性决定默认的输入法。 -
Extra: EXTRA_CLIENT_LABEL,一个label,携带的信息是不同语言下的”输入法“这个词语,不太重要。
-
Extra: EXTRA_CLIENT_INTENT,输入法设置页面Activity路径,一般配置在系统的Settings App中。
-
通过这一部分代码,IMMS与系统默认的IME App最终建立了绑定关系,实现了远端相互通讯。
2.3 客户端创建会话
2.3.1 bindService成功后,触发onServiceConnected的回调:
//InputMethodManagerService.java
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
...
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
requestClientSessionLocked(mCurClient);
}
}
}
}
此回调中关键语句在于
requestClientSessionLocked(mCurClient);
,它的作用是创建一个IM客户端Session——IInputMethodSession,用于IMMS与IME(InputMethodEditor)的具体事件通讯,如软键盘字符按下的事件、隐藏软键盘等。2.3.2 创建一个IM客户端Session并注册,触发MethodCallback的回调:
//InputMethodManagerService.java
void requestClientSessionLocked(ClientState cs) {
if (!cs.sessionRequested) {
if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
cs.sessionRequested = true;
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
MSG_CREATE_SESSION, mCurMethod, channels[1],
new MethodCallback(this, mCurMethod, channels[0])));
}
}
请注意上述代码的MethodCallback这个类,从命名上可以看出,它是一个回调接口。事实上,当IMMS成功创建IInputMethodSession并将它注册到服务端后,MethodCallback就会收到Session创建成功后由sessionCreated调用的onSessionCreated回调。
2.4 输入法系统服务调起输入法
2.4.1 应用向系统IMMS发起handler消息请求
1)InputMethodManagerService.onSessionCreated——>
InputMethodManagerService.attachNewInputLocked :
//InputMethodManagerService.java
InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
if (!mBoundToMethod) {
...
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(mCurFocusedWindow, getAppShowFlags(), null,
SoftInputShowHideReason.ATTACH_NEW_INPUT);
}
return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
session.session, (session.channel != null ? session.channel.dup() : null),
mCurId, mCurSeq, mCurActivityViewToScreenMatrix);
}
它向Handler发送了一条
MSG_START_INPUT
的消息://InputMethodManagerService.java
case MSG_START_INPUT: {
final int missingMethods = msg.arg1;
final boolean restarting = msg.arg2 != 0;
args = (SomeArgs) msg.obj;
final IBinder startInputToken = (IBinder) args.arg1;
final SessionState session = (SessionState) args.arg2;
final IInputContext inputContext = (IInputContext) args.arg3;
final EditorInfo editorInfo = (EditorInfo) args.arg4;
try {
setEnabledSessionInMainThread(session);
session.method.startInput(startInputToken, inputContext, missingMethods,
editorInfo, restarting, session.client.shouldPreRenderIme);
} catch (RemoteException e) {
}
args.recycle();
return true;
}
从
session.method.startInput
这句话开始,中间会经历 IInputMethodWrapper.startInput——>
InputMethod.dispatchStartInputWithToken——>
InputManagerService.startInput——>
InputMethodService.doStartInput
2.4.2 系统IMMS将输入法窗口显示出来
1)最终调用到IMMS bind服务端——InputMethodService的
doStartInput
方法中://InputMethodService.java
void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
...
if (mDecorViewVisible) {
...
} else if (mCanPreRender && mInputEditorInfo != null && mStartedInputConnection != null) {
// Pre-render IME views and window when real EditorInfo is available.
// pre-render IME window and keep it invisible.
if (DEBUG) Log.v(TAG, "Pre-Render IME for " + mInputEditorInfo.fieldName);
if (mInShowWindow) {
Log.w(TAG, "Re-entrance in to showWindow");
return;
}
mDecorViewWasVisible = mDecorViewVisible;
mInShowWindow = true;
startViews(prepareWindow(true /* showInput */));
// compute visibility
mIsPreRendered = true;
onPreRenderedWindowVisibilityChanged(false /* setVisible */);
// request draw for the IME surface.
// When IME is not pre-rendered, this will actually show the IME.
if (DEBUG) Log.v(TAG, "showWindow: draw decorView!");
mWindow.show();
maybeNotifyPreRendered();
mDecorViewWasVisible = true;
mInShowWindow = false;
} else {
mIsPreRendered = false;
}
}
doStartInput
函数代码中的mWindow.show()
调用标识着IME软键盘的UI界面已经成功展示在屏幕上,至此完成点击输入框到调出软键盘的整个流程。本文来自博客园,作者:小汀,转载请注明原文链接:https://www.cnblogs.com/1118zjg/p/18101121