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

1 概述

首先看一下輸入法架構(gòu)圖:

根據(jù)上面的架構(gòu)圖可以將輸入法交互流程概況成如下幾步:

1>ClientApp啟動(dòng)時(shí) IMM(InputMethodManager的簡(jiǎn)稱) 被創(chuàng)建并且獲取IMMS(InputMethodManagerService的簡(jiǎn)稱)的代理對(duì)象(實(shí)現(xiàn)了IInputMethodManager接口)

2> IMMS 綁定輸入法服務(wù)IMS(InputMethodService的簡(jiǎn)稱), 得到 IInputMethod

3> IMMS 通過 IInputMethod 請(qǐng)求IMS創(chuàng)建交互 IInputMethodSession善炫,然后通過 IInputMethodClient 告知 IMM IInputMethodSession

4> IMM通過IInputMethodManager請(qǐng)求IMMS將IInputContext交給IMS (IMMS通過IInputMethod將IInputContext傳遞給IMS)

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

接下來看一下IInputMethodManager绳姨、IInputMethodClient、IInputMethod渠旁、IInputMethodSession 和 IInputContext 提供的能力(即IMM镀层、IMMS 和 ClientApp 之間是怎么交互的)镰禾,有注釋的方法是我熟悉的方法,分析源碼切忌不要過于糾結(jié)細(xì)節(jié),下面的方法大家粗略看看就行:

interface IInputMethodManager {
    // TODO: Use ParceledListSlice instead
    List<InputMethodInfo> getInputMethodList();
    List<InputMethodInfo> getVrInputMethodList();
    // 獲取所有可用的輸入法信息列表吴侦,對(duì)應(yīng)于 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的代理對(duì)象(IInputMethodClient), IInputMethodClient.aidl中列舉了IMM提供給IMMS的能力
    void addClient(in IInputMethodClient client,
            in IInputContext inputContext, int uid, int pid);
    // 從IMMS中移除IMM的代理對(duì)象
    void removeClient(in IInputMethodClient client);

    // 結(jié)束語當(dāng)前的IMS輸入通信
    void finishInput(in IInputMethodClient client);
    // 彈出當(dāng)前的鍵盤
    boolean showSoftInput(in IInputMethodClient client, int flags,
            in ResultReceiver resultReceiver);
    // 隱藏當(dāng)前的鍵盤
    boolean hideSoftInput(in IInputMethodClient client, int flags,
            in ResultReceiver resultReceiver);
    // 開啟與當(dāng)前的IMS輸入通信谷饿,在ClientApp中EditText獲取焦點(diǎn)時(shí)會(huì)被調(diào)用
    // 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 請(qǐng)求IMS創(chuàng)建 IInputMethodSession,最終就是通過這個(gè)方法傳遞給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創(chuàng)建輸入法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);
    // 創(chuàng)建用于IMMS調(diào)用IMS能力的會(huì)話IInputMethodSession
    void createSession(in InputChannel channel, IInputSessionCallback callback);
    // 設(shè)置指定會(huì)話是否可用
    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 {
    // 結(jié)束ClientApp與IMS的輸入通信
    void finishInput();
    void updateExtractedText(int token, in ExtractedText text);
    // 通知IMS更新選中的范圍,最終會(huì)調(diào)用IMS的onUpdateSelection方法    
    void updateSelection(int oldSelStart, int oldSelEnd,
            int newSelStart, int newSelEnd,
            int candidatesStart, int candidatesEnd);
    // 點(diǎn)擊ClientApp中EditText時(shí)調(diào)用绸贡,最終會(huì)調(diào)用IMS的onViewClicked方法
    void viewClicked(boolean focusChanged);
    // 通知IMS更新光標(biāo)盯蝴,最終會(huì)調(diào)用IMS的onUpdateCursor方法
    void updateCursor(in Rect newCursor);
    void displayCompletions(in CompletionInfo[] completions);
    void appPrivateCommand(String action, in Bundle data);
    // 切換輸入法(顯示或者隱藏),最終會(huì)調(diào)用IMS的onToggleSoftInput方法
    void toggleSoftInput(int showFlags, int hideFlags);
    // 結(jié)束ClientApp與IMS之間的會(huì)話
    void finishSession();
    void updateCursorAnchorInfo(in CursorAnchorInfo cursorAnchorInfo);
}
oneway interface IInputContext {
    // 獲取光標(biāo)前的內(nèi)容
    void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback); 
    // 獲取光標(biāo)后的內(nèi)容
    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);
    // 刪除光標(biāo)前面beforeLength長(zhǎng)度和后面afterLength長(zhǎng)度的字符串
    void deleteSurroundingText(int beforeLength, int afterLength);
    void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
    // 設(shè)置預(yù)上屏的文本
    void setComposingText(CharSequence text, int newCursorPosition);
    // 將預(yù)上屏的文本上屏
    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發(fā)出指定Action听怕,比如IME_ACTION_GO捧挺、IME_ACTION_SEARCH、IME_ACTION_SEND 等等
    void performEditorAction(int actionCode);
    void performContextMenuAction(int id);
    void beginBatchEdit();
    void endBatchEdit();
    // 發(fā)送事件給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);
}

