安卓输入法源码1--IMM、IMMS、IMS启动流程

一、前言

本文基于Android V(安卓15),主要内容是输入法三大部分的初始化流程。每个函数都配备了路径和注释。

安卓输入法主要分为三个部分,先简单介绍一下:

IMM(InputMethodManager,应用客户端)

  • 运行在 应用进程 中,作为输入法的客户端,负责与 IMMS 交互。

  • 通过 IMMS 控制输入法的显示、隐藏、文本输入等行为,并与 IMS 进行通信。

IMS(InputMethodService,输入法服务)

  • 运行在 输入法进程(例如讯飞、搜狗输入法等)中,负责具体的输入法逻辑。

  • 大部分第三方输入法都是继承 InputMethodService 并进行扩展,实现定制效果。

  • 通过 IMMS 注册自身,并与 IMM 进行交互。

IMMS(InputMethodManagerService,系统管理端)

  • 运行在 system_server 进程 中,负责 管理系统中的所有输入法

  • 充当 IMM(应用)与 IMS(输入法)之间的桥梁,处理输入法的连接、切换、权限管理等。

  • 维护当前输入法的状态,并协调输入法的生命周期。

二、详细流程

1、IMM 的初始化流程

每个应用进程都会创建一个 InputMethodManager(IMM)实例,与 InputMethodManagerService(IMMS)进行通信。

IMM 的主要作用是将 IInputMethodClientIRemoteInputConnection(AIDL 接口)传递给 IMMS。随后,IMMS 会将这两个实例与 IMS(输入法服务) 进行绑定,使得输入法能够通过这些接口与应用交互,从而实现 输入内容在应用中正确显示

由于 IMM 运行在应用进程中,我们可以在 Android Studio 中通过 Attach Debugger to Android Process 选择 目标应用进程 进行调试。例如,在调试小米应用商店时,我们可以选择 com.xiaomi.market 进行附加调试。

1.0 ViewRootImpl

// frameworks/core/java/android/view/ViewRootImpl.java
// 每当创建新的窗口的时候,都会创建ViewRootImpl实例,在ViewRootImpl的构造函数中会检查imm是否实例化
public ViewRootImpl(Context context, Display display) {
    this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout());
}

1.1 WindowManagerGlobal.getWindowSession

//frameworks/core/java/android/view/WindowManagerGlobal.java
public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                //The global instance of InputMethodManager  was instantiated here.
                //翻译:IMM的全局实例在这里被初始化
                InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                IWindowManager windowManager = getWindowManagerService();
                ...
            }
    } 

1.2 IMM.ensureDefaultInstanceForDefaultDisplayIfNecessary

//frameworks/core/java/android/view/inputmethod/InputMethodManager.java
public static void ensureDefaultInstanceForDefaultDisplayIfNecessary() {
    // Skip this call if we are in system_server, as the system code should not use this deprecated instance.
    // 翻译:如果当前运行在 system_server 进程中,则跳过此调用,因为系统代码不应该使用这个已弃用的实例。
    // 目前我们现在是在 小米商店 进程中,这里会继续运行。
    if (!ActivityThread.isSystem()) {
        forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());
    }
}

1.3 IMM.forContextInternal

//frameworks/core/java/android/view/inputmethod/InputMethodManager.java
private static InputMethodManager forContextInternal(int displayId, Looper looper) {
    final boolean isDefaultDisplay = displayId == Display.DEFAULT_DISPLAY;
    synchronized (sLock) {
        InputMethodManager instance = sInstanceMap.get(displayId);
        if (instance != null) {
            return instance;
        }
        //这里会创建 InputMethodManger 的实例
        instance = createInstance(displayId, looper);
        if (sInstance == null && isDefaultDisplay) {
            sInstance = instance;
        }
        sInstanceMap.put(displayId, instance);
        return instance;
    }
}

1.4 IMM.createInstance

