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 {
...
}