简单了解:Android14中的Input event

72 阅读10分钟

输入事件的来源

数据的源头,来自硬件(如:点击屏幕)event事件输入,内核进行处理

当用户进行触摸、按键等操作时,硬件会产生相应的事件,这些事件经过内核的输入子系统处理后,会被传递给 Android 系统的 Input 系统。然后再由 Input 系统将事件分发给对应的应用程序处理。在这个过程中,会涉及到事件的传递、分发、处理等多个环节,任何一个环节有问题,都会引入bug或性能问题。 image.png inputevent事件产生.png

输入事件的处理

Android系统获取和分发输入事件的过程涉及到多个组件和层次,如下图所示: image.png

  • 输入设备:是指能够产生输入事件的硬件设备,如触摸屏、键盘、鼠标等。每个输入设备都有一个唯一的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_create1int flagsepoll 实例的文件描述符创建新的 epoll 实例
epoll_ctlint epfd, int op, int fd, struct epoll_event *event0 成功,-1 失败管理 epoll 实例中的文件描述符
epoll_waitint epfd, struct epoll_event *events, int maxevents, int timeout就绪的事件数量等待 I/O 事件发生
epoll_pwait同上,增加 const sigset_t *sigmask就绪的事件数量带信号掩码的 epoll_wait
#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 事件类型:

事件类型十进制值描述
EPOLLIN1文件描述符可读(有数据可读取、有数据到达、新连接到达等)
EPOLLPRI2紧急数据可读(带外数据)
EPOLLOUT4文件描述符可写(可以发送数据、发送缓冲区有空间)
EPOLLRDNORM64普通数据可读
EPOLLRDBAND128优先级带数据可读
EPOLLWRNORM256普通数据可写
EPOLLWRBAND512优先级带数据可写
EPOLLMSG1024有消息可用(很少使用)
EPOLLERR8错误条件(总是监控,无需设置)
EPOLLHUP16挂起(总是监控,无需设置)
EPOLLRDHUP8192对端关闭连接或半关闭
EPOLLEXCLUSIVE268435456独占唤醒,避免惊群效应(>=3.7)
EPOLLWAKEUP536870912确保在系统挂起时保持唤醒
EPOLLONESHOT1073741824一次性事件,触发后需重新注册、需重新 epoll_ctl
EPOLLET2147483648边缘触发(Edge Triggered),默认水平触发

EventHub1.png EventHub2.png

1.2 EventHub 封装event为RawEvent

这个结构体是 Android 输入系统里最底层、最原始的事件单元,从内核的 /dev/input/eventX 节点读出来以后,EventHub 直接把它包成 RawEvent 交给上层(InputReader)。 它只负责“陈述事实”,不做任何解释或加工。

/*
 * 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;//事件值
};
字段类型含义
whennsecs_t(纳秒)事件在硬件层面发生的时间,由内核驱动打戳,精度到纳秒。
readTimensecs_tEventHub 从 fd 读出该事件的时间,仅对输入事件有效
deviceIdint32_t产生该事件的输入设备 ID,EventHub 内部编号,与 /dev/input/eventX 一一对应。
typeint32_t事件类型,对应内核的 EV_* 宏:
0 = EV_SYN(同步包)
1 = EV_KEY(按键/触摸按下)
3 = EV_ABS(绝对坐标,如触摸屏)
codeint32_t事件代码,在 type 下的具体含义:
type == EV_KEYcode == BTN_TOUCH 表示触摸按下;
type == EV_ABScode == ABS_MT_POSITION_X 表示 X 坐标。
valueint32_t事件值
对按键:1 按下,0 松开,2 长按重复;
对坐标:具体的坐标/压力值;
SYN_REPORT:必须为 0,表示一包数据结束。

RawEvent 就是内核 input_event 结构的“Android 复刻版”,额外加了一个 readTime 用于性能统计,上层代码靠RawEvent逐条还原用户的手指、按键、鼠标等原始动作。

事件类型:

十进制含义常见用途
EV_SYN0同步事件打包用,标记“一组事件结束”
EV_KEY1按键遥控器/键盘/按钮 按下/松开
EV_REL2相对位移鼠标移动、滚轮
EV_ABS3绝对位移触摸屏坐标、摇杆绝对值
EV_MSC4杂项扫描码、USB 用法码、低层调试数据
EV_SW5开关耳机插拔、盖子开合、HDMI 切换
EV_LED17LED 灯键盘大写灯、NumLock
EV_SND18声音蜂鸣器、古董 PC 喇叭(几乎不用)
EV_REP20自动重复键盘长按连续触发
EV_FF21力反馈游戏手柄震动、方向盘力回馈
EV_PWR22电源休眠/唤醒键(内核级别)
EV_FF_STATUS23力反馈状态设备报告力反馈当前状态
EV_MAX31边界标记数组大小用,不会真的出现

✅ (1). EV_SYN(type=0)同步事件—>事件代码:

十进制 code宏常量作用
0SYN_REPORT一组事件结束,前面所有事件算一个“帧”
1SYN_CONFIG已废弃,几乎见不到
2SYN_MT_REPORT旧式多点触控,Android 已不用
3SYN_DROPPED内核缓冲区溢出,告诉用户空间“我丢事件了”

✅ (2). EV_KEY(type=1)按键事件—>事件代码:

十进制 code宏常量说明
1KEY_ESC返回/ESC
2KEY_1 ~ 11 KEY_9数字 1-9
10KEY_0数字 0
28KEY_ENTER确定/回车
102KEY_HOMEHome 键
103KEY_UP
105KEY_LEFT
106KEY_RIGHT
108KEY_DOWN
114KEY_VOLUMEDOWN音量减
115KEY_VOLUMEUP音量加
116KEY_POWER电源键
139KEY_MENU菜单键
158KEY_BACK返回键(Android 最常用)
172KEY_HOMEPAGE首页(浏览器/launcher)
217KEY_SEARCH搜索键
304BTN_SOUTH游戏手柄 A
305BTN_EAST游戏手柄 B
306BTN_C游戏手柄 C
307BTN_NORTH游戏手柄 Y
308BTN_WEST游戏手柄 X
310BTN_TL左肩键 L1
311BTN_TR右肩键 R1
315BTN_START手柄 Start
316BTN_SELECT手柄 Select

1.3各类Mapper封装为NotifyKeyArgs

关键类1.png 事件的输入粗略流程.png

2.分发

2.1InputDispatcher接收源数据

InputDispatcher的数据来源.png 从原始数据到InputDispatcher接收数据的封装历程 keyevent.png

2.2 InputDispatcher发送数据

InputDispatcher 是 Android 输入系统的“中央邮局”。它把 InputReader 打包好的 NotifyKeyArgsNotifyMotionArgs 等“裸事件”转成 EventEntry 队列,再按“窗口焦点触摸区域权限”三条规则投递到目标应用的主线程。核心流程可浓缩为 5 步:

  1. 入队
    InputReader 通过 InputDispatcher::notifyKey() / notifyMotion() 把事件推进 mInboundQueue(单向生产,无锁)。

  2. 找目标,在 dispatchOnceInnerLocked() 里:

    • Key 事件 → 调用 findFocusedWindowTargetsLocked(),拿到当前“输入法窗口”或“前台焦点窗口”。
    • Motion 事件 → 调用 findTouchedWindowTargetsLocked(),按 x,y 遍历 InputWindowHandle 列表,最上层、可见、可触摸、无遮罩的窗口胜出;同时把 PointerCapture监视通道(monitor)也加进来。
  3. 权限检查,对 Key 还要过 policy(PhoneWindowManager)拦截:

    • HOME/BACK/POWER 等系统键会被 policy 提前消费,不会继续发给应用。
    • 如果目标窗口没有 INJECT_EVENTS 权限,则丢弃。
  4. 连接投递
    每个窗口在注册时都会新建 InputChannel(一对 socket)。
    把事件序列化成 InputMessage,通过 InputPublisher::publishKeyEvent() / publishMotionEvent() 写进 socket;
    应用主线程的 InputConsumer 在下次 nativePollOnce() 时收到消息,直接组装成 Java 层的 KeyEvent / MotionEvent 回调。

  5. 等待与重试
    如果目标窗口 5 秒内不消费(不 finish),触发 ANR
    如果窗口失去焦点或区域变化,调用 cancelEventsForAnrLocked() 把后续事件取消或重新分发给新窗口。

一句话:
InputDispatcher = 队列 + 焦点查找 + 权限过滤 + socket 直投 + ANR watchdog。所有事件必须经过它“盖章”才能变成应用收到的 onKeyDown() / onTouchEvent()

学习参考:

developer.aliyun.com/article/148… kernel.meizu.com/2023/10/27/…