第四板块: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
- 策略拦截
- 系统处理 (Home/Back)
- 分发
- 焦点判断
- 分发
获取焦点
绑定
显示窗口
输入文字
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 分发按键时,遵循严格的优先级:
- PhoneWindowManager : 首先拦截系统键(Home, Back, Power)。如果返回
true,则不分发给应用。 - Focus Window: 如果系统不拦截,分发给当前获得焦点的窗口。
- 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),按键分发流程会被改变:
- InputDispatcher 将按键事件同时发送给应用和无障碍服务。
- 无障碍服务可能会将
KEYCODE_DPAD_CENTER解释为"点击"。 - 应用收到的可能不是原始的 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 机制