Android 输入法框架浅析

0 阅读9分钟

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)普通应用用户普通应用用户
生命周期系统运行期间一直存在可被杀死、重启随应用生命周期
核心类InputMethodManagerServiceInputMethodServiceInputMethodManager
职责管理输入法实现输入法 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,                                       │   │
│  │      但在某些场景(如分屏)可能指向其他窗口                              │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

三者对比

变量类型计算方式用途
mImeLayeringTargetWindowStatecomputeImeTarget() 遍历查找决定 IME 窗口的 Z-Order
mImeInputTargetInputTargetImeInsetsSourceProvider 设置接收输入的实际窗口
mImeControlTargetInsetsControlTargetImeInsetsSourceProvider 设置控制 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