简单了解:Android14中的Input event

输入事件的来源

数据的源头,来自硬件(如:点击屏幕)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_KEYcode == BTN_TOUCH 表示触摸按下; 若 type == EV_ABScode == 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 打包好的 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/...

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui