美文网首页Android开发Android开发经验谈Android技术知识
输入法框架(InputMethod Framework)原理分析

输入法框架(InputMethod Framework)原理分析

作者: 小芸论 | 来源:发表于2022-11-05 22:06 被阅读0次

    1 概述

    首先看一下输入法架构图:

    根据上面的架构图可以将输入法交互流程概况成如下几步:

    1>ClientApp启动时 IMM(InputMethodManager的简称) 被创建并且获取IMMS(InputMethodManagerService的简称)的代理对象(实现了IInputMethodManager接口)

    2> IMMS 绑定输入法服务IMS(InputMethodService的简称), 得到 IInputMethod

    3> IMMS 通过 IInputMethod 请求IMS创建交互 IInputMethodSession,然后通过 IInputMethodClient 告知 IMM IInputMethodSession

    4> IMM通过IInputMethodManager请求IMMS将IInputContext交给IMS (IMMS通过IInputMethod将IInputContext传递给IMS)

    5> IMM 和 IMS 通过 IInputMethodSession 和 IInputContext 交互

    接下来看一下IInputMethodManager、IInputMethodClient、IInputMethod、IInputMethodSession 和 IInputContext 提供的能力(即IMM、IMMS 和 ClientApp 之间是怎么交互的),有注释的方法是我熟悉的方法,分析源码切忌不要过于纠结细节,下面的方法大家粗略看看就行:

    interface IInputMethodManager {
        // TODO: Use ParceledListSlice instead
        List<InputMethodInfo> getInputMethodList();
        List<InputMethodInfo> getVrInputMethodList();
        // 获取所有可用的输入法信息列表,对应于 adb shell ime list -stead
        List<InputMethodInfo> getEnabledInputMethodList();
        List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId,
                boolean allowsImplicitlySelectedSubtypes);
        InputMethodSubtype getLastInputMethodSubtype();
        // TODO: We should change the return type from List to List<Parcelable>
        // Currently there is a bug that aidl doesn't accept List<Parcelable>
        List getShortcutInputMethodsAndSubtypes();
        // 向IMMS中添加IMM的代理对象(IInputMethodClient), IInputMethodClient.aidl中列举了IMM提供给IMMS的能力
        void addClient(in IInputMethodClient client,
                in IInputContext inputContext, int uid, int pid);
        // 从IMMS中移除IMM的代理对象
        void removeClient(in IInputMethodClient client);
    
        // 结束语当前的IMS输入通信
        void finishInput(in IInputMethodClient client);
        // 弹出当前的键盘
        boolean showSoftInput(in IInputMethodClient client, int flags,
                in ResultReceiver resultReceiver);
        // 隐藏当前的键盘
        boolean hideSoftInput(in IInputMethodClient client, int flags,
                in ResultReceiver resultReceiver);
        // 开启与当前的IMS输入通信,在ClientApp中EditText获取焦点时会被调用
        // If windowToken is null, this just does startInput().  Otherwise this reports that a window
        // has gained focus, and if 'attribute' is non-null then also does startInput.
        // @NonNull
        InputBindResult startInputOrWindowGainedFocus(
                /* @InputMethodClient.StartInputReason */ int startInputReason,
                in IInputMethodClient client, in IBinder windowToken, int controlFlags,
                /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
                int windowFlags, in EditorInfo attribute, IInputContext inputContext,
                /* @InputConnectionInspector.MissingMethodFlags */ int missingMethodFlags,
                int unverifiedTargetSdkVersion);
        // 显示切换输入法的弹框
        void showInputMethodPickerFromClient(in IInputMethodClient client,
                int auxiliarySubtypeMode);
        void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
        boolean isInputMethodPickerShownForTest();
        void setInputMethod(in IBinder token, String id);
        void setInputMethodAndSubtype(in IBinder token, String id, in InputMethodSubtype subtype);
        void hideMySoftInput(in IBinder token, int flags);
        void showMySoftInput(in IBinder token, int flags);
        void updateStatusIcon(in IBinder token, String packageName, int iconId);
        void setImeWindowStatus(in IBinder token, in IBinder startInputToken, int vis,
                int backDisposition);
        void registerSuggestionSpansForNotification(in SuggestionSpan[] spans);
        boolean notifySuggestionPicked(in SuggestionSpan span, String originalString, int index);
        InputMethodSubtype getCurrentInputMethodSubtype();
        boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype);
        boolean switchToPreviousInputMethod(in IBinder token);
        boolean switchToNextInputMethod(in IBinder token, boolean onlyCurrentIme);
        boolean shouldOfferSwitchingToNextInputMethod(in IBinder token);
        void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
        int getInputMethodWindowVisibleHeight();
        void clearLastInputMethodWindowForTransition(in IBinder token);
    
        IInputContentUriToken createInputContentUriToken(in IBinder token, in Uri contentUri,
                in String packageName);
    
        void reportFullscreenMode(in IBinder token, boolean fullscreen);
    
        oneway void notifyUserAction(int sequenceNumber);
    }
    
    oneway interface IInputMethodClient {
        void setUsingInputMethod(boolean state);
        // IMMS 通过 IInputMethod 请求IMS创建 IInputMethodSession,最终就是通过这个方法传递给IMM
        void onBindMethod(in InputBindResult res);
        // unbindReason corresponds to InputMethodClient.UnbindReason.
        void onUnbindMethod(int sequence, int unbindReason);
        void setActive(boolean active, boolean fullscreen);
        void setUserActionNotificationSequenceNumber(int sequenceNumber);
        void reportFullscreenMode(boolean fullscreen);
    }
    
    oneway interface IInputMethod {
        // 传递window token给IMS,IMS使用该token创建输入法Window
        void attachToken(IBinder token);
        // 绑定输入法
        void bindInput(in InputBinding binding);
        // 解绑输入法
        void unbindInput();
        // 建立ClientApp与IMS之间输入通信
        void startInput(in IBinder startInputToken, in IInputContext inputContext, int missingMethods,
                in EditorInfo attribute, boolean restarting);
        // 创建用于IMMS调用IMS能力的会话IInputMethodSession
        void createSession(in InputChannel channel, IInputSessionCallback callback);
        // 设置指定会话是否可用
        void setSessionEnabled(IInputMethodSession session, boolean enabled);
        void revokeSession(IInputMethodSession session);
        // 通知IMS显示输入法
        void showSoftInput(int flags, in ResultReceiver resultReceiver);
        // 通知IMS隐藏输入法
        void hideSoftInput(int flags, in ResultReceiver resultReceiver);
        // 切换到指定输入法
        void changeInputMethodSubtype(in InputMethodSubtype subtype);
    }
    
    oneway interface IInputMethodSession {
        // 结束ClientApp与IMS的输入通信
        void finishInput();
        void updateExtractedText(int token, in ExtractedText text);
        // 通知IMS更新选中的范围,最终会调用IMS的onUpdateSelection方法    
        void updateSelection(int oldSelStart, int oldSelEnd,
                int newSelStart, int newSelEnd,
                int candidatesStart, int candidatesEnd);
        // 点击ClientApp中EditText时调用,最终会调用IMS的onViewClicked方法
        void viewClicked(boolean focusChanged);
        // 通知IMS更新光标,最终会调用IMS的onUpdateCursor方法
        void updateCursor(in Rect newCursor);
        void displayCompletions(in CompletionInfo[] completions);
        void appPrivateCommand(String action, in Bundle data);
        // 切换输入法(显示或者隐藏),最终会调用IMS的onToggleSoftInput方法
        void toggleSoftInput(int showFlags, int hideFlags);
        // 结束ClientApp与IMS之间的会话
        void finishSession();
        void updateCursorAnchorInfo(in CursorAnchorInfo cursorAnchorInfo);
    }
    
    oneway interface IInputContext {
        // 获取光标前的内容
        void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback); 
        // 获取光标后的内容
        void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback);
        void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback);
        void getExtractedText(in ExtractedTextRequest request, int flags, int seq,
                IInputContextCallback callback);
        // 删除光标前面beforeLength长度和后面afterLength长度的字符串
        void deleteSurroundingText(int beforeLength, int afterLength);
        void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
        // 设置预上屏的文本
        void setComposingText(CharSequence text, int newCursorPosition);
        // 将预上屏的文本上屏
        void finishComposingText();
        // 上屏
        void commitText(CharSequence text, int newCursorPosition);
        void commitCompletion(in CompletionInfo completion);
        void commitCorrection(in CorrectionInfo correction);
        // 选中指定范围的字符串
        void setSelection(int start, int end);
        // 向ClientApp中的EditText发出指定Action,比如IME_ACTION_GO、IME_ACTION_SEARCH、IME_ACTION_SEND 等等
        void performEditorAction(int actionCode);
        void performContextMenuAction(int id);
        void beginBatchEdit();
        void endBatchEdit();
        // 发送事件给ClientApp中的EditText,比如物理键盘或者遥控器的按键事件
        void sendKeyEvent(in KeyEvent event);
        void clearMetaKeyStates(int states);
        void performPrivateCommand(String action, in Bundle data);
        void setComposingRegion(int start, int end);
        // 获取选中的字符串
        void getSelectedText(int flags, int seq, IInputContextCallback callback);
        void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
                IInputContextCallback callback);
        void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts, int sec,
                IInputContextCallback callback);
    }
    

    对于输入法APP开发者而言,可以用到IInputMethodManager(对应于InputMethodManager)、IInputMethod(对应于InputMethodService#InputMethodImpl)、IInputContext(对应于InputMethodService.getCurrentInputConnection()) 提供的能力。

    接下来就是对上面的输入法交互流程进行源码分析,直到第2节结束都是源码分析,重要的事情再说一遍,切忌不要太纠结于源码的细节,知道整体的架构和交互流程才是重要的。

    2 源码分析

    2.1 IMMS的启动

    // SystemServer.java
    
    public static void main(String[] args) {
        new SystemServer().run();
    }
    
    private void run() {
    ...
        traceBeginAndSlog("StartServices");
        startBootstrapServices();
        startCoreServices();
        startOtherServices();
        SystemServerInitThreadPool.shutdown();
    ...
    }
    private void startOtherServices() {
      ...
      mSystemServiceManager.startService(InputMethodManagerService.Lifecycle.class);
      ...
      mActivityManagerService.systemReady(() -> {
        mSystemServiceManager.startBootPhase(
                SystemService.PHASE_ACTIVITY_MANAGER_READY);
        ...
      }
    }
    
    // InputMethodManagerService.Lifecycle
    
    public static final class Lifecycle extends SystemService {
        private final InputMethodManagerService mService;
    
        public Lifecycle(Context context) {
            super(context);
            mService = new InputMethodManagerService(context);
        }
    
        @Override
        public void onStart() {
            LocalServices.addService(InputMethodManagerInternal.class,
                    new LocalServiceImpl(mService.mHandler));
            publishBinderService(Context.INPUT_METHOD_SERVICE, mService);
        }
    
        @Override
        public void onBootPhase(int phase) {
            // Called on ActivityManager thread.
            // TODO: Dispatch this to a worker thread as needed.
            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
                StatusBarManagerService statusBarService = (StatusBarManagerService) ServiceManager
                        .getService(Context.STATUS_BAR_SERVICE);
                mService.systemRunning(statusBarService);
            }
        }
    }
    
    // SystemService.java
    
    protected final void publishBinderService(String name, IBinder service,
            boolean allowIsolated, int dumpPriority) {
        ServiceManager.addService(name, service, allowIsolated, dumpPriority);
    }
    

    可以看到Android系统启动时就会启动IMMS服务并且将其放到了ServiceManager中。

    2.2 IMS的启动

    首先看一下流程图:

    2.2.1 前4步完成了通过bindService的方式启动IMS服务:

    // InputMethodManagerService.java
    
    // 第1步
    public void systemRunning(StatusBarManagerService statusBar) {
        synchronized (mMethodMap) {
            if (!mSystemReady) {
                mSystemReady = true;
                ...
                try {
                    startInputInnerLocked();
                } catch (RuntimeException e) {
                    Slog.w(TAG, "Unexpected exception", e);
                }
            }
        }
    }
    // 第2步
    InputBindResult startInputInnerLocked() {
        ...
        InputMethodInfo info = mMethodMap.get(mCurMethodId);
        if (info == null) {
            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
        }
    
        unbindCurrentMethodLocked(true);
    
        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));
        // 第3步
        if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
            ...
        }
        mCurIntent = null;
        Slog.w(TAG, "Failure connecting to input method service: " + mCurIntent);
        return InputBindResult.IME_NOT_CONNECTED;
    }
    

    第2步中mMethodMap 保存了设备上所有的输入法信息, key 就是你输入法的 ID ,可以通过命令获取所有启用的输入法ID列表:

    λ adb shell ime list -s
    com.huawei.ohos.inputmethod/com.android.inputmethod.latin.LatinIME
    com.baidu.input_huawei/.ImeService
    com.sohu.inputmethod.sogou/.SogouIME
    com.iflytek.inputmethod/.FlyIME
    

    可以看到设备上一共启用了4个输入法,可以通过下面的命令切换输入法

    λ adb shell ime set com.sohu.inputmethod.sogou/.SogouIME
    Input method com.sohu.inputmethod.sogou/.SogouIME selected for user #0
    

    接下来继续分析代码,InputMethod.SERVICE_INTERFACE 的值为android.view.InputMethod,是不是感觉很熟悉,IMS App中LatinIME的注册信息一般如下

    <service
        android:name="xxx.LatinIME"
        android:directBootAware="true"
        android:label="@string/english_ime_name"
        android:permission="android.permission.BIND_INPUT_METHOD">
        <intent-filter>
            <action android:name="android.view.InputMethod" />
        </intent-filter>
    
        <meta-data
            android:name="android.view.im"
            android:resource="@xml/method" />
    </service>
    

    LatinIME Service(继承自IMS)的Action就是InputMethod.SERVICE_INTERFACE,到这里大家应该明白了上面的bindCurrentInputMethodServiceLocked方法会启动IMS,也就是通过bindService的方式启动IMS服务。

    2.2.2 IMMS启动IMS服务成功后通过ServiceConnection接口持有IMS的代理对象(实现了IInputMethod接口,对应成员变量mCurMethod,5到7步):

    // InputMethodService.java
    
    @Override
    public void onCreate() {
        ...
        // Gravity.BOTTOM 说明了输入法显示在屏幕底部
        // WindowManager.LayoutParams.TYPE_INPUT_METHOD 说明了输入法会显示到其他
        // 页面上面,这个值是 2011 ,它是一个系统级的窗口,而应用窗口是1~99,
        // 子窗口是从1000~1999,数值大的会覆盖在数值小的上面,这是 Window 内部机制决定的,
        // 所以输入法UI会显示到其他页面上面
        mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
            WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
        // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set
        // by default (but IME developers can opt this out later if they want a new behavior).
        mWindow.getWindow().setFlags(
            FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        initViews();
        mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
    }
    
    // 第5步
    @Override
    final public IBinder onBind(Intent intent) {
        if (mInputMethod == null) {
            mInputMethod = onCreateInputMethodInterface();
        }
        // IInputMethodWrapper是binder类型对象,继承自IInputMethod.Stub
        // IMMS就是通过该返回对象调用IMS提供的能力
        return new IInputMethodWrapper(this, mInputMethod);
    }
    
    @Override
    public AbstractInputMethodImpl onCreateInputMethodInterface() {
        return new InputMethodImpl();
    }
    
    // InputMethodManagerService.java
    
    // 第6步
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        synchronized (mMethodMap) {
            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
                // mCurMethod为IMS的代理对象,即第5步中的IInputMethodWrapper对象
                mCurMethod = IInputMethod.Stub.asInterface(service);
                ...
                if (mCurClient != null) {
                    clearClientSessionLocked(mCurClient);
                    requestClientSessionLocked(mCurClient);
                }
            }
        }
    }
    

    2.3 IMM的创建

    首先看一下流程图:

    // ViewRootImpl.java
    
    第1步
    public ViewRootImpl(Context context, Display display) {  
     mContext = context;  
     mWindowSession = WindowManagerGlobal.getWindowSession();
    ......
    }
    
    // WindowManagerGlobal.java
    
    // 第2步
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    // 第3步
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    // 第5步
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
    
    // InputMethodManager.java
    
    第3步
    public static InputMethodManager getInstance() {
        synchronized (InputMethodManager.class) {
            if (sInstance == null) {
                try {
                    // 单例,每个app中只存在一个
                    sInstance = new InputMethodManager(Looper.getMainLooper());
                } catch (ServiceNotFoundException e) {
                    throw new IllegalStateException(e);
                }
            }
            return sInstance;
        }
    }
    
    InputMethodManager(Looper looper) throws ServiceNotFoundException {
        this(IInputMethodManager.Stub.asInterface(
                ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)), looper);
    }
    
    InputMethodManager(IInputMethodManager service, Looper looper) {
        mService = service;
        mMainLooper = looper;
        mH = new H(looper);
        // 第4步,创建ControlledInputConnectionWrapper对象(实现了IInputContext接口)
        mIInputContext = new ControlledInputConnectionWrapper(looper,
                mDummyInputConnection, this);
    }
    
    // WindowManagerService.java
    
    @Override
    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
            IInputContext inputContext) {
        if (client == null) throw new IllegalArgumentException("null client");
        if (inputContext == null) throw new IllegalArgumentException("null inputContext");
        Session session = new Session(this, callback, client, inputContext);
        return session;
    }
    
    // Session.java
    
    public Session(WindowManagerService service, IWindowSessionCallback callback,
            IInputMethodClient client, IInputContext inputContext) {
        mService = service;
        mCallback = callback;
        mClient = client;
        ...
        synchronized (mService.mWindowMap) {
            if (mService.mInputMethodManager == null && mService.mHaveInputMethods) {
                IBinder b = ServiceManager.getService(
                        Context.INPUT_METHOD_SERVICE);
                mService.mInputMethodManager = IInputMethodManager.Stub.asInterface(b);
            }
        }
        long ident = Binder.clearCallingIdentity();
        try {
            // Note: it is safe to call in to the input method manager
            // here because we are not holding our lock.
            if (mService.mInputMethodManager != null) {
                // 第7步,将ControlledInputConnectionWrapper对象(实现了IInputContext接口)传递给IMMS
                mService.mInputMethodManager.addClient(client, inputContext,
                        mUid, mPid);
            ...
    }
    

    前4步说明了ClientApp启动就会创建IMM实例并且获取持有了IMMS的代理对象(对应成员变量mService,实现了IInputMethodManager接口)。
    5到7步完成向IMMS传递了ControlledInputConnectionWrapper对象(保存于成员变量mClients中,实现了IInputContext接口)。

    3 EditText唤起输入法的流程

    显示输入法的前提就是ClientApp中EditText获取焦点,最终会调用到InputMethodManager的checkFocus方法,所以下面直接从该方法看起,首先全局的看一下流程图:

    3.1 IMM通过IInputMethodManager请求IMMS将IInputContext交给IMS (IMMS通过IInputMethod将IInputContext传递给IMS),1到12步

    接下来就看一下源代码:

    // InputMethodManager.java
    
    // 第1步
    public void checkFocus() {
        if (checkFocusNoStartInput(false)) {
            startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);
        }
    }
    
    // 第2步,开启ClientApp与IMS之间Input
    boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
            IBinder windowGainingFocus, int controlFlags, int softInputMode,
            int windowFlags) {
        ...
        // 第3步,创建IMS向ClientApp输出的InputConnection
        InputConnection ic = view.onCreateInputConnection(tba);
        if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
    
        synchronized (mH) {
            ...
            ControlledInputConnectionWrapper servedContext;
            final int missingMethodFlags;
            if (ic != null) {
                ...
                // ControlledInputConnectionWrapper实现了IInputContext.Stub,
                // 即ClientApp通过ControlledInputConnectionWrapper向IMS提供能力
                servedContext = new ControlledInputConnectionWrapper(
                        icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);
            } else {
                servedContext = null;
                missingMethodFlags = 0;
            }
            mServedInputConnectionWrapper = servedContext;
    
            try {
                if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
                        + ic + " tba=" + tba + " controlFlags=#"
                        + Integer.toHexString(controlFlags));
                // 第5步,调用IMMS的startInputOrWindowGainedFocus方法
                // 从而将ControlledInputConnectionWrapper传递给了IMMS
                final InputBindResult res = mService.startInputOrWindowGainedFocus(
                        startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
                        windowFlags, tba, servedContext, missingMethodFlags,
                        view.getContext().getApplicationInfo().targetSdkVersion);
                ...
            } catch (RemoteException e) {
                Log.w(TAG, "IME died: " + mCurId, e);
            }
        }
    
        return true;
    }
    
    // InputMethodManagerService.java
    
    @NonNull
    @Override
    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) {
            // 第6步
            result = windowGainedFocus(startInputReason, client, windowToken, controlFlags,
                    softInputMode, windowFlags, attribute, inputContext, missingMethods,
                    unverifiedTargetSdkVersion);
        }
        ...
        return result;
    }
    
    // 第7步
    @GuardedBy("mMethodMap")
    @NonNull
    InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
            /* @InputConnectionInspector.missingMethods */ final int missingMethods,
            @NonNull EditorInfo attribute, int controlFlags,
            /* @InputMethodClient.StartInputReason */ final int startInputReason) {
        ...
        mCurInputContext = inputContext;
        ...
        if (mCurId != null && mCurId.equals(mCurMethodId)) {
            if (cs.curSession != null) {
                // 第8步
                return attachNewInputLocked(startInputReason,
                        (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
            }
            if (mHaveConnection) {
                if (mCurMethod != null) {
                    // 第16步
                    requestClientSessionLocked(cs);
                    return new InputBindResult(
                            InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
                            null, null, mCurId, mCurSeq,
                            mCurUserActionNotificationSequenceNumber);
                }
                ...
            }
            ...
        }
        ...
    }
    
    @GuardedBy("mMethodMap")
    @NonNull
    InputBindResult attachNewInputLocked(
            /* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
        ...
        executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
                MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
                startInputToken, session, mCurInputContext, mCurAttribute));
        ...
        return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                session.session, (session.channel != null ? session.channel.dup() : null),
                mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
    }
    
    @MainThread
    @Override
    public boolean handleMessage(Message msg) {
        SomeArgs args;
        switch (msg.what) {
            ...
            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);
                    // 第9步,调用IInputMethodWrapper的startInput方法,从而将
                    // ControlledInputConnectionWrapper(实现了IInputMethodSession接口)传递给了IMS,这样IMS就可以
                    // 向ClientApp发起跨进程的调用
                    session.method.startInput(startInputToken, inputContext, missingMethods,
                            editorInfo, restarting);
                } catch (RemoteException e) {
                }
                args.recycle();
                return true;
            }
            ...
        }
        return false;
    }
    

    3.2 IMMS 通过 IInputMethod 请求IMS创建交互 IInputMethodSession 然后 通过 IInputMethodClient 告知 IMM IInputMethodSession(16到23步)

    // InputMethodManagerService.java
    
    // 第16步
    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;
            // 创建IInputMethodSessionWrapper对象(实现了IInputMethodSession接口),创建成功后调用
            // MethodCallback.onSessionCreated方法
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
                    MSG_CREATE_SESSION, mCurMethod, channels[1],
                    new MethodCallback(this, mCurMethod, channels[0])));
        }
    }
    
    @Override
    public boolean handleMessage(Message msg) {
        SomeArgs args;
        switch (msg.what) {
            ...
            case MSG_CREATE_SESSION: {
                args = (SomeArgs)msg.obj;
                IInputMethod method = (IInputMethod)args.arg1;
                InputChannel channel = (InputChannel)args.arg2;
                try {
                    // 第17步,调用IInputMethodWrapper的createSession方法
                    method.createSession(channel, (IInputSessionCallback)args.arg3);
                ...
            }
            ...
            case MSG_BIND_CLIENT: {
                args = (SomeArgs)msg.obj;
                IInputMethodClient client = (IInputMethodClient)args.arg1;
                InputBindResult res = (InputBindResult)args.arg2;
                try {
                    // 第22步,调用client.onBindMethod方法将IInputMethodSessionWrapper对象(实现了IInputMethodSession接口)传递给IMM(对应成员变量mCurMethod)
                    client.onBindMethod(res);
                ...
    }
    
    // 21步
    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为ClientApp调用IMS能力的会话
                    mCurClient.curSession = new SessionState(mCurClient,
                            method, session, channel);
                    InputBindResult res = attachNewInputLocked(
                            InputMethodClient.START_INPUT_REASON_SESSION_CREATED_BY_IME, true);
                    if (res.method != null) {
                        // 将session传递给IMM
                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
                                MSG_BIND_CLIENT, mCurClient.client, res));
                    }
                    return;
                }
            }
        }
    
        // Session abandoned.  Close its associated input channel.
        channel.dispose();
    }
    
    // InputMethodManagerService#MethodCallback
    
    private static final class MethodCallback extends IInputSessionCallback.Stub {
        private final InputMethodManagerService mParentIMMS;
        private final IInputMethod mMethod;
        private final InputChannel mChannel;
    
        MethodCallback(InputMethodManagerService imms, IInputMethod method,
                InputChannel channel) {
            mParentIMMS = imms;
            mMethod = method;
            mChannel = channel;
        }
    
        // 第20步
        @Override
        public void sessionCreated(IInputMethodSession session) {
            long ident = Binder.clearCallingIdentity();
            try {
              // 第21步,会话创建成功后调用IMMS的onSessionCreated方法
              mParentIMMS.onSessionCreated(mMethod, session, mChannel);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }
    
    // IInputMethodWrapper.java
    
    // 第17步
    @BinderThread
    @Override
    public void createSession(InputChannel channel, IInputSessionCallback callback) {
        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
                channel, callback));
    }
    
    @MainThread
    @Override
    public void executeMessage(Message msg) {
            ...
            case DO_CREATE_SESSION: {
                SomeArgs args = (SomeArgs)msg.obj;
                // 第18步,调用InputMethodImpl的createSession方法
                inputMethod.createSession(new InputMethodSessionCallbackWrapper(
                        mContext, (InputChannel)args.arg1,
                        (IInputSessionCallback)args.arg2));
                args.recycle();
                return;
            }
            ...
    }
    
    // InputMethodService#InputMethodImpl
    
    // 第18步
    @MainThread
    public void createSession(SessionCallback callback) {
        // 第19步
        callback.sessionCreated(onCreateInputMethodSessionInterface());
    }
    
    // InputMethodSessionCallbackWrapper.java
    
    @Override
    public void sessionCreated(InputMethodSession session) {
        try {
            if (session != null) {
                IInputMethodSessionWrapper wrap =
                        new IInputMethodSessionWrapper(mContext, session, mChannel);
                // 第20步,跨进程调用到IMMS中的MethodCallback.sessionCreated方法
                mCb.sessionCreated(wrap);
            } else {
                if (mChannel != null) {
                    mChannel.dispose();
                }
                mCb.sessionCreated(null);
            }
        } catch (RemoteException e) {
        }
    }
    
    // IInputMethodSessionWrapper.java
    
    class IInputMethodSessionWrapper extends IInputMethodSession.Stub
            implements HandlerCaller.Callback {
        ...
    }
    

    相关文章

      网友评论

        本文标题:输入法框架(InputMethod Framework)原理分析

        本文链接:https://www.haomeiwen.com/subject/fpmptdtx.html