Android 输入法框架浅析
基于 Android 16 (android w) 源码分析
一、概述
Android 输入法框架(Input Method Framework, IMF)采用经典的客户端-服务端架构,将输入法的管理、实现和窗口控制三层分离。本文从系统开发角度分析输入法框架的架构设计、核心组件交互以及事件分发机制。
架构总览
┌─────────────────────────────────────────────────────────────────────────────┐
│ 输入法框架三层架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 输入法应用 (IME Application) │ │
│ │ InputMethodService │ │
│ │ - 第三方输入法:搜狗、百度、Gboard 等 │ │
│ │ - 运行在独立进程 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ Binder IPC │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 输入法框架 (IMF) │ │
│ │ InputMethodManagerService (IMMS) - system_server 进程 │ │
│ │ InputMethodManager (IMM) - 应用进程 │ │
│ │ InputMethodService (IMS) - 输入法进程 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ Binder IPC │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 窗口管理服务 (WMS) │ │
│ │ WindowState、InputMethodWindow、Insets 管理 │ │
│ │ IME Target 管理 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
二、进程模型
2.1 三个关键进程
输入法框架涉及三个独立的进程,各司其职:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Android 进程架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ system_server 进程 │ │
│ │ ├─ InputMethodManagerService (IMMS) - 输入法管理服务 │ │
│ │ ├─ WindowManagerService (WMS) - 窗口管理 │ │
│ │ ├─ ActivityManagerService (AMS) - 活动管理 │ │
│ │ 用户: system (uid 1000) │ │
│ │ 生命周期: 系统运行期间一直存在 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 输入法进程 (IME Process) │ │
│ │ 进程名: com.android.inputmethod.latin 等 │ │
│ │ 用户: u0_a123 (普通应用用户) │ │
│ │ ├─ InputMethodService - 输入法服务实现 │ │
│ │ ├─ SoftInputWindow - 软键盘窗口 │ │
│ │ └─ 独立的 ViewRootImpl │ │
│ │ 生命周期: 可被系统杀死、重启 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 应用进程 (App Process) │ │
│ │ ├─ Activity - 应用界面 │ │
│ │ ├─ ViewRootImpl - 视图根节点 │ │
│ │ ├─ InputMethodManager (IMM) - 输入法客户端 │ │
│ │ └─ 独立的输入事件通道 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.2 进程对比
| 特性 | system_server 进程 | 输入法进程 | 应用进程 |
|---|---|---|---|
| 启动时机 | 系统启动时 | 首次使用或服务绑定时 | 应用启动时 |
| 用户身份 | system (uid 1000) | 普通应用用户 | 普通应用用户 |
| 生命周期 | 系统运行期间一直存在 | 可被杀死、重启 | 随应用生命周期 |
| 核心类 | InputMethodManagerService | InputMethodService | InputMethodManager |
| 职责 | 管理输入法 | 实现输入法 UI | 使用输入法 |
关键点:输入法和应用不在同一个进程,各有独立的 ViewRootImpl,通过 Binder IPC 进行通信。
三、核心组件详解
3.1 InputMethodManagerService (IMMS)
IMMS 运行在 system_server 进程,是输入法框架的核心管理服务。
源码位置:frameworks/base/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
核心职责:
/**
* This class provides a system service that manages input methods.
*/
public class InputMethodManagerService extends AbsInputMethodManagerService
implements ZeroJankProxy.Callback, Handler.Callback {
| 职责 | 说明 |
|---|---|
| 输入法生命周期管理 | 绑定/解绑输入法服务 |
| 输入法切换 | 响应切换输入法请求 |
| 焦点管理 | 跟踪当前焦点窗口和输入目标 |
| 显示/隐藏控制 | 处理 showSoftInput/hideSoftInput 请求 |
| 会话管理 | 管理 InputMethodSession |
3.2 InputMethodService (IMS)
InputMethodService 是输入法应用的基类,运行在独立的输入法进程。
源码位置:frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
/**
* InputMethodService provides a standard implementation of an InputMethod,
* which final implementations can derive from and customize.
*
* <p>In addition to the normal Service lifecycle methods, this class
* introduces some new specific callbacks:</p>
* <ul>
* <li> {@link #onInitializeInterface()} for user-interface initialization
* <li> {@link #onBindInput} to find out about switching to a new client
* <li> {@link #onStartInput} to deal with an input session starting
* <li> {@link #onCreateInputView()}, {@link #onCreateCandidatesView()}
* <li> {@link #onStartInputView(EditorInfo, boolean)}
* </ul>
*/
public class InputMethodService extends AbstractInputMethodService {
核心职责:
┌─────────────────────────────────────────────────────────────────────────────┐
│ InputMethodService 职责 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 实现 IInputMethod 接口 (Binder 服务端) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ InputMethodImpl │ │
│ │ - bindInput() → 绑定到应用 │ │
│ │ - unbindInput() → 解除绑定 │ │
│ │ - startInput() → 开始输入会话 │ │
│ │ - createSession() → 创建会话 │ │
│ │ - showSoftInput() → 显示软键盘 │ │
│ │ - hideSoftInput() → 隐藏软键盘 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. 实现 IInputMethodSession 接口 (Session 服务端) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ InputMethodSessionImpl │ │
│ │ - dispatchKeyEvent() → 接收按键事件 │ │
│ │ - updateCursor() → 更新光标位置 │ │
│ │ - updateExtractedText() → 更新提取文本 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ 3. 管理 UI 组件 │
│ - SoftInputWindow (软键盘窗口容器) │
│ - onCreateInputView() (创建键盘视图) │
│ - onCreateCandidatesView() (创建候选词视图) │
│ │
│ 4. 与应用通信 (通过 InputConnection) │
│ - commitText() → 提交文字 │
│ - sendKeyEvent() → 发送按键事件给应用 │
│ - getTextBeforeCursor() → 获取光标前文字 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
3.3 SoftInputWindow
SoftInputWindow 是输入法窗口的容器,继承自 Dialog。
源码位置:frameworks/base/core/java/android/inputmethodservice/SoftInputWindow.java
/**
* A {@link SoftInputWindow} is a {@link Dialog} that is intended to be used
* for a top-level input method window. It will be displayed along the edge
* of the screen, moving the application user interface away from it so that
* the focused item is always visible.
*/
final class SoftInputWindow extends Dialog {
private final KeyEvent.DispatcherState mDispatcherState;
private final InputMethodService mService;
窗口特点:
- 窗口类型:
TYPE_INPUT_METHOD - 拥有独立的 ViewRootImpl
- 独立的事件分发链
- Z-Order 高于应用窗口
四、Binder 接口设计
输入法框架通过多个 Binder 接口实现跨进程通信。
┌─────────────────────────────────────────────────────────────────────────────┐
│ Binder 接口总览 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. IInputMethodManager │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 方向: 应用进程 → IMMS (system_server) │ │
│ │ 方法: startInput(), switchInputMethod(), showInputMethod() │ │
│ │ 作用: 应用请求启动/切换输入法 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. IInputMethod │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 方向: IMMS → 输入法进程 │ │
│ │ 方法: bindInput(), startInput(), createSession() │ │
│ │ 作用: IMMS 绑定输入法、创建会话 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 3. IInputMethodSession │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 方向: ViewRootImpl → 输入法进程 (KeyEvent 分发) │ │
│ │ 方法: dispatchKeyEvent(), updateCursor() │ │
│ │ 作用: 向输入法发送按键事件、光标信息 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 4. IInputContext │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 方向: 输入法进程 → ViewRootImpl (文字提交) │ │
│ │ 方法: commitText(), sendKeyEvent(), getTextBeforeCursor() │ │
│ │ 作用: 输入法提交文字、发送事件给应用 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 5. IWindowSession │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 方向: 所有进程 → WMS │ │
│ │ 方法: addToDisplay(), relayout() │ │
│ │ 作用: 输入法窗口的添加、布局、层级管理 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
架构图:
┌──────────────────────────────────────────────────────────────────────────────┐
│ 应用进程 A │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ Activity / View │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ InputMethodManager (IMM) ◄───────┐ │ │
│ │ │ │ │ │
│ │ │ IInputMethodManager │ Binder │ │
│ │ ▼ │ │ │
│ │ ViewRootImpl │ │ │
│ │ │ │ │ │
│ │ │ IInputMethodSession ───────┼────────┐ │ │
│ └───────┼────────────────────────────┼────────┼──────────────────────────┘ │
│ │ │ │ │
└──────────┼────────────────────────────┼────────┼──────────────────────────────┘
│ │ │
│ Binder │ │ Binder
▼ │ ▼
┌──────────────────────────────────────┼────────┼──────────────────────────────┐
│ system_server│ │ │
│ ┌────────────────────────────────────┼────────┼──────────────────────────┐ │
│ │ InputMethodManagerService (IMMS) │ │ │
│ │ │ │ │ │
│ │ │ 管理 │ │ │
│ │ ▼ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐│ │ │
│ │ │ mMethodMap │ │ mCurMethod ││◄─── 当前激活的输入法 │ │
│ │ │ (所有输入法列表) │ │ (当前输入法代理) ││ │ │
│ │ └─────────────────┘ └─────────────────┘│ │ │
│ │ │ │ │ │
│ │ │ 与 WMS 交互 │ │ │
│ │ ▼ │ │ │
│ │ WindowManagerService │ │ │
│ │ - addWindow (InputMethodWindow) │ │ │
│ │ - 窗口层级管理 (TYPE_INPUT_METHOD) │ │ │
│ │ - 焦点管理 │ │ │
│ └─────────────────────────────────────────────┼──────────────────────────┘ │
│ │ │
└────────────────────────────────────────────────┼──────────────────────────────┘
│
│ Binder (IInputMethod)
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ 输入法进程 (IME Process) │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ InputMethodService (IMS) │ │
│ │ │ │ │
│ │ ├── InputMethodImpl (Binder 服务端) │ │
│ │ │ └── IInputMethod.Stub │ │
│ │ │ │ │
│ │ ├── InputMethodSessionImpl (Session 服务端) │ │
│ │ │ └── IInputMethodSession.Stub │ │
│ │ │ └── dispatchKeyEvent() ◄───── 接收按键事件 │ │
│ │ │ │ │
│ │ ├── SoftInputWindow (输入法窗口) │ │
│ │ │ └── TYPE_INPUT_METHOD │ │
│ │ │ │ │
│ │ └── InputConnection (与应用通信) │ │
│ │ └── commitText(), sendKeyEvent() ────► 发送文字/事件给应用 │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
六、WMS 与输入法窗口的交互
6.1 输入法窗口层级
输入法窗口是特殊类型的系统窗口:
// WindowManager.LayoutParams
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW + 11;
窗口 Z-Order 层级:
┌─────────────────────────────────────────────────────────────────┐
│ 最高层 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ TYPE_STATUS_BAR_OVERLAY │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ TYPE_NAVIGATION_BAR │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ TYPE_INPUT_METHOD ◄─── 输入法窗口 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ TYPE_APPLICATION (应用窗口) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ 最低层 │
└─────────────────────────────────────────────────────────────────┘
6.2 IME Target 体系
源码位置:frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
系统中定义了两个 IME Target 常量,但实际维护了三个相关变量:
// ═══════════════════════════════════════════════════════════════
// 常量定义(用于 getImeTarget() 的参数)
// ═══════════════════════════════════════════════════════════════
/**
* Used by {@link #getImeTarget} to return the IME target window
* which the IME container (surface layer) is z-ordered on
* top of for adjusting input method window surface layer Z-Ordering.
*/
static final int IME_TARGET_LAYERING = 0;
/**
* Used by {@link #getImeTarget} to return the IME target which controls
* the IME insets visibility and animation.
*/
static final int IME_TARGET_CONTROL = 2;
// ═══════════════════════════════════════════════════════════════
// 实际变量
// ═══════════════════════════════════════════════════════════════
/**
* This just indicates the window the input method is on top of,
* not necessarily the window its input is going to.
*/
WindowState mImeLayeringTarget; // ← 层级目标
/**
* The window which receives input from the input method.
*/
private InputTarget mImeInputTarget; // ← 输入目标
/**
* This controls the visibility and animation of the input method window.
*/
InsetsControlTarget mImeControlTarget; // ← 控制目标
三者关系图解:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 三个 IME Target 的含义与关系 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ mImeLayeringTarget (WindowState) │ │
│ │ ══════════════════════════════════ │ │
│ │ 含义: IME 窗口的层级参照,IME Surface 位于这个窗口之上 │ │
│ │ 来源: computeImeTarget() 遍历查找得出 │ │
│ │ 用途: 决定 IME 窗口的 Z-Order(层级) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ 通常指向同一个窗口 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ mImeInputTarget (InputTarget) │ │
│ │ ═══════════════════════════════ │ │
│ │ 含义: 接收输入法输入的窗口 │ │
│ │ 来源: 由 ImeInsetsSourceProvider 设置 │ │
│ │ 用途: 决定哪个窗口接收 KeyEvent、更新光标信息等 │ │
│ │ │ │
│ │ 注: 通常和 mImeLayeringTarget 指向同一个窗口 │ │
│ │ 但在 EmbeddedWindow 场景下可能不同 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 控制关系 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ mImeControlTarget (InsetsControlTarget) │ │
│ │ ═════════════════════════════════════ │ │
│ │ 含义: 控制 IME 显示/隐藏动画的窗口 │ │
│ │ 来源: 由 ImeInsetsSourceProvider 计算 │ │
│ │ 用途: 控制 IME Insets 的可见性和动画 │ │
│ │ │ │
│ │ 注: 通常指向 mImeInputTarget, │ │
│ │ 但在某些场景(如分屏)可能指向其他窗口 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
三者对比:
| 变量 | 类型 | 计算方式 | 用途 |
|---|---|---|---|
mImeLayeringTarget | WindowState | computeImeTarget() 遍历查找 | 决定 IME 窗口的 Z-Order |
mImeInputTarget | InputTarget | ImeInsetsSourceProvider 设置 | 接收输入的实际窗口 |
mImeControlTarget | InsetsControlTarget | ImeInsetsSourceProvider 设置 | 控制 IME 显示/隐藏动画 |
6.3 computeImeTarget() 计算逻辑
焦点切换时,WMS 会调用 computeImeTarget() 计算 IME 的层级目标:
// DisplayContent.java
WindowState computeImeTarget(boolean updateImeTarget) {
if (mInputMethodWindow == null) {
// 没有 IME 窗口,返回 null
if (updateImeTarget) {
setImeLayeringTargetInner(null);
}
return null;
}
// 遍历所有窗口,找到第一个 canBeImeTarget() 返回 true 的窗口
WindowState target = getWindow(mComputeImeTargetPredicate);
// mComputeImeTargetPredicate = w -> w.canBeImeTarget()
if (updateImeTarget) {
setImeLayeringTargetInner(target); // ← 更新 mImeLayeringTarget
}
return target;
}
canBeImeTarget() 判断条件:
// WindowState.java
boolean canBeImeTarget() {
// 1. IME 窗口本身不能是 IME Target
if (mIsImWindow) return false;
// 2. 画中画模式的窗口不能是 IME Target
if (inPinnedWindowingMode()) return false;
// 3. 截图窗口不能是 IME Target
if (mAttrs.type == TYPE_SCREENSHOT) return false;
// 4. 窗口必须是可聚焦的
if (!windowsAreFocusable) return false;
// 5. Task 必须是可聚焦的
if (task != null && !task.isFocusable()) return false;
// 6. FLAG_NOT_FOCUSABLE 和 FLAG_ALT_FOCUSABLE_IM 的组合判断
final int fl = mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)) {
return false;
}
return true;
}
焦点切换时的调用流程:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 焦点切换时的 IME Target 更新流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ViewRootImpl 焦点变化 │
│ │ │
│ ▼ │
│ WMS.updateFocusedWindowLocked() │
│ │ │
│ ▼ │
│ DisplayContent.updateFocusedWindowLocked() │
│ │ │
│ ├── 查找新的焦点窗口 │
│ │ │
│ ├── computeImeTarget(true) │
│ │ │ │
│ │ ▼ │
│ │ 遍历所有窗口,找第一个 canBeImeTarget() 的窗口 │
│ │ │ │
│ │ ▼ │
│ │ setImeLayeringTargetInner(target) │
│ │ │ │
│ │ ├── 更新 mImeLayeringTarget │
│ │ │ │
│ │ ├── 调整 IME Surface 的父容器 │
│ │ │ (reparent 到 target 所在的 DisplayArea) │
│ │ │ │
│ │ └── 触发 ImeInsetsSourceProvider 更新 │
│ │ (间接影响 mImeControlTarget) │
│ │ │
│ └── assignWindowLayers() ← 重新计算窗口层级 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
6.4 典型场景下的三者关系
普通场景:三者指向同一个窗口
mImeLayeringTarget ──┐
│
mImeInputTarget ─────┼──► EditText 所在的 WindowState
│
mImeControlTarget ───┘
EmbeddedWindow 场景:可能不同
mImeLayeringTarget ──────► Activity 的主窗口 (决定 Z-Order)
mImeInputTarget ─────────► EmbeddedWindow (实际接收输入)
mImeControlTarget ───────► Activity 的主窗口 (控制显示/隐藏动画)
6.5 ImeInsetsSourceProvider
负责管理 IME 的 Insets(内边距),确保应用窗口不会被输入法遮挡。
源码位置:frameworks/base/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
/**
* Controller for IME inset source on the server. It's called provider as it provides
* the {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}.
*/
final class ImeInsetsSourceProvider extends InsetsSourceProvider {
/** The target that requested to show the IME */
private InsetsControlTarget mImeRequester;
/** @see #isImeShowing() */
private boolean mImeShowing;
七、输入法显示/隐藏流程
7.1 输入法显示流程
┌─────────────────────────────────────────────────────────────────────────────┐
│ 输入法显示完整流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 应用请求显示输入法 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ EditText.onFocusChanged() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ InputMethodManager.showSoftInput() │ │
│ │ │ │ │
│ │ │ Binder IPC (IInputMethodManager) │ │
│ │ ▼ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Step 2: IMMS 处理显示请求 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ InputMethodManagerService.showSoftInput() │ │
│ │ │ │ │
│ │ │ // 检查权限、焦点等 │ │
│ │ ▼ │ │
│ │ showSoftInputLocked() │ │
│ │ │ │ │
│ │ │ // 计算是否需要显示 │ │
│ │ ▼ │ │
│ │ setImeVisibilityOnFocusedWindowClient(true) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Step 3: 通知输入法显示 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ DefaultImeVisibilityApplier.performShowIme() │ │
│ │ │ │ │
│ │ │ Binder IPC (IInputMethod) │ │
│ │ ▼ │ │
│ │ IInputMethodInvoker.showSoftInput() │ │
│ │ │ │ │
│ │ │ // 调用输入法进程 │ │
│ │ ▼ │ │
│ │ InputMethodService.showSoftInput() │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Step 4: 输入法显示窗口 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ InputMethodService.showWindow() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ SoftInputWindow.setVisibility(VISIBLE) │ │
│ │ │ │ │
│ │ │ // 通过 ViewRootImpl 通知 WMS │ │
│ │ ▼ │ │
│ │ WMS.relayoutWindow() │ │
│ │ │ │ │
│ │ │ // 更新窗口可见性 │ │
│ │ ▼ │ │
│ │ ImeInsetsSourceProvider.setServerVisible(true) │ │
│ │ │ │ │
│ │ │ // 通知应用 IME 已显示,调整布局 │ │
│ │ ▼ │ │
│ │ InsetsSourceConsumer.onStateChanged() │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Step 5: WMS 更新 IME Target │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ DisplayContent.setInputMethodTarget() │ │
│ │ │ │ │
│ │ ├── 更新 mImeLayeringTarget (层级目标) │ │
│ │ │ │ │
│ │ └── 更新 mImeControlTarget (控制目标) │ │
│ │ │ │
│ │ // 调整应用窗口布局,避免被 IME 遮挡 │ │
│ │ InsetsPolicy.onInsetsModified() │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
7.2 输入法隐藏流程
┌─────────────────────────────────────────────────────────────────────────────┐
│ 输入法隐藏完整流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 应用请求隐藏输入法 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ InputMethodManager.hideSoftInputFromWindow() │ │
│ │ │ │ │
│ │ │ Binder IPC │ │
│ │ ▼ │ │
│ │ InputMethodManagerService.hideSoftInput() │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Step 2: IMMS 处理隐藏请求 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ hideSoftInputLocked() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ setImeVisibilityOnFocusedWindowClient(false) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ DefaultImeVisibilityApplier.performHideIme() │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Step 3: 通知输入法隐藏 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ IInputMethodInvoker.hideSoftInput() │ │
│ │ │ │ │
│ │ │ Binder IPC │ │
│ │ ▼ │ │
│ │ InputMethodService.hideSoftInput() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ InputMethodService.hideWindow() │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Step 4: WMS 更新状态 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ SoftInputWindow.setVisibility(INVISIBLE) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ImeInsetsSourceProvider.setServerVisible(false) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ // 通知应用 IME 已隐藏,恢复布局 │ │
│ │ InsetsSourceConsumer.onStateChanged() │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
7.3 窗口焦点变化触发输入法
最常见的场景是用户点击 EditText:
// InputMethodManagerService.java
// 窗口获得焦点时触发
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags, ...);
焦点触发流程:
ViewRootImpl.windowFocusChanged()
│
│ (焦点变化)
▼
WMS.updateFocusedWindowLocked()
│
│ 检查 softInputMode
▼
WMS.notifyFocusChangedToInputMethod()
│
│ Binder IPC
▼
IMMS.windowGainedFocus() / startInputOrWindowGainedFocus()
│
│ 根据 softInputMode 决定是否显示 IME
│ - SOFT_INPUT_STATE_VISIBLE: 显示
│ - SOFT_INPUT_STATE_HIDDEN: 隐藏
│ - SOFT_INPUT_STATE_UNSPECIFIED: 根据情况决定
▼
ImeVisibilityStateComputer.computeState()
│
│ 返回 STATE_SHOW_IME 或 STATE_HIDE_IME
▼
DefaultImeVisibilityApplier.performShowIme() / performHideIme()
7.4 输入法窗口创建时机
// SoftInputWindow.java
void setToken(IBinder token) {
switch (mWindowState) {
case WindowState.TOKEN_PENDING:
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.token = token; // 设置窗口 token
getWindow().setAttributes(lp);
// 设置 INVISIBLE 并调用 show(),确保窗口被添加到 WMS
getWindow().getDecorView().setVisibility(View.INVISIBLE);
show(); // 触发 Window 添加到 WMS
return;
}
}
关键点:输入法窗口在服务绑定时就创建了,但设置为 INVISIBLE,真正显示时只是改变可见性。
八、完整架构图
┌─────────────────────────────────────────────────────────────────────────────┐
│ 输入法框架完整架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ Binder IPC ┌─────────────────────────────────────┐ │
│ │ 应用进程 │ ◄──────────────► │ system_server │ │
│ │ │ │ │ │
│ │ IMM │ IInputMethodManager │ IMMS (管理服务) │ │
│ │ ViewRootImpl│ ◄──────────────► │ │ │ │
│ │ │ IInputContext │ ├── 管理输入法生命周期 │ │
│ │ │ ◄──────────────► │ ├── 处理切换/显示/隐藏请求 │ │
│ │ │ │ └── 维护焦点状态 │ │
│ │ │ │ │ │
│ └─────────────┘ │ ┌─────────────────────────┐ │ │
│ ▲ │ │ WMS (窗口管理) │ │ │
│ │ │ │ │ │ │
│ │ InputConnection │ │ IME_TARGET_LAYERING │ │ │
│ │ (commitText等) │ │ IME_TARGET_CONTROL │ │ │
│ │ │ │ ImeInsetsSourceProvider│ │ │
│ ┌──────┴──────────┐ │ └─────────────────────────┘ │ │
│ │ │ │ │ │
│ │ 输入法进程 │ Binder IPC └─────────────────────────────────────┘ │
│ │ │ ◄──────────► │
│ │ InputMethodService IInputMethod │
│ │ │ │
│ │ - SoftInputWindow│ │
│ │ - ViewRootImpl │ │
│ │ - 键盘 UI │ │
│ │ - 文字处理逻辑 │ │
│ │ │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
参考资料
- 源码:
frameworks/base/services/core/java/com/android/server/inputmethod/ - 源码:
frameworks/base/core/java/android/inputmethodservice/ - 源码:
frameworks/base/services/core/java/com/android/server/wm/ - 相关文档:
VRI事件分发责任链.md