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

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

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

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

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


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 从物理按键到应用回调

#mermaid-svg-1CauB3Lpc92rFcY9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1CauB3Lpc92rFcY9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1CauB3Lpc92rFcY9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1CauB3Lpc92rFcY9 .error-icon{fill:#552222;}#mermaid-svg-1CauB3Lpc92rFcY9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1CauB3Lpc92rFcY9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1CauB3Lpc92rFcY9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1CauB3Lpc92rFcY9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1CauB3Lpc92rFcY9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1CauB3Lpc92rFcY9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1CauB3Lpc92rFcY9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1CauB3Lpc92rFcY9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1CauB3Lpc92rFcY9 .marker.cross{stroke:#333333;}#mermaid-svg-1CauB3Lpc92rFcY9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1CauB3Lpc92rFcY9 p{margin:0;}#mermaid-svg-1CauB3Lpc92rFcY9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1CauB3Lpc92rFcY9 .cluster-label text{fill:#333;}#mermaid-svg-1CauB3Lpc92rFcY9 .cluster-label span{color:#333;}#mermaid-svg-1CauB3Lpc92rFcY9 .cluster-label span p{background-color:transparent;}#mermaid-svg-1CauB3Lpc92rFcY9 .label text,#mermaid-svg-1CauB3Lpc92rFcY9 span{fill:#333;color:#333;}#mermaid-svg-1CauB3Lpc92rFcY9 .node rect,#mermaid-svg-1CauB3Lpc92rFcY9 .node circle,#mermaid-svg-1CauB3Lpc92rFcY9 .node ellipse,#mermaid-svg-1CauB3Lpc92rFcY9 .node polygon,#mermaid-svg-1CauB3Lpc92rFcY9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1CauB3Lpc92rFcY9 .rough-node .label text,#mermaid-svg-1CauB3Lpc92rFcY9 .node .label text,#mermaid-svg-1CauB3Lpc92rFcY9 .image-shape .label,#mermaid-svg-1CauB3Lpc92rFcY9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-1CauB3Lpc92rFcY9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1CauB3Lpc92rFcY9 .rough-node .label,#mermaid-svg-1CauB3Lpc92rFcY9 .node .label,#mermaid-svg-1CauB3Lpc92rFcY9 .image-shape .label,#mermaid-svg-1CauB3Lpc92rFcY9 .icon-shape .label{text-align:center;}#mermaid-svg-1CauB3Lpc92rFcY9 .node.clickable{cursor:pointer;}#mermaid-svg-1CauB3Lpc92rFcY9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1CauB3Lpc92rFcY9 .arrowheadPath{fill:#333333;}#mermaid-svg-1CauB3Lpc92rFcY9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1CauB3Lpc92rFcY9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1CauB3Lpc92rFcY9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1CauB3Lpc92rFcY9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1CauB3Lpc92rFcY9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1CauB3Lpc92rFcY9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1CauB3Lpc92rFcY9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1CauB3Lpc92rFcY9 .cluster text{fill:#333;}#mermaid-svg-1CauB3Lpc92rFcY9 .cluster span{color:#333;}#mermaid-svg-1CauB3Lpc92rFcY9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1CauB3Lpc92rFcY9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1CauB3Lpc92rFcY9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-1CauB3Lpc92rFcY9 .icon-shape,#mermaid-svg-1CauB3Lpc92rFcY9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1CauB3Lpc92rFcY9 .icon-shape p,#mermaid-svg-1CauB3Lpc92rFcY9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1CauB3Lpc92rFcY9 .icon-shape .label rect,#mermaid-svg-1CauB3Lpc92rFcY9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1CauB3Lpc92rFcY9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1CauB3Lpc92rFcY9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1CauB3Lpc92rFcY9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用进程
输入法进程
System Server
Linux 内核
硬件层
按下
写入
poll()
KeyEvent

  1. 策略拦截
  2. 系统处理 (Home/Back)
  3. 分发
  4. 焦点判断
  5. 分发
    获取焦点
    绑定
    显示窗口
    输入文字
    commitText()
    物理按键 / 软键盘
    Keyboard Driver
    /dev/input/eventX
    InputReader
    InputDispatcher
    InputMethodManagerService
    PhoneWindowManager
    InputMethodService
    InputConnection
    ViewRootImpl
    Activity
    EditText / View

