输入事件的来源
数据的源头,来自硬件(如:点击屏幕)event事件输入,内核进行处理
当用户进行触摸、按键等操作时,硬件会产生相应的事件,这些事件经过内核的输入子系统处理后,会被传递给 Android 系统的 Input 系统。然后再由 Input 系统将事件分发给对应的应用程序处理。在这个过程中,会涉及到事件的传递、分发、处理等多个环节,任何一个环节有问题,都会引入bug或性能问题。

输入事件的处理
Android系统获取和分发输入事件的过程涉及到多个组件和层次,如下图所示: 
-
输入设备:是指能够产生输入事件的硬件设备,如触摸屏、键盘、鼠标等。每个输入设备都有一个唯一的ID和名称,以及一组属性和功能。 -
EventHub:是位于Native层的一个组件,它负责打开和关闭输入设备,并从Linux内核读取原始的event数据,并将其封装成RawEvent结构体。 -
InputReader:是位于Native层的一个组件,它负责接收EventHub传递过来的RawEvent,并对其进行解析和转换,生成NotifyMotionArgs对象,并将其发送给InputDispatcher。 -
InputDispatcher:是位于Native层的一个组件,它负责接收InputReader传递过来的NotifyMotionArgs,并对其进行筛选和分发,根据不同的策略将其发送给不同的Window或应用程序。 -
InputManagerService:是位于Java层的一个服务,它负责管理InputDispatcher,并提供一些接口供其他组件调用,如注册或注销监听器、设置或获取过滤器等。 -
WindowManagerService:是位于Java层的一个服务,它负责管理Window和视图层次,并与InputDispatcher进行交互,提供一些回调方法供InputDispatcher调用,如拦截或分发输入事件等。 -
ViewRootImpl:是位于Java层的一个类,它负责连接Window和View,并从InputDispatcher接收输入事件,并将其传递给View。 -
View:是位于Java层的一个类,它是所有视图的基类,它负责处理或消费输入事件,并根据需要进行响应或反馈。
1.获取
1.1 EventHub 使用epoll监听 fd 事件
epoll 是 Linux 内核的一种 I/O 事件通知机制,用于高效地处理大量文件描述符的 I/O 事件。它是 select/poll 的增强版本,解决了它们在处理大量连接时性能下降的问题。
主要用途:
- 高性能网络服务器(Nginx、Redis等)
- 文件描述符事件监控
- 异步 I/O 处理
- Android 中的 Input 系统事件监听
核心优势:
- O(1) 时间复杂度 :不管监控多少文件描述符,性能都保持稳定,在内核里维护一棵事件就绪红黑树,只返回"真正发生事件"的 fd,复杂度 O(1) 级别
- 无需遍历所有fd:只返回就绪的文件描述符
- 支持边缘触发(ET)和水平触发(LT) 模式
epoll API 列表:
| API 函数 | 参数 | 返回值 | 作用 |
|---|---|---|---|
| epoll_create1 | int flags |
epoll 实例的文件描述符 | 创建新的 epoll 实例 |
| epoll_ctl | int epfd, int op, int fd, struct epoll_event *event |
0 成功,-1 失败 | 管理 epoll 实例中的文件描述符 |
| epoll_wait | int epfd, struct epoll_event *events, int maxevents, int timeout |
就绪的事件数量 | 等待 I/O 事件发生 |
| epoll_pwait | 同上,增加 const sigset_t *sigmask |
就绪的事件数量 | 带信号掩码的 epoll_wait |
go
#include <sys/epoll.h>
epfd:`epoll_create` 函数返回的 epoll 实例的文件描述符; 作用:指定你要操作的是哪个 epoll 实例。
op: 需要执行的操作,用宏定义指定{EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL}
fd: 需要被监视/操作的目标文件描述符。
*event: 一个指向 epoll_event 结构体的指针,它告诉内核我们需要监视什么事件,以及我们希望关联的用户数据
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd:`epoll_create` 函数返回的 epoll 实例的文件描述符;
*events: 一个由调用者分配内存的 epoll_event 结构体数组; 作用:当 `epoll_wait` 返回时,内核会把所有就绪的事件和它们对应的 `data`(即之前在 `epoll_ctl` 中设置的 `data`)填充到这个数组中。这是 `epoll` 高效的关键之一,它通过一个系统调用就返回了所有就绪的事件,避免了像 `select`/`poll` 那样需要遍历所有文件描述符。
maxevents: `events` 数组的容量,即一次最多可以接收多少个就绪事件
timeout: 超时时间,单位是毫秒。
- **作用**:控制 `epoll_wait` 的阻塞行为。
- **可选值**:
- -1:无限阻塞。直到有事件发生才返回。
- 0:非阻塞。立即返回,即使没有任何事件就绪。用于检查状态。
- > 0:阻塞指定毫秒数。如果在超时,时间内有事件就绪,则立即返回;否则在超时后返回。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll_ctl 操作类型:
EPOLL_CTL_ADD- 添加文件描述符到监控列表EPOLL_CTL_MOD- 修改已监控的文件描述符的事件设置EPOLL_CTL_DEL- 从监控列表中移除文件描述符
epoll 事件类型:
| 事件类型 | 十进制值 | 描述 |
|---|---|---|
| EPOLLIN | 1 | 文件描述符可读(有数据可读取、有数据到达、新连接到达等) |
| EPOLLPRI | 2 | 紧急数据可读(带外数据) |
| EPOLLOUT | 4 | 文件描述符可写(可以发送数据、发送缓冲区有空间) |
| EPOLLRDNORM | 64 | 普通数据可读 |
| EPOLLRDBAND | 128 | 优先级带数据可读 |
| EPOLLWRNORM | 256 | 普通数据可写 |
| EPOLLWRBAND | 512 | 优先级带数据可写 |
| EPOLLMSG | 1024 | 有消息可用(很少使用) |
| EPOLLERR | 8 | 错误条件(总是监控,无需设置) |
| EPOLLHUP | 16 | 挂起(总是监控,无需设置) |
| EPOLLRDHUP | 8192 | 对端关闭连接或半关闭 |
| EPOLLEXCLUSIVE | 268435456 | 独占唤醒,避免惊群效应(>=3.7) |
| EPOLLWAKEUP | 536870912 | 确保在系统挂起时保持唤醒 |
| EPOLLONESHOT | 1073741824 | 一次性事件,触发后需重新注册、需重新 epoll_ctl |
| EPOLLET | 2147483648 | 边缘触发(Edge Triggered),默认水平触发 |

