简单了解: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/...

相关推荐
2501_915921432 小时前
从 HBuilder 到 App Store,uni-app 与 HBuilder 项目的 iOS 上架流程实战解析
android·ios·小程序·https·uni-app·iphone·webview
天向上3 小时前
ubuntu系统adb shell报错 ADB server didn‘t ACK
android·linux·ubuntu·adb
xiaoyan20153 小时前
自研2025版flutter3.38实战抖音app短视频+聊天+直播商城系统
android·flutter·dart
愤怒的代码3 小时前
深入解析 SystemUI 依赖注入:Dagger2 实践剖析
android·dagger
游戏开发爱好者84 小时前
以 uni-app 为核心的 iOS 上架流程实践, 从构建到最终提交的完整路径
android·ios·小程序·https·uni-app·iphone·webview
hashiqimiya4 小时前
在hbuidex的项目导入androidstudio离线生成apk
android
QING6184 小时前
Kotlin Flow 节流 (Throttle) 详解
android·kotlin·android jetpack
Kapaseker5 小时前
Context 知多少,组件通联有门道
android·kotlin
游戏开发爱好者85 小时前
构建可落地的 iOS 性能测试体系,从场景拆解到多工具协同的工程化实践
android·ios·小程序·https·uni-app·iphone·webview