//frameworks/core/java/android/view/inputmethod/InputMethodManager.java
private static InputMethodManager createInstance(int displayId, Looper looper) {
    //isInEditMode  在应用进程中会直接返回 False,所以我们直接看createRealInstance即可
    return isInEditMode() ? createStubInstance(displayId, looper)
            : createRealInstance(displayId, looper);
}

1.5 IMM.createRealInstance

//frameworks/core/java/android/view/inputmethod/InputMethodManager.java
private static InputMethodManager createRealInstance(int displayId, Looper looper) {
    //GlobalInvoker--全局调用者,是一种架构设计思路。
    //IInputMethodManagerGlobalInvoker是对 IMMS 的一种封装。对外提供统一的API,来简化和规范化API的调用
    final IInputMethodManager service = IInputMethodManagerGlobalInvoker.getService();
    if (service == null) {
        throw new IllegalStateException("IInputMethodManager is not available");
    }
    //这里创建了 imm 的实例
    final InputMethodManager imm = new InputMethodManager(service, displayId, looper);
    final long identity = Binder.clearCallingIdentity();
    try {
        //这里是将 imm信息 添加到IMMS
        IInputMethodManagerGlobalInvoker.addClient(imm.mClient, imm.mFallbackInputConnection,
                displayId);
    } finally {
        Binder.restoreCallingIdentity(identity);
    }
    return imm;
}

1.6 IInputMethodManagerGlobalInvoker.addClient

//frameworks/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
static void addClient(@NonNull IInputMethodClient client,
        @NonNull IRemoteInputConnection fallbackInputConnection, int untrustedDisplayId) {
    //getService : 通过 AIDL 机制获取输入法管理服务 (IMMS) 的远程接口实例
    final IInputMethodManager service = getService();
    if (service == null) {
        return;
    }
    try {
        service.addClient(client, fallbackInputConnection, untrustedDisplayId);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

1.7 IMMS.addClient

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
public void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
        int selfReportedDisplayId) {
    final int callerUid = Binder.getCallingUid();
    final int callerPid = Binder.getCallingPid();
    final IInputMethodClientInvoker clientInvoker =
            IInputMethodClientInvoker.create(client, mHandler);
    synchronized (ImfLock.class) {
        //Tips:mClientController -- Store and manage {@link InputMethodManagerService} clients.
        mClientController.addClient(clientInvoker, inputConnection, selfReportedDisplayId,
                callerUid, callerPid);
    }
}

1.8 ClientController.addClient

//frameworks/services/core/java/com/android/server/inputmethod/ClientController.java
ClientState addClient(IInputMethodClientInvoker clientInvoker,
        IRemoteInputConnection inputConnection, int selfReportedDisplayId, int callerUid,
        int callerPid) {
    final IBinder.DeathRecipient deathRecipient = () -> {
        // Exceptionally holding ImfLock here since this is a internal lambda expression.
        synchronized (ImfLock.class) {
            removeClientAsBinder(clientInvoker.asBinder());
        }
    };

    // 看看之前是否已经存储过了这个 mClient
    final int numClients = mClients.size();
    for (int i = 0; i < numClients; ++i) {
        final ClientState state = mClients.valueAt(i);
        if (state.mUid == callerUid && state.mPid == callerPid
                && state.mSelfReportedDisplayId == selfReportedDisplayId) {
            throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid
                    + "/displayId=" + selfReportedDisplayId + " is already registered");
        }
    }
    
    try {
        clientInvoker.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
    } catch (RemoteException e) {
        throw new IllegalStateException(e);
    }
    
    
    final ClientState cs = new ClientState(clientInvoker, inputConnection,
            callerUid, callerPid, selfReportedDisplayId, deathRecipient);
    //创建客户端状态(ClientState)并存入 mClients,用于管理应用与输入法服务的连接     
    mClients.put(clientInvoker.asBinder(), cs);
    return cs;
}

//至此,IMM的创建以及绑定到 IMMS 的流程就结束了

2、IMMS 的初始化流程

