1、概述
IMMS如何处理view绑定输入法事件呢?为了讲解整个绑定过程,我们假设此时输入法service还没启动,这个情况下的输入法绑定是最长的,整个过程经历过如下过程:
- 启动输入法service
- 绑定输入法window的token
- 请求输入法为焦点程序创建一个连接会话
- 将输入法的接口传递回程序client端
- 绑定输入法和焦点view
1-4是和程序相关的,而5是和view相关的。所以你可以说1~4是用来绑定程序window和输入法,而5是用来绑定程序view和输入法。
输入法还没启动时,弹出输入法会经过1-5,输入法已经启动,但是焦点window发生变化时会经历3~5,焦点window没有变化,只是改变了焦点view,则只会经历5。整个流程如下:
2、启动输入法service
前文Android P输入法框架系统–view如何触发绑定介绍到调用IMMS中的startInputOrWindowGainedFocus,接着分析往下的流程:
//frameworks/base/services/core/java/com/android/server/InputMethodManagerService.java
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) {//第一次启动时,windowToken为null,不走该分支
result = windowGainedFocus(startInputReason, client, windowToken, controlFlags,
softInputMode, windowFlags, attribute, inputContext, missingMethods,
unverifiedTargetSdkVersion);
} else {
result = startInput(startInputReason, client, inputContext, missingMethods, attribute,
controlFlags);
}
//省略...
}
private InputBindResult startInput(
/* @InputMethodClient.StartInputReason */ final int startInputReason,
IInputMethodClient client, IInputContext inputContext,
/* @InputConnectionInspector.missingMethods */ final int missingMethods,
@Nullable EditorInfo attribute, int controlFlags) {
//省略...
final long ident = Binder.clearCallingIdentity();
try {
return startInputLocked(startInputReason, client, inputContext, missingMethods,
attribute, controlFlags);
}
//省略...
}
InputBindResult startInputLocked(IInputMethodClient client,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
//程序在service端对应的数据结构
ClientState cs = mClients.get(client.asBinder());
return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
}
InputBindResult startInputUncheckedLocked(ClientState cs,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
//如果新程序和当前活动的程序不同
if (mCurClient != cs) {
//取消当前活动程序和输入法的绑定
unbindCurrentClientLocked();
}
//将新程序设置为当前活动的程序
mCurClient = cs;
mCurInputContext = inputContext;
mCurAttribute = attribute;
if (mCurId != null && mCurId.equals(mCurMethodId)) {
if (cs.curSession != null) {
//连接已经建立,直接开始绑定
return attachNewInputLocked(
(controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
}
if (mHaveConnection) {
//输入法的连接是否已经创建,如果已经创建,直接传递给程序client端
if (mCurMethod != null) {
requestClientSessionLocked(cs);
return new InputBindResult(null, null, mCurId, mCurSeq);
}
}
}
//否则需要启动输入法,并建立连接
return startInputInnerLocked();
}
InputBindResult startInputInnerLocked() {
InputMethodInfo info = mMethodMap.get(mCurMethodId);
unbindCurrentMethodLocked(false, true);
//启动输入法service
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), 0));
if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
| Context.BIND_NOT_VISIBLE | Context.BIND_SHOWING_UI)) {
mHaveConnection = true;
mCurId = info.getId();
//这个token是给输入法service用来绑定输入法的window的,通过这个token
//InputMethodManagerService可以很方便的直接管理输入法的window
mCurToken = new Binder();
try {
mIWindowManager.addWindowToken(mCurToken,
WindowManager.LayoutParams.TYPE_INPUT_METHOD);
} catch (RemoteException e) {
}
return new InputBindResult(null, null, mCurId, mCurSeq);
}
return null;
}
private boolean bindCurrentInputMethodService(
Intent service, ServiceConnection conn, int flags) {
if (service == null || conn == null) {
Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
return false;
}
return mContext.bindServiceAsUser(service, conn, flags,
new UserHandle(mSettings.getCurrentUserId()));
}
//输入法启动完成后就在函数onBind 传回一个binder接口
@Override
final public IBinder onBind(Intent intent) {
if (mInputMethod == null) {
mInputMethod = onCreateInputMethodInterface();
}
// IInputMethodWrapper只是一个wrapper,它负责将IMMS的调用转化为message
//然后在message线程再调用mInputMethod对应的接口
//这样输入法的处理就是异步的了,因此你说它就是mInputMethod
return new IInputMethodWrapper(this, mInputMethod);
}
@Override
public AbstractInputMethodImpl onCreateInputMethodInterface() {
return new InputMethodImpl();
}
//由于IMMS是以bindService的方式启动输入法service,所以当输入法service启动完
//成后它就会回调IMMS的onServiceConnected
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
//保存输入法service传递过来的通信接口IInputMethod
mCurMethod = IInputMethod.Stub.asInterface(service);
//将刚刚创建的window token传递给输入法service,然后输入用这个token
//创建window,这样IMMS可以用根据这个token找到输入法在IMMS里
//的数据及输入法window在WMS里的数据
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
if (mCurClient != null) {
//请求为程序和输入法建立一个连接会话,这样client就可以直接和
//输入法通信了
requestClientSessionLocked(mCurClient);
}
}
}
}
3、输入法Window token的绑定及使用分析
3.1 输入法Window token绑定
IMMS在输入法启动完成并回调onServiceConnected时会将一个Window token传递给输入法。
//frameworks/base/services/core/java/com/android/server/InputMethodManagerService.java
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
mCurMethod = IInputMethod.Stub.asInterface(service);
// 发送MSG_ATTACH_TOKEN消息
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
requestClientSessionLocked(mCurClient);
}
}
}
}
case MSG_ATTACH_TOKEN:
args = (SomeArgs)msg.obj;
try {
//和输入法通信
((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
//frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
public class InputMethodService extends AbstractInputMethodService {
public class InputMethodImpl extends AbstractInputMethodImpl {
public void attachToken(IBinder token) {
if (mToken == null) {
//保存token
mToken = token;
//这样输入法的window就绑定这个window token
mWindow.setToken(token);
}
}
}
3.2 输入法Window token使用
由于系统存在多个输入法,所以输入法要和IMMS通信,必须要个机制来标示自己是哪个输入法,这个就是通过上面的输入法Window token来实现的,比如输入法自己关闭自己:
//InputMethodService.java输入法接口
public void requestHideSelf(int flags) {
//mToken就是上面提到的过程----IMMS传递给输入法的
mImm.hideSoftInputFromInputMethod(mToken, flags);
}
//InputMethodManager.java
public void hideSoftInputFromInputMethod(IBinder token, int flags) {
try {
mService.hideMySoftInput(token, flags);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
//InputMethodManagerService.java
@Override
public void hideMySoftInput(IBinder token, int flags) {
if (!calledFromValidUser()) {
return;
}
synchronized (mMethodMap) {
if (token == null || mCurToken != token) {
if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
+ Binder.getCallingUid() + " token: " + token);
return;
}
long ident = Binder.clearCallingIdentity();
try {
hideCurrentInputLocked(flags, null);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
4、输入法连接会话创建
到此程序和输入法的session就建立了
//frameworks/base/services/core/java/com/android/server/InputMethodManagerService.java
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
//省略......
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
requestClientSessionLocked(mCurClient);
}
}
}
}
void requestClientSessionLocked(ClientState cs) {
if (!cs.sessionRequested) {
//这里又出现了InputChannel对,很面熟吧,在前面几篇文章已经详细分析过
//了,可见它已经成为一种通用的跨平台的数据通信接口了
InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
cs.sessionRequested = true;
//发送MSG_CREATE_SESSION消息
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
MSG_CREATE_SESSION, mCurMethod, channels[1],
new MethodCallback(this, mCurMethod, channels[0])));
}
}
case MSG_CREATE_SESSION: {
args = (SomeArgs)msg.obj;
IInputMethod method = (IInputMethod)args.arg1;
InputChannel channel = (InputChannel)args.arg2;
try {
method.createSession(channel, (IInputSessionCallback)args.arg3);
} catch (RemoteException e) {
}
//上面是IMMS端,下面就看IMS输入法端的处理
//frameworks/base/core/java/android/inputmethodservice/AbstractInputMethodService.java
public abstract class AbstractInputMethodService extends Service
implements KeyEvent.Callback {
public abstract class AbstractInputMethodImpl implements InputMethod {
public void createSession(SessionCallback callback) {
callback.sessionCreated(onCreateInputMethodSessionInterface());
}
}
}
@Override
public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() {
//sesion的真正实现
return new InputMethodSessionImpl();
}
//然后回到了IMMS
//InputMethodManagerService.java
void onSessionCreated(IInputMethod method, IInputMethodSession session,
InputChannel channel) {
synchronized (mMethodMap) {
if (mCurMethod != null && method != null
&& mCurMethod.asBinder() == method.asBinder()) {
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
//将session相关的数据封装到SessionState对象里
mCurClient.curSession = new SessionState(mCurClient,
method, session, channel);
//这个会开始真正的绑定 详见第5节
InputBindResult res = attachNewInputLocked(
InputMethodClient.START_INPUT_REASON_SESSION_CREATED_BY_IME, true);
if (res.method != null) {
//发送MSG_BIND_CLIENT消息,详见第6节
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
MSG_BIND_CLIENT, mCurClient.client, res));
}
return;
}
}
}
}
5、输入法和view绑定
//IMMS
//frameworks/base/services/core/java/com/android/server/InputMethodManagerService.java
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;
//发送MSG_START_INPUT消息
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);
}
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);
} catch (RemoteException e) {
}
args.recycle();
return true;
}
//frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
public void startInput(InputConnection ic, EditorInfo attribute) {
if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
doStartInput(ic, attribute, false);
}507 public void startInput(InputConnection ic, EditorInfo attribute) {
if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
doStartInput(ic, attribute, false);
}
void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
if (!restarting) {
doFinishInput();
}
mInputStarted = true;
mStartedInputConnection = ic;
mInputEditorInfo = attribute;
initialize();
if (DEBUG) Log.v(TAG, "CALL: onStartInput");
onStartInput(attribute, restarting);
if (mWindowVisible) {
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);
}
}
}
6、传递输入法接口给程序
case MSG_BIND_CLIENT: {
args = (SomeArgs)msg.obj;
IInputMethodClient client = (IInputMethodClient)args.arg1;
InputBindResult res = (InputBindResult)args.arg2;
try {
//会调回到程序端
client.onBindMethod(res);
} catch (RemoteException e) {
Slog.w(TAG, "Client died receiving input method " + args.arg2);
} finally {
// Dispose the channel if the input method is not local to this process
// because the remote proxy will get its own copy when unparceled.
if (res.channel != null && Binder.isProxy(client)) {
res.channel.dispose();
}
}
args.recycle();
return true;
}
到此焦点view已经通过调用IMMS的startInput和输入法绑定了,但是此时输入法还没有显示。但是系统紧接着会调用windowGainFocus来显示输入法。未完待续。。。