Android的輸入法系統(tǒng)框架原理

[TOC]

概述

image.png

我們從以下幾個(gè)點(diǎn)看這張圖:

  • 兩個(gè)過程:
    • 按鍵消息,由客戶端進(jìn)程接收,如果客戶端進(jìn)程判斷當(dāng)前有輸入法窗口,則需要跨進(jìn)程轉(zhuǎn)交給InputMethod進(jìn)程
    • 觸屏消息(觸摸在輸入法窗口中),由輸入法處理,結(jié)束后把結(jié)果跨進(jìn)程提交給客戶端進(jìn)程
  • 四個(gè)binder:
    • IInputContext:負(fù)責(zé)InputMethod進(jìn)程和應(yīng)用進(jìn)程的編輯框的通信,如上屏、查詢光標(biāo)前后字符等
    • IInputMethodClient:IMMS使用該接口查找和IMS對應(yīng)的客戶端應(yīng)用進(jìn)程,并通知應(yīng)用進(jìn)程綁定/解綁輸入法。
    • IInputMethodSession:定義了客戶端可以調(diào)用輸入法的相關(guān)方法,如updateCursor, finishInput等
    • IInputMethod:負(fù)責(zé)IMS和IMMS的通信,IMMS通知IMS進(jìn)行startInput,bindInput等生命周期操作。

各個(gè)Binder的創(chuàng)建過程

Binder1: InputConnection

這個(gè)binder是編輯框做Server端,輸入法進(jìn)程做Client端,主要作用是負(fù)責(zé)InputMethod進(jìn)程和應(yīng)用進(jìn)程的編輯框的通信,如上屏、查詢光標(biāo)前后字符等。下面一張圖顯示的是InputConnection模塊的類圖。IInputContext.aidl表征IMS進(jìn)程和ClientApp進(jìn)程的通信協(xié)議,兩邊的實(shí)現(xiàn)類分別是IInputConnectionWrapper(Server)和InputConnectionWrapper(Client);另一方面,兩者都會(huì)在內(nèi)部持有一個(gè)InputConnection對象,因此我們也使用InputConnection這個(gè)接口來表征兩者的通信。

InputConnection結(jié)構(gòu)圖

每個(gè)應(yīng)用程序進(jìn)程都運(yùn)行著對應(yīng)的InputMethodMananger(IMM),IMM負(fù)責(zé)應(yīng)用進(jìn)程和IMMS的通信。應(yīng)用程序和IMS進(jìn)程的InputConnection連接正是借助于IMM和IMMS,在應(yīng)用程序的窗口獲得焦點(diǎn)時(shí)建立的。入口在InputMethodManager.startInputInner()中:

// InputMethodManager.startInputInner(這個(gè)方法是當(dāng)前應(yīng)用程序的窗口的focus狀態(tài)改變的時(shí)候會(huì)調(diào)用)
boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode, int windowFlags) {
    view = mServedView;
    ...
    ...
    InputConnection ic = view.onCreateInputConnection(tba); // 回調(diào)當(dāng)前焦點(diǎn)View的方法來創(chuàng)建inputConnection

    synchronized (mH) {
        ...
        ControlledInputConnectionWrapper servedContext;
        if (ic != null) {
            ...
            servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this); // 用這個(gè)InputConnection創(chuàng)建wrapper
        } else {
            servedContext = null;
        }

        try {
            if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic="
                    + ic + " tba=" + tba + " controlFlags=#"
                    + Integer.toHexString(controlFlags));
            InputBindResult res;
            if (windowGainingFocus != null) {
                res = mService.windowGainedFocus(mClient, windowGainingFocus,
                        controlFlags, softInputMode, windowFlags,
                        tba, servedContext); // 這個(gè)方法更新IMMS中關(guān)于焦點(diǎn)窗口的一些信息,如果需要的話,會(huì)啟動(dòng)輸入法,所以這個(gè)wrapper要傳進(jìn)去
            } else {
                res = mService.startInput(mClient,
                        servedContext, tba, controlFlags); // 調(diào)用IMMS的startInput方法,將wrapper對象傳過去**
            }
            ...
        } catch (RemoteException e) {
            ...
        }
    }

    return true;
}

BINDER2: InputMethodClient

