第四板块:Android 输入系统与触控事件 | 第十六篇:按键分发与软键盘(IME)的窗口协同

24 阅读6分钟

第四板块:Android 输入系统与触控事件 | 第十六篇:按键分发与软键盘(IME)的窗口协同

所属板块:第四板块 — Android 输入系统与触控事件

前置知识:第十五篇中的 InputReader 解析、InputDispatcher 投递、ANR 机制、窗口焦点管理

本篇定位:这是输入系统的逻辑中枢。如果说触控是连续流,那么按键就是离散的指令。本篇将彻底拆解 物理按键与软键盘按键的统一分发模型输入法管理器(InputMethodManager)的窗口注入机制IME 与 App 窗口的 Z-order 协同Back 键的拦截与返回栈无障碍服务(Accessibility)对输入的劫持。我们将深入 System Server应用进程 的交互细节,揭示为何软键盘弹出时界面会压缩,以及 Back 键是如何一路回溯的。


1. 核心结论先行(Thesis Statement)

Android 的按键系统是一个基于焦点的定向广播模型

  • 按键的本质:一个离散的指令信号。无论是物理按键(Power, Volume)还是虚拟按键(软键盘上的字母),最终都被抽象为 KeyEvent
  • IME 的本质:一个特殊的输入法应用。它运行在独立的进程(com.android.inputmethod.latin),通过 InputMethodManagerService (IMMS) 与系统服务通信,并作为一个 Input Window 叠加在普通应用之上。
  • 窗口协同的核心Token 共享。IME 窗口与应用窗口共享同一个 WindowToken,确保系统能正确计算两者的位置关系和动画。
  • Back 键的本质:一个系统级的导航指令。它不单纯是关闭当前 Activity,而是通知 ActivityStack 将当前 Task 移出前台或销毁 Activity。

2. 按键分发的全链路架构

2.1 从物理按键到应用回调

graph TB
    subgraph Hardware ["硬件层"]
        Key["物理按键 / 软键盘"]
    end

    subgraph Kernel ["Linux 内核"]
        Driver["Keyboard Driver"]
        Node["/dev/input/eventX"]
    end

    subgraph SystemServer ["System Server"]
        Reader["InputReader"]
        Dispatcher["InputDispatcher"]
        IMMS["InputMethodManagerService"]
        Policy["PhoneWindowManager"]
    end

    subgraph IMEProc ["输入法进程"]
        IME["InputMethodService"]
        Editor["InputConnection"]
    end

    subgraph AppProc ["应用进程"]
        ViewRoot["ViewRootImpl"]
        Activity["Activity"]
        View["EditText / View"]
    end

    Key -->|"按下"| Driver
    Driver -->|"写入"| Node
    Node -->|"poll()"| Reader
    Reader -->|"KeyEvent"| Dispatcher
    Dispatcher -->|"1. 策略拦截"| Policy
    Policy -->|"2. 系统处理 (Home/Back)"| Dispatcher
    Dispatcher -->|"3. 分发"| ViewRoot
    ViewRoot -->|"4. 焦点判断"| Activity
    Activity -->|"5. 分发"| View

    %% 软键盘路径
    View -->|"获取焦点"| IMMS
    IMMS -->|"绑定"| IME
    IME -->|"显示窗口"| Dispatcher
    IME -->|"输入文字"| Editor
    Editor -->|"commitText()"| View

2.2 核心角色职责表

角色运行进程职责
InputMethodManagerService (IMMS)System Server管理所有输入法,控制 IME 窗口的显示/隐藏。
InputMethodService (IMS)输入法进程实现具体的输入法 UI 和逻辑(如搜狗、百度输入法)。
InputConnection应用进程应用与输入法之间的通信通道,用于传输文本。
PhoneWindowManagerSystem Server决定系统按键(Home, Back, Recent)的行为。

3. 物理按键与软键盘的统一模型

3.1 KeyEvent 的构成

