素材巴巴 > 程序开发 >

Android P输入法框架系统--view绑定输入法过程

程序开发 2023-09-14 12:41:33

1、概述

     IMMS如何处理view绑定输入法事件呢?为了讲解整个绑定过程,我们假设此时输入法service还没启动,这个情况下的输入法绑定是最长的,整个过程经历过如下过程:

  1. 启动输入法service
  2. 绑定输入法window的token
  3. 请求输入法为焦点程序创建一个连接会话
  4. 将输入法的接口传递回程序client端
  5. 绑定输入法和焦点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);//启动输入法servicemCurIntent = 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可以很方便的直接管理输入法的windowmCurToken = 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接口@Overridefinal public IBinder onBind(Intent intent) {if (mInputMethod == null) {mInputMethod = onCreateInputMethodInterface();}// IInputMethodWrapper只是一个wrapper,它负责将IMMS的调用转化为message//然后在message线程再调用mInputMethod对应的接口 //这样输入法的处理就是异步的了,因此你说它就是mInputMethodreturn new IInputMethodWrapper(this, mInputMethod);}@Overridepublic AbstractInputMethodImpl onCreateInputMethodInterface() {return new InputMethodImpl();}//由于IMMS是以bindService的方式启动输入法service,所以当输入法service启动完//成后它就会回调IMMS的onServiceConnected@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {synchronized (mMethodMap) {if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {//保存输入法service传递过来的通信接口IInputMethodmCurMethod = 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@Overridepublic 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) {//保存tokenmToken = token;//这样输入法的window就绑定这个window tokenmWindow.setToken(token);}}}
 

3.2 输入法Window token使用

由于系统存在多个输入法,所以输入法要和IMMS通信,必须要个机制来标示自己是哪个输入法,这个就是通过上面的输入法Window token来实现的,比如输入法自己关闭自己:

    //InputMethodService.java输入法接口public void requestHideSelf(int flags) {//mToken就是上面提到的过程----IMMS传递给输入法的mImm.hideSoftInputFromInputMethod(mToken, flags);}//InputMethodManager.javapublic void hideSoftInputFromInputMethod(IBinder token, int flags) {try {mService.hideMySoftInput(token, flags);} catch (RemoteException e) {throw new RuntimeException(e);}}//InputMethodManagerService.java@Overridepublic 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@Overridepublic 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.javapublic abstract class AbstractInputMethodService extends Serviceimplements 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来显示输入法。未完待续。。。


标签:

素材巴巴 Copyright © 2013-2021 http://www.sucaibaba.com/. Some Rights Reserved. 备案号:备案中。