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

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);
 
        //启动输入法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来显示输入法。未完待续。。。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值