IMMS查找和IMS對應(yīng)的客戶端應(yīng)用進(jìn)程,并負(fù)責(zé)通知應(yīng)用進(jìn)程綁定/解綁輸入法。

oneway interface IInputMethodClient {
    void setUsingInputMethod(boolean state);
    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);
}

類圖如下:


Paste_Image.png

根據(jù)圖中的示意,這個(gè)binder其實(shí)相當(dāng)于一個(gè)媒介binder。其中,服務(wù)端對象mClient會(huì)跟隨IMM的創(chuàng)建而創(chuàng)建。而客戶端的對象其實(shí)在IMMS的一個(gè)map中。其實(shí)當(dāng)系統(tǒng)認(rèn)為一個(gè)客戶進(jìn)程(應(yīng)用程序)如果有可能會(huì)需要使用輸入法,就會(huì)把它注冊到IMMS中,以便后續(xù)誰需要輸入法服務(wù)的時(shí)候,IMMS可以找到對應(yīng)的客戶進(jìn)程,那么什么時(shí)候它可能會(huì)使用輸入法呢?當(dāng)然是它展現(xiàn)窗口的時(shí)候了。所以從這個(gè)角度來說,就好理解為什么注冊時(shí)機(jī)會(huì)是這個(gè)應(yīng)用程序向WMS申請窗口的時(shí)候了。

Binder3:InputMethodSession

這個(gè)binder其實(shí)定義了客戶端可以調(diào)用輸入法的相關(guān)方法。先大概看看接口方法:

interface IInputMethodSession {
  void finishInput();
  void updateExtractedText(int token, in ExtractedText text);
  void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd);
  void viewClicked(boolean focusChanged);
  void updateCursor(in Rect newCursor);
  void displayCompletions(in CompletionInfo[] completions);
  void appPrivateCommand(String action, in Bundle data);
  void toggleSoftInput(int showFlags, int hideFlags);
  void finishSession();
  void updateCursorAnchorInfo(in CursorAnchorInfo cursorAnchorInfo);
}

然后看一下服務(wù)端的類圖:

Paste_Image.png

我們主要先關(guān)心一下這個(gè)binder的服務(wù)端(在IMS中)是何時(shí)創(chuàng)建的,又是如何把客戶端binder傳給客戶應(yīng)用程序的。
這個(gè)binder其實(shí)對應(yīng)于一個(gè)客戶端應(yīng)用進(jìn)程,所以當(dāng)有一個(gè)客戶端應(yīng)用程序接入的時(shí)候創(chuàng)建,創(chuàng)建以后又要把客戶端binder傳過去。不過這個(gè)交互的過程需要IMMS作為媒介,而和IMMS交互的過程中又牽涉到InputMethod這個(gè)接口。
大概的意思是,IMMS先調(diào)用bindService啟動(dòng)IMS,然后IMS會(huì)返回一個(gè)Binder給IMMS。然后有了這個(gè)binder,IMMS就會(huì)在需要的時(shí)候,調(diào)用IMS的createSession創(chuàng)建這個(gè)SessionBinder,然后傳給客戶端進(jìn)程。

Binder4: IInputMethod

IInputMethod這個(gè)binder是IMS和IMMS交互用的。先看一下接口方法:

interface IInputMethod {
  void attachToken(IBinder token);
  void bindInput(in InputBinding binding);
  void unbindInput();
  void startInput(in IInputContext inputContext, in EditorInfo attribute);
  void restartInput(in IInputContext inputContext, in EditorInfo attribute);
  void createSession(in InputChannel channel, IInputSessionCallback callback);
  void setSessionEnabled(IInputMethodSession session, boolean enabled);
  void revokeSession(IInputMethodSession session);
  void showSoftInput(int flags, in ResultReceiver resultReceiver);
  void hideSoftInput(int flags, in ResultReceiver resultReceiver);
  void changeInputMethodSubtype(in InputMethodSubtype subtype);
}
Paste_Image.png

這個(gè)binder其實(shí)是隨著輸入法啟動(dòng),就一直不會(huì)變化的(也就是和IMS是擁有同一個(gè)生命周期的),所以創(chuàng)建一次就不用再創(chuàng)建了。創(chuàng)建時(shí)機(jī)自然是IMMS啟動(dòng)IMS的時(shí)候(bindService)。

