一、前言
本文基于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 的主要作用是将 IInputMethodClient
和 IRemoteInputConnection
(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 Studio 中 Attach 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接口。