系列目录 :第一篇:从硬件到应用的事件旅程 | 第二篇:EventHub | 第三篇:InputReader | 第四篇:InputDispatcher | 第五篇:应用侧
一、为什么要研究输入系统
做过 Android 开发的朋友应该都跟 onTouchEvent() 和 dispatchTouchEvent() 打过交道。滑动冲突、点击穿透、ANR 弹窗......这些日常问题背后,都藏着一个庞大而精密的事件分发体系。
但如果你只停留在应用层的事件分发机制,很多深层问题会无从下手:
- 为什么有时候
onTouchEvent迟迟收不到事件?是 InputDispatcher 卡了还是主线程消息队列满了? - "应用无响应"弹窗背后的输入超时机制到底是怎么判定的?
- 插上一个外接键盘,Android 怎么做到"即插即用"地开始接收按键?
- 一个触摸事件从手指按下到
View.onTouchEvent()被调用,中间到底经过了多少层?
这些问题,只有把 Android 输入系统的全链路搞清楚,才能彻底理解。本系列将以 AOSP 7(android-7.1.2_r36) 为基准源码版本,带你从内核驱动一路走到应用层 View 树,逐层拆解 Android 输入子系统。
本篇是整个系列的总览与导航,先帮你建立起完整的宏观认知。
二、宏观架构:一张图看懂事件流水线
在 Android 输入系统中,一个事件的生命周期可以用一条清晰的流水线来描述:
硬件驱动 --> Linux Kernel --> EventHub --> InputReader
(触摸屏) /dev/input/ (Native) (Native)
|
NotifyArgs
|
v
APP进程 <-- InputDispatcher <-- InputManager
ViewRootImpl (socket/Channel) Service(Java)
或者用更直观的分层视角来看:
| 层级 | 组件 | 进程 | 职责 |
|---|---|---|---|
| 内核层 | Linux Input 子系统 | Kernel | 驱动硬件,向 /dev/input/eventX 写入 input_event |
| Native 采集层 | EventHub | system_server | 监听设备热插拔,读取原始事件 |
| Native 加工层 | InputReader | system_server | 将原始数据转换为 KeyEvent / MotionEvent |
| Native 分发层 | InputDispatcher | system_server | 确定目标窗口,通过 socket 发送事件,监控 ANR |
| Java 管理层 | InputManagerService | system_server | 与 WMS 联动,管理输入法、手势等策略 |
| 应用接收层 | ViewRootImpl | APP 进程 | 从 socket 接收事件,沿 View 树分发 |
三、内核层:Linux Input 子系统的角色
3.1 /dev/input/ 设备节点
当你插上一个 USB 键盘或者设备自带触摸屏时,Linux 内核会在 /dev/input/ 目录下创建对应的设备节点:
bash
$ ls /dev/input/
event0 event1 event2 event3 event4 mice mouse0
每个 eventX 都对应一个输入设备。可以用 getevent 命令实时查看原始事件:
bash
$ adb shell getevent -lt /dev/input/event2
# 触摸屏按下
[ 123456.789] EV_ABS ABS_MT_TRACKING_ID 00000045
[ 123456.789] EV_ABS ABS_MT_POSITION_X 00000320
[ 123456.789] EV_ABS ABS_MT_POSITION_Y 00000580
[ 123456.789] EV_SYN SYN_REPORT 00000000
# 触摸屏抬起
[ 123457.123] EV_ABS ABS_MT_TRACKING_ID ffffffff
[ 123457.123] EV_SYN SYN_REPORT 00000000
3.2 input_event 结构体
内核中所有输入事件都用一个统一的结构体来描述:
源码路径 :kernel/include/linux/input.h
c
struct input_event {
struct timeval time; // 时间戳
__u16 type; // 事件类型(EV_KEY、EV_ABS、EV_SYN 等)
__u16 code; // 事件编码(如 KEY_HOME、ABS_MT_POSITION_X)
__s32 value; // 事件值(按键:0抬起/1按下;坐标:具体像素值)
};
Android 后续的所有事件处理,起点都是从这个结构体开始的。
四、Native 核心层:InputFlinger 三剑客
AOSP 源码中,输入系统的 Native 核心代码位于:
frameworks/native/services/inputflinger/
├── EventHub.cpp / .h # 设备管理与原始事件读取
├── InputReader.cpp / .h # 事件加工与转换
├── InputDispatcher.cpp / .h # 事件分发与超时控制
├── InputManager.cpp / .h # 三者的协调者
├── InputListener.cpp / .h # 监听器接口
└── InputWindow.cpp / .h # 窗口信息
这三个核心组件运行在 system_server 进程中,各自有独立的线程:
源码路径 :frameworks/native/services/inputflinger/InputManager.cpp
cpp
status_t InputManager::start() {
// 先启动 InputDispatcher,再启动 InputReader
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
if (result) {
ALOGE("Could not start InputDispatcher thread due to error %d.", result);
return result;
}
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
if (result) {
ALOGE("Could not start InputReader thread due to error %d.", result);
mDispatcherThread->requestExit();
return result;
}
return OK;
}
注意两个线程都是 PRIORITY_URGENT_DISPLAY 优先级,Google 对输入响应速度要求非常高------用户每一次触摸都期望"跟手"。
4.1 EventHub:原始事件采集者
EventHub 是整个输入流水线的第一站。它的核心职责非常纯粹:
- 设备发现 :通过 Linux 的
inotify机制监听/dev/input/目录,当设备插入/拔出时自动感知 - 设备打开 :通过
ioctl获取设备能力信息(支持哪些按键、哪些坐标轴等) - 事件读取 :通过
epoll_wait监听所有设备文件描述符,有数据时用read()读取input_event - 事件上报 :将读取到的原始事件封装为
RawEvent,传递给InputReader
关键方法签名:
源码路径 :frameworks/native/services/inputflinger/EventHub.h
cpp
class EventHub {
public:
size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize);
private:
int mEpollFd; // epoll 实例
int mINotifyFd; // inotify 实例
KeyedVector<int, Device*> mDevices; // 设备列表
};
(第二篇会详细展开 EventHub 的全部细节。)
4.2 InputReader:事件加工厂
InputReader 拿到 RawEvent 后,需要完成从"内核原始数据"到"Android 可以理解的事件"的转换。这个转换过程远比想象中复杂:
- 一个触摸屏上报的是绝对坐标
(320, 580),但 Android 需要知道这是相对于哪个屏幕、坐标系是否需要旋转 - 一个外接键盘的上报是 Linux 键码
KEY_A = 30,需要转换为 Android 键码AKEYCODE_A = 29 - 多点触控需要跟踪每个手指的
trackingId,识别 DOWN / MOVE / UP / CANCEL 等动作
InputReader 通过 InputMapper 体系 来处理不同类型的设备:
InputReader
└── InputDevice (代表一个物理设备)
└── InputMapper (设备类型对应的处理策略)
├── SwitchInputMapper // 开关类
├── KeyboardInputMapper // 键盘
├── CursorInputMapper // 鼠标/轨迹球
├── TouchInputMapper // 触摸屏
│ ├── SingleTouchInputMapper
│ └── MultiTouchInputMapper
├── JoystickInputMapper // 摇杆
└── ExternalStylusInputMapper // 外接手写笔
加工完成后,事件被封装为 NotifyArgs 系列对象,通过 InputListener 接口传递给 InputDispatcher。
(第三篇会深入拆解 InputReader 和 Mapper 体系。)
4.3 InputDispatcher:事件分发调度器
InputDispatcher 是输入系统中最关键也最容易出问题的一环。它的核心任务是:
- 接收加工后的事件:从 InputReader 拿到 NotifyArgs
- 确定目标窗口:根据当前焦点窗口 / 触摸坐标找到应该接收事件的窗口
- 通过 InputChannel 发送:利用 socket pair 将事件跨进程发送给 APP
- 超时监控与 ANR:记录每个事件的分发时间,超时则触发 ANR
一个关键的数据结构是 InputChannel:
源码路径 :frameworks/native/libs/input/InputTransport.cpp
cpp
status_t InputChannel::openInputChannelPair(const String8& name,
sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
int sockets[2];
socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets); // 创建 socket 对
...
}
system_server 通过 socket 的 server 端发送事件,APP 进程持有 client 端来接收事件。这种设计使得事件传输不经过 Binder 驱动,避免了 Binder 线程池调度带来的延迟,保证输入事件的低延迟传输。
(第四篇会全面剖析 InputDispatcher 的分发逻辑与 ANR 机制。)
五、Java 管理层:InputManagerService
InputManagerService(IMS)是输入系统在 Java 层的管理入口:
frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
它并不直接参与事件的分发和加工,而是扮演"管理者"的角色:
- 提供 Binder 接口供外部(如 WindowManagerService、Settings 应用)调用
- 管理输入法(IME)的切换与状态
- 处理按键的"特殊行为"(如音量键的拦截策略、电源键的长按关机)
- 向 Native 层传递策略配置(如 ANR 超时时长、触摸事件的坐标偏移等)
IMS 在 SystemServer 启动时被创建,与 WindowManagerService 紧密绑定------WMS 负责窗口的布局与层级,IMS 负责把事件投递到正确的窗口。
六、应用接收层:从 InputChannel 到 View.onTouchEvent
当 InputDispatcher 通过 socket 把事件发送到 APP 进程后,应用侧的处理链路是这样的:
InputChannel (socket client 端)
│
▼
NativeInputEventReceiver (JNI 层,监听 socket fd)
│
▼
InputEventReceiver (Java 层,dispatchInputEvent)
│
▼
ViewRootImpl.WindowInputEventReceiver (子类,处理输入事件)
│
▼
ViewRootImpl.processPointerEvent / processKeyEvent
│
▼
DecorView.dispatchTouchEvent / dispatchKeyEvent
│
▼
Activity.dispatchTouchEvent
│
▼
ViewGroup.dispatchTouchEvent → onInterceptTouchEvent → child.dispatchTouchEvent
│
▼
View.onTouchEvent
其中,NativeInputEventReceiver 在 JNI 层通过向主线程的消息队列注册一个 fd 监听 来实现事件接收。这意味着 输入事件实际上是通过消息队列(MessageQueue)被注入到主线程的,它会和主线程上的其他 Message 一起排队等待处理。这也是为什么主线程卡顿会导致触摸事件延迟响应的根本原因。
(第五篇会完整展开应用侧的事件分发机制。)
七、一个触摸事件的完整旅程
用户在屏幕上点击了一个按钮:
时间线 事件流转
══════════════════════════════════════════════════════════════════
T0 手指触屏 电容屏感应 → 触摸IC → I2C/SPI → 内核驱动
T1 内核上报 input_event 写入 /dev/input/event2
T2 EventHub采集 epoll_wait 返回 → read() 读取 → 封装 RawEvent
T3 InputReader加工 MultiTouchInputMapper 识别 DOWN 动作
→ 坐标从绝对像素转为逻辑坐标
→ 生成 NotifyMotionArgs(ACTION_DOWN, x, y)
T4 InputDispatcher分发 查找触摸坐标命中的目标窗口
→ 通过 InputChannel socket 发送 InputMessage
T5 APP进程接收 NativeInputEventReceiver 从 socket 读到数据
→ 转换为 MotionEvent 对象
→ post 到主线程消息队列
T6 View树分发 ViewRootImpl → DecorView → ViewGroup → Button
→ Button.onTouchEvent(MotionEvent.ACTION_DOWN)
整个过程在正常情况下仅需 几毫秒到十几毫秒,这依赖于每一步的精巧设计:
- 内核驱动直接写入设备节点,零拷贝
- Native 层两个线程 PRIORITY_URGENT_DISPLAY 优先级
- socket pair 绕开 Binder 延迟
八、关键源码文件索引
Native 层(C++)
| 文件路径 | 说明 |
|---|---|
| frameworks/native/services/inputflinger/EventHub.cpp | 设备发现与原始事件读取 |
| frameworks/native/services/inputflinger/InputReader.cpp | 事件加工与 Mapper 调度 |
| frameworks/native/services/inputflinger/InputDispatcher.cpp | 事件分发与 ANR 监控 |
| frameworks/native/services/inputflinger/InputManager.cpp | 三剑客的启动与协调 |
| frameworks/native/services/inputflinger/InputListener.cpp | 监听器接口 |
| frameworks/native/libs/input/InputTransport.cpp | InputChannel 与 socket 通信 |
Java 管理层
| 文件路径 | 说明 |
|---|---|
| frameworks/base/services/core/java/com/android/server/input/InputManagerService.java | IMS 主类 |
| frameworks/base/services/core/java/com/android/server/input/InputMonitor.java | 输入监控器 |
| frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java | WMS,与 IMS 联动 |
JNI 桥接层
| 文件路径 | 说明 |
|---|---|
| frameworks/base/core/jni/android_view_InputEventReceiver.cpp | Native 事件接收器 |
| frameworks/base/core/jni/android_view_KeyEvent.cpp | KeyEvent JNI |
| frameworks/base/core/jni/android_view_MotionEvent.cpp | MotionEvent JNI |
应用层
| 文件路径 | 说明 |
|---|---|
| frameworks/base/core/java/android/view/ViewRootImpl.java | 窗口根,事件入口 |
| frameworks/base/core/java/android/view/InputEventReceiver.java | 应用侧事件接收器基类 |
| frameworks/base/core/java/android/view/View.java | View 基类,事件分发 |
| frameworks/base/core/java/android/view/ViewGroup.java | ViewGroup,事件拦截与分发 |
九、本系列预告
通过本篇的总览,你应该已经对 Android 7 输入系统的全貌有了清晰的认知。接下来的四篇将逐一深入:
- 第二篇:深入 EventHub,拆解 inotify + epoll 设备监听与 RawEvent 采集机制
- 第三篇:全面解析 InputReader 的 Mapper 体系,理解触摸、键盘、鼠标的事件加工原理
- 第四篇:剖析 InputDispatcher 的分派策略、socket 跨进程通信与 ANR 超时判定机制
- 第五篇:从 InputChannel 到 View.onTouchEvent(),理解应用侧事件消费的完整链路
敬请期待!