輸入法主要操作過程

總體過程

Paste_Image.png

啟動(dòng)輸入法

從客戶端應(yīng)用程序的角度來說,它一般是不自己啟動(dòng)輸入法的(只有兩種開放的方法,即IMM.showSoftInput/hideSoftInput),大多數(shù)情況下輸入法是系統(tǒng)自動(dòng)啟動(dòng)的。啟動(dòng)時(shí)機(jī),之前也提到,和窗口有關(guān)。

編輯框獲得Focus后的處理流程

顯示輸入法

一般來講,editText被點(diǎn)擊自動(dòng)獲取焦點(diǎn)之后,會(huì)調(diào)用IMM的showSoftInput,然后轉(zhuǎn)向IMMS的showSoftInput。再然后需要讓IMS去顯示輸入法,這個(gè)過程大概是下面這個(gè)樣子的:

IMS和IMMS模塊的通信過程

輸入法窗口內(nèi)部顯示過程

IMS.showWindow的過程:

// InputMethodService.InputMethodImpl 
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
    boolean wasVis = isInputViewShown();
    mShowInputFlags = 0;
    if (onShowInputRequested(flags, false)) { // 這個(gè)方法內(nèi)部會(huì)調(diào)用onEvaluateInputViewShown
        try {
            showWindow(true); // 重點(diǎn)在這里
        } catch (BadTokenException e) {
            mWindowVisible = false;
            mWindowAdded = false;
        }
    }
    clearInsetOfPreviousIme();
    // If user uses hard keyboard, IME button should always be shown.
    boolean showing = isInputViewShown();
    mImm.setImeWindowStatus(mToken, IME_ACTIVE | (showing ? IME_VISIBLE : 0),
            mBackDisposition);
    if (resultReceiver != null) { // 將此次showWindow的結(jié)果狀態(tài)返回回去:Result_shown/.../...
        resultReceiver.send(wasVis != isInputViewShown()
                ? InputMethodManager.RESULT_SHOWN
                : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
                        : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
    }
}

// InputMethodService
public void showWindow(boolean showInput) {
    if (mInShowWindow) {   // 正在顯示窗口,直接返回
        return;
    }

    try {
        mWindowWasVisible = mWindowVisible;
        mInShowWindow = true;
        showWindowInner(showInput); // 顯示窗口
    } finally {
        mWindowWasVisible = true;
        mInShowWindow = false;
    }
}

void showWindowInner(boolean showInput) {
    boolean doShowInput = false;
    final int previousImeWindowStatus =
            (mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0);
    mWindowVisible = true;
    if (!mShowInputRequested) {
        if (mInputStarted) {
            if (showInput) {
                doShowInput = true;
                mShowInputRequested = true;
            }
        }
    } else {
        showInput = true;
    }

    initialize(); // 這里面有個(gè)標(biāo)志判斷是否要進(jìn)行onInitializeInterface回調(diào)
    updateFullscreenMode();
    updateInputViewShown();

    if (!mWindowAdded || !mWindowCreated) {
        mWindowAdded = true;
        mWindowCreated = true;
        initialize();
        View v = onCreateCandidatesView();
        if (v != null) {
            setCandidatesView(v);
        }
    }
    if (mShowInputRequested) {
        if (!mInputViewStarted) {
            mInputViewStarted = true;
            onStartInputView(mInputEditorInfo, false); // 回調(diào)onStartInputView
        }
    } else if (!mCandidatesViewStarted) {
        mCandidatesViewStarted = true;
        onStartCandidatesView(mInputEditorInfo, false);
    }

    if (doShowInput) {
        startExtractingText(false);
    }

    final int nextImeWindowStatus = IME_ACTIVE | (isInputViewShown() ? IME_VISIBLE : 0);
    if (previousImeWindowStatus != nextImeWindowStatus) {
        mImm.setImeWindowStatus(mToken, nextImeWindowStatus, mBackDisposition);
    }
    if ((previousImeWindowStatus & IME_ACTIVE) == 0) {
        onWindowShown();
        mWindow.show();
        mShouldClearInsetOfPreviousIme = false;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,555評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,516評論 2 379

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