InputMethodManagerService(IMMS)主要负责以下工作:

  • 输入法的管理:包括 绑定、解绑、启动 输入法(IMS)。

  • 处理应用(APP)与输入法(IMS)的交互,确保输入内容能够正确传输到应用。

由于 IMMS 运行在 system_server 进程中,如果需要调试 IMMS,可以在 Android StudioAttach Debugger to Android Process,然后选择 system_process 进行调试。

2.0 SystemServer.main

几乎所有的系统服务都在SystemServer中。IMMS也不例外
在开机启动的时候,这个类就会执行。相关的系统服务就会被启动
使用crete 创建了一个新的IInputMethodManagerImpl实例,

//frameworks/services/java/com/android/server/SystemServer.java
public static void main(String[] args) {
  new SystemServer().run();
}

//frameworks/services/java/com/android/server/SystemServer.java
private void run() {
    ...
    // Start services.
    try {
    t.traceBegin("StartServices");
        startBootstrapServices(t);
        startCoreServices(t);
        // IMMS 在这里启动
        startOtherServices(t);
        startApexServices(t);
        ...
    }
}

2.1 SystemServer.startOtherServices

//frameworks/services/java/com/android/server/SystemServer.java
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
    ...
    // Bring up services needed for UI.
    if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
        t.traceBegin("StartInputMethodManagerLifecycle");
        String immsClassName = context.getResources().getString(
                R.string.config_deviceSpecificInputMethodManagerService);
        if (immsClassName.isEmpty()) {
             //没有自定义 IMMS,使用默认的 InputMethodManagerService
             //adb shell getprop ro.config.deviceSpecificInputMethodManagerService 为空
            mSystemServiceManager.startService(InputMethodManagerService.Lifecycle.class);
        } else {
            try {
                Slog.i(TAG, "Starting custom IMMS: " + immsClassName);
                mSystemServiceManager.startService(immsClassName);
            } catch (Throwable e) {
                reportWtf("starting " + immsClassName, e);
            }
        }
    ...
 }