2.2 核心角色职责表

角色 运行进程 职责
InputMethodManagerService (IMMS) System Server 管理所有输入法,控制 IME 窗口的显示/隐藏。
InputMethodService (IMS) 输入法进程 实现具体的输入法 UI 和逻辑(如搜狗、百度输入法)。
InputConnection 应用进程 应用与输入法之间的通信通道,用于传输文本。
PhoneWindowManager System 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 Type TYPE_APPLICATION TYPE_INPUT_METHOD
Z-order 中等 极高 (覆盖在应用之上)
Token Activity Token 共享 Activity Token
生命周期 随 Activity 独立,但依附于客户端

4.2 Token 共享机制

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

java 复制代码
// 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
InputConnection 应用进程 (EditText) 输入法进程 InputConnection 应用进程 (EditText) 输入法进程 #mermaid-svg-tEOE1NhhSYcht92F{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tEOE1NhhSYcht92F .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tEOE1NhhSYcht92F .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tEOE1NhhSYcht92F .error-icon{fill:#552222;}#mermaid-svg-tEOE1NhhSYcht92F .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tEOE1NhhSYcht92F .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tEOE1NhhSYcht92F .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tEOE1NhhSYcht92F .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tEOE1NhhSYcht92F .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tEOE1NhhSYcht92F .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tEOE1NhhSYcht92F .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tEOE1NhhSYcht92F .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tEOE1NhhSYcht92F .marker.cross{stroke:#333333;}#mermaid-svg-tEOE1NhhSYcht92F svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tEOE1NhhSYcht92F p{margin:0;}#mermaid-svg-tEOE1NhhSYcht92F .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tEOE1NhhSYcht92F text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-tEOE1NhhSYcht92F .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tEOE1NhhSYcht92F .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-tEOE1NhhSYcht92F .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-tEOE1NhhSYcht92F .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-tEOE1NhhSYcht92F #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-tEOE1NhhSYcht92F .sequenceNumber{fill:white;}#mermaid-svg-tEOE1NhhSYcht92F #sequencenumber{fill:#333;}#mermaid-svg-tEOE1NhhSYcht92F #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-tEOE1NhhSYcht92F .messageText{fill:#333;stroke:none;}#mermaid-svg-tEOE1NhhSYcht92F .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tEOE1NhhSYcht92F .labelText,#mermaid-svg-tEOE1NhhSYcht92F .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-tEOE1NhhSYcht92F .loopText,#mermaid-svg-tEOE1NhhSYcht92F .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-tEOE1NhhSYcht92F .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tEOE1NhhSYcht92F .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-tEOE1NhhSYcht92F .noteText,#mermaid-svg-tEOE1NhhSYcht92F .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-tEOE1NhhSYcht92F .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tEOE1NhhSYcht92F .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tEOE1NhhSYcht92F .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tEOE1NhhSYcht92F .actorPopupMenu{position:absolute;}#mermaid-svg-tEOE1NhhSYcht92F .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-tEOE1NhhSYcht92F .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tEOE1NhhSYcht92F .actor-man circle,#mermaid-svg-tEOE1NhhSYcht92F line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-tEOE1NhhSYcht92F :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} getTextBeforeCursor(10)查询 EditText 的文本返回 "Hello""Hello"commitText(" World", 1)插入文本更新 EditText 显示

学术定义

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

5. Back 键的导航逻辑

5.1 Back 键的分发流程

