[TOC]
概述
我們從以下幾個(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è)接口來表征兩者的通信。
每個(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);
}
類圖如下:
根據(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ù)端的類圖:
我們主要先關(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);
}
這個(gè)binder其實(shí)是隨著輸入法啟動(dòng),就一直不會(huì)變化的(也就是和IMS是擁有同一個(gè)生命周期的),所以創(chuàng)建一次就不用再創(chuàng)建了。創(chuàng)建時(shí)機(jī)自然是IMMS啟動(dòng)IMS的時(shí)候(bindService)。
輸入法主要操作過程
總體過程
啟動(dòng)輸入法
從客戶端應(yīng)用程序的角度來說,它一般是不自己啟動(dòng)輸入法的(只有兩種開放的方法,即IMM.showSoftInput/hideSoftInput),大多數(shù)情況下輸入法是系統(tǒng)自動(dòng)啟動(dòng)的。啟動(dòng)時(shí)機(jī),之前也提到,和窗口有關(guān)。
顯示輸入法
一般來講,editText被點(diǎn)擊自動(dòng)獲取焦點(diǎn)之后,會(huì)調(diào)用IMM的showSoftInput,然后轉(zhuǎn)向IMMS的showSoftInput。再然后需要讓IMS去顯示輸入法,這個(gè)過程大概是下面這個(gè)樣子的:
輸入法窗口內(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;
}
}