2.2 SystemServiceManager.startService

 //frameworks/services/core/java/com/android/server/SystemServiceManager.java
 public <T extends SystemService> T startService(Class<T> serviceClass) {
    try {
        final String name = serviceClass.getName();
        Slog.i(TAG, "Starting " + name);
        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartService " + name);

        // Create the service.
        if (!SystemService.class.isAssignableFrom(serviceClass)) {
            throw new RuntimeException("Failed to create " + name
                    + ": service must extend " + SystemService.class.getName());
        }
        final T service;
        try {
            Constructor<T> constructor = serviceClass.getConstructor(Context.class);
            service = constructor.newInstance(mContext);
        }
        ...
        startService(service);
 }

2.3SystemServiceManager.startService

//frameworks/services/core/java/com/android/server/SystemServiceManager.java
 public void startService(@NonNull final SystemService service) {
    // Check if already started
    String className = service.getClass().getName();
    if (mServiceClassnames.contains(className)) {
        Slog.i(TAG, "Not starting an already started service " + className);
        return;
    }
    mServiceClassnames.add(className);

    // Register it.
    //将service注册到mServices列表中
    mServices.add(service);

    // Start it.
    long time = SystemClock.elapsedRealtime();
    try {
        //调用相应service的onStart函数,这里就是IMMS
        service.onStart();
    } catch (RuntimeException ex) {
        throw new RuntimeException("Failed to start service " + service.getClass().getName()
                + ": onStart threw an exception", ex);
    }
    warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStart");
}

2.4 IMMS.onStart

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
public void onStart() {
    mService.publishLocalService();
    IInputMethodManagerImpl.Callback service;
    if (Flags.useZeroJankProxy()) {
        service = new ZeroJankProxy(mService.mHandler::post, mService);
    } else {
        service = mService;
    }
    //使用crete 创建了一个新的IInputMethodManagerImpl实例,用于实现输入法管理相关的操作。
    //通过调用publishBinderService方法,把Context.INPUT_METHOD_SERVICE 和 IMMS 关联起来了
    publishBinderService(Context.INPUT_METHOD_SERVICE,
            IInputMethodManagerImpl.create(service), false /*allowIsolated*/,
            DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
    if (Flags.refactorInsetsController()) {
        mService.registerImeRequestedChangedListener();
    }
}

2.5 SystemService.publishBinderService

//frameworks/services/core/java/com/android/server/SystemService.java
protected final void publishBinderService(String name, IBinder service,
        boolean allowIsolated, int dumpPriority) {
    //这样,其他组件ServiceManger.getService(Context.INPUT_METHOD_SERVICE,与IMMS交互
    ServiceManager.addService(name, service, allowIsolated, dumpPriority);
}

 至此 IMMS 的初始化就完成了

3、IMS 的初始化流程

注意:IMS(Input Method Service)是所有输入法的基类,例如讯飞输入法。IMS 作为一个普通的服务,通常会在 AndroidManifest.xml 文件中注册。其他进程可以通过bindService来与该服务进行通信。

下面,我们以 Google 的默认输入法LatinIME为例,来说明 IMS 的初始化流程。

3.0 AndroidManifest.xml

//packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/LatinIME.java
//可以看到LatinIME是继承了IMS
public class LatinIME extends InputMethodService implements KeyboardActionListener,
        SuggestionStripView.Listener, SuggestionStripViewAccessor,
        DictionaryFacilitator.DictionaryInitializationListener,
        PermissionsManager.PermissionsResultCallback {
        ...
}

//我们在AndroidManifest.xml 看一下他注册的服务
//packages/inputmethods/LatinIME/java/AndroidManifest.xml 
<service android:name="LatinIME"
             android:label="@string/english_ime_name"
             android:permission="android.permission.BIND_INPUT_METHOD"
             android:exported="true"
             android:visibleToInstantApps="true">
            <intent-filter>
                <action android:name="android.view.InputMethod"/>
            </intent-filter>
            <meta-data android:name="android.view.im"
                 android:resource="@xml/method"/>
        </service>

下一步我们看哪里使用bindService去调用了这个Service 当应用请求输入法的时候,imm会调用startInputInner,我们从这里开始开始 温馨提示:这一块的代码和 输入法弹出 的流程高度相似,大部分函数也是共用的

3.1 IMM.startInputInner

//frameworks/core/java/android/view/inputmethod/InputMethodManager.java
//流程的起点是 IMM 的startInputInner
private boolean startInputInner(@StartInputReason int startInputReason,
        @Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
        @SoftInputModeFlags int softInputMode, int windowFlags) {
        ...
        int startInputSeq = -1;
        if (Flags.useZeroJankProxy()) {
            // async result delivered via MSG_START_INPUT_RESULT.
            安卓15 需要用这个 异步的方法,但是FrameWork层没有源码,按照下面的同步代码进行跟踪
            startInputSeq = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocusAsync(
                    startInputReason, mClient, windowGainingFocus, startInputFlags,
                    softInputMode, windowFlags, editorInfo, servedInputConnection,
                    servedInputConnection == null ? null
                            : servedInputConnection.asIRemoteAccessibilityInputConnection(),
                    view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
                    mImeDispatcher, ASYNC_SHOW_HIDE_METHOD_ENABLED);
        } else {
        //Involker函数,前面已经说过了,是一个对IMMS的封装,对外提供统一的API,简化调用
            res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
                    startInputReason, mClient, windowGainingFocus, startInputFlags,
                    softInputMode, windowFlags, editorInfo, servedInputConnection,
                    servedInputConnection == null ? null
                            : servedInputConnection.asIRemoteAccessibilityInputConnection(),
                    view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
                    mImeDispatcher);
        }
}

3.2 IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus

//frameworks/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
static InputBindResult startInputOrWindowGainedFocus(@StartInputReason int startInputReason,
        @NonNull IInputMethodClient client, @Nullable IBinder windowToken,....)
    //这里的getService就是去获取了IMMS
    final IInputMethodManager service = getService();
    if (service == null) {
        return InputBindResult.NULL;
    }
    try {
        //接下来就要去看IMMS的这个函数了
        return service.startInputOrWindowGainedFocus(startInputReason, client, windowToken,
                startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
                remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
                imeDispatcher);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

3.3 IMMS.startInputOrWindowGainedFocus

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
public InputBindResult startInputOrWindowGainedFocus(
        @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
        @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,...)
        ...
        //调用了内部函数
        result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
            client, windowToken, startInputFlags, softInputMode, windowFlags,
            editorInfo, inputConnection, remoteAccessibilityInputConnection,
            unverifiedTargetSdkVersion, bindingController, imeDispatcher, cs);
}