1.2 EventHub 封装event为RawEvent
这个结构体是 Android 输入系统里最底层、最原始的事件单元 ,从内核的 /dev/input/eventX 节点读出来以后,EventHub 直接把它包成 RawEvent 交给上层(InputReader)。 它只负责"陈述事实",不做任何解释或加工。
arduino
/*
* A raw event as retrieved from the EventHub.
*/
struct RawEvent {
// Time when the event happened. Time when the event was read by EventHub. Only populated for input events. For other events (device added/removed/etc), this value is undefined and should not be read.
nsecs_t when;
nsecs_t readTime;
int32_t deviceId; //产生事件的"输入设备ID"
int32_t type; //事件类型
int32_t code; //事件代码
int32_t value;//事件值
};
| 字段 | 类型 | 含义 |
|---|---|---|
when |
nsecs_t(纳秒) |
事件在硬件层面发生的时间,由内核驱动打戳,精度到纳秒。 |
readTime |
nsecs_t |
EventHub 从 fd 读出该事件的时间 ,仅对输入事件有效 |
deviceId |
int32_t |
产生该事件的输入设备 ID ,EventHub 内部编号,与 /dev/input/eventX 一一对应。 |
type |
int32_t |
事件类型 ,对应内核的 EV_* 宏: 0 = EV_SYN(同步包) 1 = EV_KEY(按键/触摸按下) 3 = EV_ABS(绝对坐标,如触摸屏) ... |
code |
int32_t |
事件代码 ,在 type 下的具体含义: 若 type == EV_KEY,code == BTN_TOUCH 表示触摸按下; 若 type == EV_ABS,code == ABS_MT_POSITION_X 表示 X 坐标。 |
value |
int32_t |
事件值 : 对按键:1 按下,0 松开,2 长按重复; 对坐标:具体的坐标/压力值; 对 SYN_REPORT:必须为 0,表示一包数据结束。 |
RawEvent就是内核input_event结构的"Android 复刻版",额外加了一个readTime用于性能统计,上层代码靠RawEvent逐条还原用户的手指、按键、鼠标等原始动作。
事件类型:
| 宏 | 十进制 | 含义 | 常见用途 |
|---|---|---|---|
EV_SYN |
0 | 同步事件 | 打包用,标记"一组事件结束" |
EV_KEY |
1 | 按键 | 遥控器/键盘/按钮 按下/松开 |
EV_REL |
2 | 相对位移 | 鼠标移动、滚轮 |
EV_ABS |
3 | 绝对位移 | 触摸屏坐标、摇杆绝对值 |
EV_MSC |
4 | 杂项 | 扫描码、USB 用法码、低层调试数据 |
EV_SW |
5 | 开关 | 耳机插拔、盖子开合、HDMI 切换 |
EV_LED |
17 | LED 灯 | 键盘大写灯、NumLock |
EV_SND |
18 | 声音 | 蜂鸣器、古董 PC 喇叭(几乎不用) |
EV_REP |
20 | 自动重复 | 键盘长按连续触发 |
EV_FF |
21 | 力反馈 | 游戏手柄震动、方向盘力回馈 |
EV_PWR |
22 | 电源 | 休眠/唤醒键(内核级别) |
EV_FF_STATUS |
23 | 力反馈状态 | 设备报告力反馈当前状态 |
EV_MAX |
31 | 边界标记 | 数组大小用,不会真的出现 |
✅ (1). EV_SYN(type=0)同步事件--->事件代码:
| 十进制 code | 宏常量 | 作用 |
|---|---|---|
| 0 | SYN_REPORT |
一组事件结束,前面所有事件算一个"帧" |
| 1 | SYN_CONFIG |
已废弃,几乎见不到 |
| 2 | SYN_MT_REPORT |
旧式多点触控,Android 已不用 |
| 3 | SYN_DROPPED |
内核缓冲区溢出,告诉用户空间"我丢事件了" |
✅ (2). EV_KEY(type=1)按键事件--->事件代码:
| 十进制 code | 宏常量 | 说明 |
|---|---|---|
| 1 | KEY_ESC |
返回/ESC |
| 2 | KEY_1 ~ 11 KEY_9 |
数字 1-9 |
| 10 | KEY_0 |
数字 0 |
| 28 | KEY_ENTER |
确定/回车 |
| 102 | KEY_HOME |
Home 键 |
| 103 | KEY_UP |
上 |
| 105 | KEY_LEFT |
左 |
| 106 | KEY_RIGHT |
右 |
| 108 | KEY_DOWN |
下 |
| 114 | KEY_VOLUMEDOWN |
音量减 |
| 115 | KEY_VOLUMEUP |
音量加 |
| 116 | KEY_POWER |
电源键 |
| 139 | KEY_MENU |
菜单键 |
| 158 | KEY_BACK |
返回键(Android 最常用) |
| 172 | KEY_HOMEPAGE |
首页(浏览器/launcher) |
| 217 | KEY_SEARCH |
搜索键 |
| 304 | BTN_SOUTH |
游戏手柄 A |
| 305 | BTN_EAST |
游戏手柄 B |
| 306 | BTN_C |
游戏手柄 C |
| 307 | BTN_NORTH |
游戏手柄 Y |
| 308 | BTN_WEST |
游戏手柄 X |
| 310 | BTN_TL |
左肩键 L1 |
| 311 | BTN_TR |
右肩键 R1 |
| 315 | BTN_START |
手柄 Start |
| 316 | BTN_SELECT |
手柄 Select |
1.3各类Mapper封装为NotifyKeyArgs