對(duì)于輸入法APP開發(fā)者而言闽烙,可以用到IInputMethodManager(對(duì)應(yīng)于InputMethodManager)、IInputMethod(對(duì)應(yīng)于InputMethodService#InputMethodImpl)声搁、IInputContext(對(duì)應(yīng)于InputMethodService.getCurrentInputConnection()) 提供的能力黑竞。

接下來就是對(duì)上面的輸入法交互流程進(jìn)行源碼分析,直到第2節(jié)結(jié)束都是源碼分析疏旨,重要的事情再說一遍很魂,切忌不要太糾結(jié)于源碼的細(xì)節(jié),知道整體的架構(gòu)和交互流程才是重要的檐涝。

2 源碼分析

2.1 IMMS的啟動(dòng)

// 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系統(tǒng)啟動(dòng)時(shí)就會(huì)啟動(dòng)IMMS服務(wù)并且將其放到了ServiceManager中遏匆。

2.2 IMS的啟動(dòng)

首先看一下流程圖:

2.2.1 前4步完成了通過bindService的方式啟動(dòng)IMS服務(wù):

// 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 保存了設(shè)備上所有的輸入法信息, 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

可以看到設(shè)備上一共啟用了4個(gè)輸入法幅聘,可以通過下面的命令切換輸入法

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

接下來繼續(xù)分析代碼,InputMethod.SERVICE_INTERFACE 的值為android.view.InputMethod窃植,是不是感覺很熟悉帝蒿,IMS App中LatinIME的注冊(cè)信息一般如下

<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,到這里大家應(yīng)該明白了上面的bindCurrentInputMethodServiceLocked方法會(huì)啟動(dòng)IMS撕瞧,也就是通過bindService的方式啟動(dòng)IMS服務(wù)陵叽。

2.2.2 IMMS啟動(dòng)IMS服務(wù)成功后通過ServiceConnection接口持有IMS的代理對(duì)象(實(shí)現(xiàn)了IInputMethod接口,對(duì)應(yīng)成員變量mCurMethod丛版,5到7步):

// InputMethodService.java

@Override
public void onCreate() {
    ...
    // Gravity.BOTTOM 說明了輸入法顯示在屏幕底部
    // WindowManager.LayoutParams.TYPE_INPUT_METHOD 說明了輸入法會(huì)顯示到其他
    // 頁面上面巩掺,這個(gè)值是 2011 ,它是一個(gè)系統(tǒng)級(jí)的窗口页畦,而應(yīng)用窗口是1~99胖替,
    // 子窗口是從1000~1999,數(shù)值大的會(huì)覆蓋在數(shù)值小的上面,這是 Window 內(nèi)部機(jī)制決定的,
    // 所以輸入法UI會(huì)顯示到其他頁面上面
    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類型對(duì)象独令,繼承自IInputMethod.Stub
    // IMMS就是通過該返回對(duì)象調(diào)用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的代理對(duì)象,即第5步中的IInputMethodWrapper對(duì)象
            mCurMethod = IInputMethod.Stub.asInterface(service);
            ...
            if (mCurClient != null) {
                clearClientSessionLocked(mCurClient);
                requestClientSessionLocked(mCurClient);
            }
        }
    }
}

2.3 IMM的創(chuàng)建