3.4 IMMS.startInputOrWindowGainedFocusInternalLocked

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
private InputBindResult startInputOrWindowGainedFocusInternalLocked(
        @StartInputReason int startInputReason, IInputMethodClient client,...)
        if (sameWindowFocused && isTextEditor) {
           
            if (editorInfo != null) {
                return startInputUncheckedLocked(cs, inputContext,
                        remoteAccessibilityInputConnection, editorInfo, startInputFlags,
                        startInputReason, unverifiedTargetSdkVersion, imeDispatcher,
                        bindingController);
            }
            ...
}

3.5 IMMS.startInputUncheckedLocked

这个函数还挺关键的,将Client、InputConnection、InputMethodBindingController 连在一起

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
private InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
        IRemoteInputConnection inputConnection,...,@NonNull InputMethodBindingController bindingController) {
    ...
    final String curId = bindingController.getCurId();
    final int displayIdToShowIme = bindingController.getDisplayIdToShowIme();
    //如果说我们之前已经连接过输入法了,这里就可以复用之前了
    if (curId != null && curId.equals(bindingController.getSelectedMethodId())
            && displayIdToShowIme == getCurTokenDisplayIdLocked()) {
        if (cs.mCurSession != null) {
            cs.mSessionRequestedForAccessibility = false;
            requestClientSessionForAccessibilityLocked(cs);
            attachNewAccessibilityLocked(startInputReason,
                    (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
            //这里是显示输入法的逻辑,ic会在这里传递给输入法
            return attachNewInputLocked(startInputReason,
                    (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
        }

        InputBindResult bindResult = tryReuseConnectionLocked(bindingController, cs);
        if (bindResult != null) {
            return bindResult;
        }
    }
    //这里是绑定输入法的逻辑,如果你的断点到不了这里,你可以在IMMS打断点、同时切换输入法即可
    bindingController.unbindCurrentMethod();
    return bindingController.bindCurrentMethod();
}

3.6 InputMethodBindingController.bindCurrentMethod

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
InputBindResult bindCurrentMethod() {
    ...
    mCurIntent = createImeBindingIntent(info.getComponent());
    if (bindCurrentInputMethodServiceMainConnection()) {
        mCurId = info.getId();
        mLastBindTime = SystemClock.uptimeMillis();
    
        mCurToken = new Binder();
        mCurTokenDisplayId = mDisplayIdToShowIme;
    }
}

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@GuardedBy("ImfLock.class")
private boolean bindCurrentInputMethodServiceMainConnection() {
    mHasMainConnection = bindCurrentInputMethodService(mMainConnection,
            mImeConnectionBindFlags);
    return mHasMainConnection;
}

//frameworks/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) {
    if (mCurIntent == null || conn == null) {
        Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
        return false;
    }
    //至此IMMS完成输入法的绑定。
    return mContext.bindServiceAsUser(mCurIntent, conn, flags, new UserHandle(mUserId));
}

三、总结

至此,安卓输入法的三个组成部分的初始化流程就走完了。 后面我们会再学习 输入法的弹出和隐藏 以及 安卓输入法源码3 -- InputConnection这个我认为是最重要的AIDL接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值