2. IME初始化啟動流程
2.1. IME客戶端(IMM)初始化流程
涉及代碼文件路徑: frameworks/base/core/java/android/view/ViewRootImpl.java frameworks/base/core/java/android/view/WindowManagerGlobal.java frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java frameworks/base/core/java/com/android/internal/view/IInputMethodClient.aidl frameworks/base/core/java/com/android/internal/view/IInputContext.aidl frameworks/base/core/java/com/android/internal/view/IInputMethodManager.aidl frameworks/base/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
2.1.1. 函數流程梳理
# 每次新增窗口window時,都會實例化ViewRootImpl,而ViewRootImpl在獲取IWindowSession時會檢查輸入法是否已經初始化
ViewRootImpl.java -- 初始化構造函數,調用WindowManagerGlobal.getWindowSession()---> WindowManagerGlobal.java -- getWindowSession()調用InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary() 實例化全局調用InputMethodManager,即初始化IMM---> InputMethodManager.java -- ensureDefaultInstanceForDefaultDisplayIfNecessary()調用forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper()),入參默認displayID和looper# 此處也說明,對于APP層,IMM有且只有一個實例,每次創建ViewRootImpl都會檢查IMM是否實例化完成---》 調用forContextInternal函數,先從緩存Map中查詢是否有IMM實例,如果沒有則創建IMM實例,并添加到Map中---》 調用createInstance創建實例,然后在三目運算中默認固定調用createRealInstance(displayId, looper)---》 調用createRealInstance函數, (1)獲取輸入法服務service,即Context.INPUT_METHOD_SERVICE(service是AIDL接口文件IInputMethodManager.aidl);(2)new InputMethodManager(service, displayId, looper)創建實例---》 InputMethodManager構造函數---》 new IInputConnectionWrapper 創建虛擬的輸入法上下文,主要用于監聽輸入法服務的激活狀態,接受輸入事件# 添加IMM實例到輸入法service服務中# 此處兩個入參都是AIDL接口類型的對象# (1)IInputMethodClient.aidl:輸入法客戶端, 主要用于報告輸入法當前的狀態, 讓APP應用端的IMM做出相應的處理# (2)IInputContext.aidl:輸入法上下文, 主要用于操作字符輸入操作, 讓當前接收字符的view進行處理(3)調用service.addClient(imm.mClient //[AIDL對象,即IInputMethodClient], imm.mIInputContext//[AIDL對象,IInputContext], displayId)---> IInputMethodManager.aidl -- 調用addClient(跨進程通信到IMMS)---> 服務端InputMethodManagerService.java "extends IInputMethodManager.Stub" -- 調用addClient函數,創建ClientState對象---》 調用內部靜態類ClientState的構造函數,保存client相關狀態屬性
綜上代碼流程梳理,可以看出:
- 對于每個APP應用,IMM有且只有一個實例,并且每次創建ViewRootImpl時,都會檢查IMM是否已經實例化成功
- 實例化IMM對象時,會涉及到兩個AIDL接口文件,一個用于應用端IMM處理輸入法當前狀態,一個用于輸入法上下文,創建一個虛擬的InputContext代表輸入空間,用于監聽輸入法激活狀態
- 實例化過程中會有個displayid,用于多屏幕顯示(通常情況下默認是default display=0)
- 實例化最后,會通過AIDL的addClient接口函數,將IMM添加到IMMS中,如此IMM實例化完成
2.1.2. 代碼詳細說明
//ViewRootImpl.javapublic ViewRootImpl(Context context, Display display) {this(context, display, WindowManagerGlobal.getWindowSession(),false /* useSfChoreographer */);}//WindowManagerGlobal.javapublic static IWindowSession getWindowSession() {synchronized (WindowManagerGlobal.class) {if (sWindowSession == null) {try {//調用該函數,初始化IMMInputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();......} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return sWindowSession;}}//InputMethodManager.javapublic static void ensureDefaultInstanceForDefaultDisplayIfNecessary() {//默認default displayforContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());}private static InputMethodManager forContextInternal(int displayId, Looper looper) {final boolean isDefaultDisplay = displayId == Display.DEFAULT_DISPLAY;synchronized (sLock) {//從緩存Map中查找是否由default display的IMM實例InputMethodManager instance = sInstanceMap.get(displayId);//如果存在實例,則直接返回if (instance != null) {return instance;}//初始化創建實例instance = createInstance(displayId, looper);//如果是用于default display使用,則存儲到sInstance中作為全局單例實例if (sInstance == null && isDefaultDisplay) {sInstance = instance;}//將IMM實例保存到Map中sInstanceMap.put(displayId, instance);return instance;}}private static InputMethodManager createInstance(int displayId, Looper looper) {//isInEditMode固定返回false,直接調用createRealInstancereturn isInEditMode() ? createStubInstance(displayId, looper): createRealInstance(displayId, looper);}private static InputMethodManager createRealInstance(int displayId, Looper looper) {//IInputMethodManager是AIDL接口文件,用于跨進程通信到IMMS(InputMethodManagerService)final IInputMethodManager service;try {//獲取serviceservice = IInputMethodManager.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));} catch (ServiceNotFoundException e) {throw new IllegalStateException(e);}//創建IMM實例final InputMethodManager imm = new InputMethodManager(service, displayId, looper);//將PID/UID和每個IME客戶端關聯,然后作為跨進程服務端IPC使用梳理//如果作為同進程內調用梳理,則需要確保Binder.getCalling{Pid, Uid}()返回Process.my{Pid, Uid}()//無論哪種情況,都要調用Binder的{clear, restore}CallingIdentity()函數,對跨進程沒有影響,對同進程可以滿足需求實現final long identity = Binder.clearCallingIdentity();try {// 添加 IMM 實例到輸入法服務// imm.mClient 是一個aidl對象, mClient即new IInputMethodClient.Stub(),AIDL接口// imm.mIInputContext 是一個aidl對象, IInputContext,AIDL接口service.addClient(imm.mClient, imm.mIInputContext, displayId);} catch (RemoteException e) {e.rethrowFromSystemServer();} finally {Binder.restoreCallingIdentity(identity);}return imm;}//InputMethodManagerService.java//由每個APP應用進程調用,作為輸入法開始與交互的準備@Overridepublic void addClient(IInputMethodClient client, IInputContext inputContext,int selfReportedDisplayId) {//獲取調用的uid和pid(即InputMethodManager實際運行所在的UID/PID)//兩種情況下調用此方法://1.IMM正在另一個進程中實例化//2.IMM正在同一個進程中實例化,final int callerUid = Binder.getCallingUid();final int callerPid = Binder.getCallingPid();synchronized (mMethodMap) {// TODO: Optimize this linear search.final int numClients = mClients.size();for (int i = 0; i < numClients; ++i) {final ClientState state = mClients.valueAt(i);if (state.uid == callerUid && state.pid == callerPid&& state.selfReportedDisplayId == selfReportedDisplayId) {throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid+ "/displayId=" + selfReportedDisplayId + " is already registered.");}}//利用IBinder.deathRecipient監聽client存活狀態//如果client的Binder死亡,則將Client從緩存Map中移除final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client);try {client.asBinder().linkToDeath(deathRecipient, 0);} catch (RemoteException e) {throw new IllegalStateException(e);}//此處不驗證displayID,后續每當客戶端需要與指定的交互時,就需要檢查displayID//此處創建ClientState對象,將client和inputContext緩存進去,然后將該對象保存到緩存Map mClients中mClients.put(client.asBinder(), new ClientState(client, inputContext, callerUid,callerPid, selfReportedDisplayId, deathRecipient));}}