首先看一下流程圖:

// 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 {
                // 單例端朵,每個(gè)app中只存在一個(gè)
                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步,創(chuàng)建ControlledInputConnectionWrapper對(duì)象(實(shí)現(xiàn)了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對(duì)象(實(shí)現(xiàn)了IInputContext接口)傳遞給IMMS
            mService.mInputMethodManager.addClient(client, inputContext,
                    mUid, mPid);
        ...
}

前4步說明了ClientApp啟動(dòng)就會(huì)創(chuàng)建IMM實(shí)例并且獲取持有了IMMS的代理對(duì)象(對(duì)應(yīng)成員變量mService冲呢,實(shí)現(xiàn)了IInputMethodManager接口)。
5到7步完成向IMMS傳遞了ControlledInputConnectionWrapper對(duì)象(保存于成員變量mClients中招狸,實(shí)現(xiàn)了IInputContext接口)敬拓。

3 EditText喚起輸入法的流程

顯示輸入法的前提就是ClientApp中EditText獲取焦點(diǎn),最終會(huì)調(diào)用到InputMethodManager的checkFocus方法裙戏,所以下面直接從該方法看起乘凸,首先全局的看一下流程圖:

3.1 IMM通過IInputMethodManager請(qǐng)求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步营勤,創(chuàng)建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實(shí)現(xiàn)了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步壹罚,調(diào)用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步葛作,調(diào)用IInputMethodWrapper的startInput方法,從而將
                // ControlledInputConnectionWrapper(實(shí)現(xiàn)了IInputMethodSession接口)傳遞給了IMS渔嚷,這樣IMS就可以
                // 向ClientApp發(fā)起跨進(jìn)程的調(diào)用
                session.method.startInput(startInputToken, inputContext, missingMethods,
                        editorInfo, restarting);
            } catch (RemoteException e) {
            }
            args.recycle();
            return true;
        }
        ...
    }
    return false;
}

3.2 IMMS 通過 IInputMethod 請(qǐng)求IMS創(chuàng)建交互 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;
        // 創(chuàng)建IInputMethodSessionWrapper對(duì)象(實(shí)現(xiàn)了IInputMethodSession接口)进鸠,創(chuàng)建成功后調(diào)用
        // 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步,調(diào)用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步形病,調(diào)用client.onBindMethod方法將IInputMethodSessionWrapper對(duì)象(實(shí)現(xiàn)了IInputMethodSession接口)傳遞給IMM(對(duì)應(yīng)成員變量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調(diào)用IMS能力的會(huì)話
                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步客年,會(huì)話創(chuàng)建成功后調(diào)用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步,調(diào)用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步,跨進(jìn)程調(diào)用到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 {
    ...
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末漠吻,一起剝皮案震驚了整個(gè)濱河市量瓜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌途乃,老刑警劉巖绍傲,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異耍共,居然都是意外死亡烫饼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門试读,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杠纵,“玉大人,你說我怎么就攤上這事钩骇”仍澹” “怎么了铝量?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)银亲。 經(jīng)常有香客問我慢叨,道長(zhǎng),這世上最難降的妖魔是什么务蝠? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任拍谐,我火速辦了婚禮,結(jié)果婚禮上馏段,老公的妹妹穿的比我還像新娘赠尾。我一直安慰自己,他們只是感情好毅弧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著当窗,像睡著了一般够坐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上崖面,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天元咙,我揣著相機(jī)與錄音,去河邊找鬼巫员。 笑死庶香,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的简识。 我是一名探鬼主播赶掖,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼七扰!你這毒婦竟也來了奢赂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤颈走,失蹤者是張志新(化名)和其女友劉穎膳灶,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體立由,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡轧钓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锐膜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毕箍。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖枣耀,靈堂內(nèi)的尸體忽然破棺而出霉晕,到底是詐尸還是另有隱情庭再,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布牺堰,位于F島的核電站拄轻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏伟葫。R本人自食惡果不足惜恨搓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望筏养。 院中可真熱鬧斧抱,春花似錦、人聲如沸渐溶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茎辐。三九已至宪郊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拖陆,已是汗流浹背弛槐。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留依啰,地道東北人乎串。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像速警,于是被迫代替她去往敵國(guó)和親叹誉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容