2.分发
2.1InputDispatcher接收源数据
从原始数据到InputDispatcher接收数据的封装历程 
2.2 InputDispatcher发送数据
InputDispatcher 是 Android 输入系统的"中央邮局"。它把 InputReader 打包好的 NotifyKeyArgs、NotifyMotionArgs 等"裸事件"转成 EventEntry 队列,再按"窗口焦点 、触摸区域 、权限"三条规则投递到目标应用的主线程。核心流程可浓缩为 5 步:
-
入队
InputReader 通过
InputDispatcher::notifyKey()/notifyMotion()把事件推进 mInboundQueue(单向生产,无锁)。 -
找目标,在 dispatchOnceInnerLocked() 里:
- Key 事件 → 调用
findFocusedWindowTargetsLocked(),拿到当前"输入法窗口 "或"前台焦点窗口"。 - Motion 事件 → 调用
findTouchedWindowTargetsLocked(),按 x,y 遍历InputWindowHandle列表,最上层、可见、可触摸、无遮罩 的窗口胜出;同时把 PointerCapture 、监视通道(monitor)也加进来。
- Key 事件 → 调用
-
权限检查,对 Key 还要过 policy(PhoneWindowManager)拦截:
- HOME/BACK/POWER 等系统键会被 policy 提前消费,不会继续发给应用。
- 如果目标窗口没有
INJECT_EVENTS权限,则丢弃。
-
连接投递
每个窗口在注册时都会新建 InputChannel (一对 socket)。
把事件序列化成
InputMessage,通过 InputPublisher::publishKeyEvent() / publishMotionEvent() 写进 socket;应用主线程的 InputConsumer 在下次
nativePollOnce()时收到消息,直接组装成 Java 层的KeyEvent/MotionEvent回调。 -
等待与重试
如果目标窗口 5 秒内不消费(不 finish),触发 ANR ;
如果窗口失去焦点或区域变化,调用
cancelEventsForAnrLocked()把后续事件取消或重新分发给新窗口。
一句话:
InputDispatcher = 队列 + 焦点查找 + 权限过滤 + socket 直投 + ANR watchdog 。所有事件必须经过它"盖章"才能变成应用收到的 onKeyDown() / onTouchEvent()。
学习参考:
developer.aliyun.com/article/148... kernel.meizu.com/2023/10/27/...