无论是物理按键还是软键盘,都使用同一个 KeyEvent 类。

学术定义

  • KeyCode: 按键编码(如 KEYCODE_A, KEYCODE_ENTER, KEYCODE_BACK)。
  • ScanCode: 硬件扫描码(由驱动上报,系统无关)。
  • MetaState: 修饰键状态(Shift, Ctrl, Alt, CapsLock)。
  • Action: ACTION_DOWN(按下)或 ACTION_UP(抬起)。

3.2 分发优先级

InputDispatcher 分发按键时,遵循严格的优先级:

  1. PhoneWindowManager: 首先拦截系统键(Home, Back, Power)。如果返回 true,则不分发给应用。
  2. Focus Window: 如果系统不拦截,分发给当前获得焦点的窗口。
  3. Input Method: 如果焦点在 EditText 上,按键通常会先发给 IME 进行处理(联想、候选词)。

4. 软键盘(IME)的窗口协同机制

4.1 IME 窗口的特殊性

IME 窗口不是一个普通的 Activity 窗口,而是一个 System Alert Window

特性普通 Activity 窗口IME 窗口
Window TypeTYPE_APPLICATIONTYPE_INPUT_METHOD
Z-order中等极高 (覆盖在应用之上)
TokenActivity Token共享 Activity Token
生命周期随 Activity独立,但依附于客户端

4.2 Token 共享机制

IME 窗口必须与它所服务的 Activity 绑定。

// InputMethodManagerService.java
public void showSoftInput(IBinder token, int flags) {
    // token 是 Activity 的 Window Token
    // 确保 IME 窗口显示在正确的 Activity 之上
    mCurToken = token;
    mWindowManager.addView(mImeWindow, mWindowAttributes);
}

学术定义

  • Window Token: 一个 Binder 对象,标识一个窗口组。IME 窗口使用 Activity 的 Token,意味着系统知道它们属于同一个“会话”。
  • Z-order 调整:当 IME 显示时,系统会调整 Activity 窗口的 Bottom 坐标,使其避开 IME,这就是所谓的“软键盘遮挡”。

4.3 InputConnection 的通信管道

应用与输入法之间不是直接通信,而是通过 InputConnection

sequenceDiagram
    participant IME as 输入法进程
    participant App as 应用进程 (EditText)
    participant IC as InputConnection

    IME->>IC: getTextBeforeCursor(10)
    IC->>App: 查询 EditText 的文本
    App-->>IC: 返回 "Hello"
    IC-->>IME: "Hello"

    IME->>IC: commitText(" World", 1)
    IC->>App: 插入文本
    App->>App: 更新 EditText 显示

学术定义

  • Binder IPC: InputConnection 实际上是一个 Binder 接口,允许 IME 跨进程操作应用的文本。
  • Batch Edit: 为了防止频繁 IPC,IME 会使用 beginBatchEdit()endBatchEdit() 包裹多次文本操作。

5. Back 键的导航逻辑

5.1 Back 键的分发流程

Back 键是唯一一个具有破坏性的按键。

sequenceDiagram
    participant HW as 硬件
    participant Dispatcher as InputDispatcher
    participant Policy as PhoneWindowManager
    participant AMS as ActivityManagerService
    participant Activity as Activity

    HW->>Dispatcher: KeyEvent(KEYCODE_BACK)
    Dispatcher->>Policy: interceptKeyBeforeQueueing()
    Policy->>Policy: 判断是否系统处理
    alt 系统处理 (如下拉状态栏)
        Policy->>Dispatcher: 消费事件
    else 应用处理
        Dispatcher->>Activity: dispatchKeyEvent(KeyEvent)
        Activity->>Activity: onKeyDown()
        alt Activity 未消费
            Activity->>AMS: finishActivity()
            AMS->>AMS: 调整 Task 栈
        end
    end

5.2 返回栈(Back Stack)算法

AMS 维护着 Task 和 Activity 的栈结构。

