Bootstrap

Android输入法IME(二)之 客户端(IMM)启动流程

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相关状态属性

综上代码流程梳理,可以看出:

  1. 对于每个APP应用,IMM有且只有一个实例,并且每次创建ViewRootImpl时,都会检查IMM是否已经实例化成功
  2. 实例化IMM对象时,会涉及到两个AIDL接口文件,一个用于应用端IMM处理输入法当前状态,一个用于输入法上下文,创建一个虚拟的InputContext代表输入空间,用于监听输入法激活状态
  3. 实例化过程中会有个displayid,用于多屏幕显示(通常情况下默认是default display=0)
  4. 实例化最后,会通过AIDL的addClient接口函数,将IMM添加到IMMS中,如此IMM实例化完成

2.1.2. 代码详细说明

//ViewRootImpl.java
    public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),
                false /* useSfChoreographer */);
    }

//WindowManagerGlobal.java
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    //调用该函数,初始化IMM
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    ......
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

//InputMethodManager.java
   public static void ensureDefaultInstanceForDefaultDisplayIfNecessary() {
        //默认default display
        forContextInternal(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,直接调用createRealInstance
        return isInEditMode() ? createStubInstance(displayId, looper)
                : createRealInstance(displayId, looper);
    }

    private static InputMethodManager createRealInstance(int displayId, Looper looper) {
        //IInputMethodManager是AIDL接口文件,用于跨进程通信到IMMS(InputMethodManagerService)
        final IInputMethodManager service;
        try {
            //获取service
            service = 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应用进程调用,作为输入法开始与交互的准备
    @Override
    public 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));
        }
    }

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;