Back 键是唯一一个具有破坏性的按键。
Activity ActivityManagerService PhoneWindowManager InputDispatcher 硬件 Activity ActivityManagerService PhoneWindowManager InputDispatcher 硬件 #mermaid-svg-FSJjN5R0RmMRWoIv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FSJjN5R0RmMRWoIv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FSJjN5R0RmMRWoIv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FSJjN5R0RmMRWoIv .error-icon{fill:#552222;}#mermaid-svg-FSJjN5R0RmMRWoIv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FSJjN5R0RmMRWoIv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FSJjN5R0RmMRWoIv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FSJjN5R0RmMRWoIv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FSJjN5R0RmMRWoIv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FSJjN5R0RmMRWoIv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FSJjN5R0RmMRWoIv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FSJjN5R0RmMRWoIv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FSJjN5R0RmMRWoIv .marker.cross{stroke:#333333;}#mermaid-svg-FSJjN5R0RmMRWoIv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FSJjN5R0RmMRWoIv p{margin:0;}#mermaid-svg-FSJjN5R0RmMRWoIv .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-FSJjN5R0RmMRWoIv text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-FSJjN5R0RmMRWoIv .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-FSJjN5R0RmMRWoIv .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-FSJjN5R0RmMRWoIv .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-FSJjN5R0RmMRWoIv .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-FSJjN5R0RmMRWoIv #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-FSJjN5R0RmMRWoIv .sequenceNumber{fill:white;}#mermaid-svg-FSJjN5R0RmMRWoIv #sequencenumber{fill:#333;}#mermaid-svg-FSJjN5R0RmMRWoIv #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-FSJjN5R0RmMRWoIv .messageText{fill:#333;stroke:none;}#mermaid-svg-FSJjN5R0RmMRWoIv .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-FSJjN5R0RmMRWoIv .labelText,#mermaid-svg-FSJjN5R0RmMRWoIv .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-FSJjN5R0RmMRWoIv .loopText,#mermaid-svg-FSJjN5R0RmMRWoIv .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-FSJjN5R0RmMRWoIv .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-FSJjN5R0RmMRWoIv .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-FSJjN5R0RmMRWoIv .noteText,#mermaid-svg-FSJjN5R0RmMRWoIv .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-FSJjN5R0RmMRWoIv .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-FSJjN5R0RmMRWoIv .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-FSJjN5R0RmMRWoIv .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-FSJjN5R0RmMRWoIv .actorPopupMenu{position:absolute;}#mermaid-svg-FSJjN5R0RmMRWoIv .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-FSJjN5R0RmMRWoIv .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-FSJjN5R0RmMRWoIv .actor-man circle,#mermaid-svg-FSJjN5R0RmMRWoIv line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-FSJjN5R0RmMRWoIv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} altActivity 未消费 alt系统处理 (如下拉状态栏)应用处理 KeyEvent(KEYCODE_BACK)interceptKeyBeforeQueueing()判断是否系统处理消费事件dispatchKeyEvent(KeyEvent)onKeyDown()finishActivity()调整 Task 栈

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 的拦截

java 复制代码
// 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 的绑定

java 复制代码
// 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 行为
AdjustResize SOFT_INPUT_ADJUST_RESIZE 调整 Activity 窗口大小,避免遮挡。
AdjustPan SOFT_INPUT_ADJUST_PAN 平移 Activity 窗口,确保焦点控件可见。
Nothing SOFT_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 机制

相关推荐
故渊at1 小时前
第三板块:Android 图形渲染与窗口体系 | 第十四篇:View 绘制体系与 RenderThread 异步渲染
android·图形渲染·ui线程·renderthread·view体系
Coffeeee2 小时前
准备升级到Android16,自适应布局应该如何适配
android·google·kotlin
神仙别闹2 小时前
基于 PHP + MySQL 图书库存管理系统
android·mysql·php
zhangphil2 小时前
Android内存回收:GC、kswapd 和 mm_vmscan_direct_reclaim概述
android
plainGeekDev2 小时前
ContentProvider → Room + Repository
android·java·kotlin
plainGeekDev2 小时前
SQLite 手动升级 → Room Migration
android·java·kotlin
MemoriKu2 小时前
Flutter 相册 APP 视频模态稳定化实战:从视频抽帧、Embedding 元数据到 Android 真机启动修复
android·开发语言·前端·flutter·架构·音视频·embedding
Che2n3JigW2 小时前
Now in Android:它不是最佳实践,而是大型 Android 工程实践的展示
android·architecture·now in android
故渊at2 小时前
第三板块:Android 图形渲染与窗口体系 | 第十三篇:SurfaceFlinger 与 VSYNC 信号机制
android·图形渲染·surfaceflinger·帧率·窗口体系