学术定义

  • Task: 一个独立的任务栈,包含一系列 Activity。
  • Back Stack: Task 内部的 Activity 栈。
  • finish(): 销毁当前 Activity,栈顶指针下移。
  • singleTask: 如果目标 Activity 已在栈中,将其之上的所有 Activity 出栈。

6. 无障碍服务(Accessibility)的劫持

6.1 Accessibility 的优先级

无障碍服务可以拦截所有输入事件

学术定义

  • AccessibilityEvent: 系统发出的事件(如窗口变化、点击)。
  • AccessibilityService: 监听这些事件的服务(如 TalkBack)。
  • Input Interception: 无障碍服务可以请求 Input Filter 权限,修改或屏蔽按键事件。

6.2 对按键分发的影响

如果启用了无障碍服务(如 TalkBack),按键分发流程会被改变:

  1. InputDispatcher 将按键事件同时发送给应用和无障碍服务。
  2. 无障碍服务可能会将 KEYCODE_DPAD_CENTER 解释为“点击”。
  3. 应用收到的可能不是原始的 KeyEvent。

7. 关键源码解析

7.1 PhoneWindowManager 的拦截

// PhoneWindowManager.java
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
    final int keyCode = event.getKeyCode();

    if (keyCode == KeyEvent.KEYCODE_HOME) {
        // Home 键永远由系统处理
        launchHomeFromHotKey();
        return 0; // 不分发给应用
    }

    if (keyCode == KeyEvent.KEYCODE_BACK) {
        // Back 键逻辑
        if (mWindowManager.isKeyguardShowing()) {
            // 锁屏下特殊处理
            return 0;
        }
    }
    return 1; // 允许分发给应用
}

7.2 InputMethodManager 的绑定

// InputMethodManager.java
public void startInput(IBinder token, EditorInfo attribute) {
    // 1. 检查当前 IME
    if (mCurMethod == null) {
        // 2. 绑定 IME 服务
        mService.startInput(mClient, token, attribute);
    }
    // 3. 建立 InputConnection
    mCurMethod.createSession(mCurId, mCurSeq, mCurChannel);
}

8. 输入系统的边界与限制

8.1 按键冲突

冲突场景学术解释
游戏手柄 vs 系统按键游戏通常需要屏蔽系统键,通过 onKeyDown 返回 true
无障碍 vs 应用无障碍服务优先级更高,可能覆盖应用的按键逻辑。
多窗口模式分屏时,Back 键作用于当前获得焦点的窗口。

8.2 软键盘的显示模式

模式Window Flag行为
AdjustResizeSOFT_INPUT_ADJUST_RESIZE调整 Activity 窗口大小,避免遮挡。
AdjustPanSOFT_INPUT_ADJUST_PAN平移 Activity 窗口,确保焦点控件可见。
NothingSOFT_INPUT_ADJUST_NOTHING不调整窗口,可能遮挡。

9. 本篇总结(Knowledge Closure)

关键点纯学术定义
按键的本质离散的指令信号,统一抽象为 KeyEvent
IME 的本质特殊的系统窗口,通过 InputConnection 与应用通信。
Token 共享IME 窗口与应用窗口共享 Token,确保协同。
Back 键逻辑系统导航指令,由 AMS 管理 Task 栈的回退。
无障碍劫持无障碍服务拥有最高优先级的输入拦截权。

10. 第四板块结语

至此,第四板块:Android 输入系统与触控事件 已全部完结。

我们从 InputReader 的原始数据解析 出发,深入 InputDispatcher 的投递策略,探索 Touch 事件的多点触控,最终抵达 按键分发与软键盘的窗口协同

我们揭示了 Android 输入系统的设计哲学:用分层拦截保障系统安全,用 Token 机制保障窗口协同,用 Binder 通信解耦输入法与应用。

下一篇预告第五板块:Android 系统服务与电源管理 | 第十七篇:Power Manager Service 与 WakeLock 机制