ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

4. 原始事件处理流程:RawEvent的获取与初步分类,设备类型判断

4. 原始事件处理流程:RawEvent的获取与初步分类,设备类型判断 4.1 RawEvent从哪里来RawEvent的来源是Linux内核的输入子系统。具体来说是通过/dev/input/目录下的设备节点读取的。每个物理输入设备——触摸屏、键盘、鼠标、轨迹球——都对应一个或多个这样的节点。我记得第一次看这部分代码时有个问题困扰了我很久为什么同一个设备会有多个事件节点后来才明白有些设备会同时上报多种类型的事件比如触摸屏既上报触摸坐标EV_ABS又上报按键EV_KEY内核会把这些事件分开发送。核心要点RawEvent本质上是一个struct input_event结构体包含三个关键字段type事件类型EV_KEY、EV_ABS、EV_REL等code事件编码比如按键的键值、触摸的轴编号value事件值按下/抬起、坐标位置、滚轮滚动量等在InputReader的循环中它会调用EventHub::getEvents()来批量读取这些原始事件。这个函数内部会做一次read()系统调用从设备节点中拉取数据。每次读取可能拿到多个事件因为内核会把一段时间内的事件缓存起来。// 伪代码示意实际代码更复杂 size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) { // 1. 遍历所有设备检查是否有新事件 // 2. 调用 epoll_wait 等待事件就绪 // 3. 从设备节点 read() 原始数据 // 4. 填充 RawEvent 结构体 // 5. 返回读取到的事件数量 }4.2 初步分类事件类型的分流拿到RawEvent之后InputReader做的第一件事就是分类。它根据type字段把事件分到不同的处理路径上。我个人习惯把这一步叫做「事件的路由」。你看一个RawEvent可能是按键事件可能是触摸事件也可能是鼠标滚轮事件。如果不先分好类后面根本没法处理。事件类型type值典型设备后续处理EV_KEY0x01键盘、按键、触摸屏按压按键映射、按键消抖EV_ABS0x03触摸屏、轨迹球、摇杆坐标转换、多点触控协议解析EV_REL0x02鼠标、触摸板相对位移计算EV_SYN0x00所有设备事件同步、批次结束标记这里有个细节EV_SYN事件本身不携带有效数据它只是一个同步标记。内核在发送完一组相关事件后会发送一个EV_SYN/SYN_REPORT事件告诉上层「这一批事件发完了你可以处理了」。我在项目中遇到过一个问题就是触摸屏驱动没有正确发送SYN_REPORT导致上层一直收不到完整的触摸事件——嗯这种问题排查起来特别头疼。4.3 设备类型判断从RawEvent到InputDevice分类完事件类型后下一步是判断这个事件来自什么设备。这一步很关键因为同样的EV_KEY事件来自键盘和来自触摸屏的处理方式完全不同。设备类型的判断主要依据设备初始化时上报的能力位capabilities。每个输入设备在注册时会告诉内核它支持哪些事件类型和编码。比如键盘设备会声明支持EV_KEY并且列出所有支持的按键编码触摸屏设备会声明支持EV_ABS并且列出支持的绝对轴ABS_MT_POSITION_X等鼠标设备会声明支持EV_REL并且列出支持的相对轴REL_X、REL_Y在Android系统中EventHub会在设备插入时读取这些能力位然后给设备分配一个InputDeviceIdentifier。这个标识符包含了设备名称、厂商ID、产品ID等信息。我的经验设备类型判断的逻辑在InputReader::loopOnce()中有一个专门的步骤叫processConfigChangesLocked()。当检测到设备插入或移除时会重新扫描设备列表并更新设备类型。我曾经踩过一个坑某款外接键盘插入后系统把它识别成了触摸屏导致按键事件全部被丢弃。后来发现是设备的USB描述符写得不规范能力位上报有误。设备类型最终会被映射为Android系统内部的几个大类// 设备类型的枚举定义简化版 enum class InputDeviceClass { KEYBOARD, // 键盘 TOUCH, // 触摸屏 TOUCHPAD, // 触摸板 MOUSE, // 鼠标 JOYSTICK, // 游戏手柄 SWITCH, // 开关如翻盖、霍尔传感器 UNKNOWN // 未知类型 };你想想看为什么需要这么细致的分类因为不同类型的设备后续的事件加工策略完全不同。比如触摸屏需要做坐标缩放、旋转校准而键盘只需要做按键映射。如果分类错了后面所有处理都会跑偏。4.4 一个完整的RawEvent处理流程示例咱们用一个实际场景串一下整个流程。假设用户按下了触摸屏上的一个点内核层触摸屏驱动检测到触摸生成一系列EV_ABS事件X坐标、Y坐标、压力值最后发送一个EV_SYN/SYN_REPORT。EventHub层通过getEvents()读取到这批原始事件填充到RawEvent数组中。InputReader层遍历RawEvent数组根据type字段分流。EV_ABS事件进入触摸处理路径EV_SYN事件触发批次结束处理。设备类型判断查询当前设备的能力位确认这是一个触摸屏设备支持EV_ABS且包含ABS_MT_POSITION_X等轴。后续处理将RawEvent交给对应的TouchInputMapper进行坐标转换、手势识别等操作。注意事项RawEvent的处理是批量进行的不是来一个处理一个。InputReader每次循环会尝试读取最多256个RawEvent然后统一处理。这样做的好处是减少系统调用次数提高吞吐量。但坏处是如果某个事件处理耗时过长会阻塞后续事件的处理——这也是为什么触摸延迟问题经常出现在这个环节。4.5 避坑指南我踩过的几个坑这部分内容我建议你重点看看。因为理论归理论实际项目中遇到的问题往往更刁钻。坑一设备类型误判我曾经遇到一个项目客户反馈某款USB键盘插上后无法使用。排查后发现这个键盘的固件在能力位中同时声明了EV_KEY和EV_ABS它内置了一个小摇杆。系统根据能力位判断优先把它识别成了游戏手柄导致按键事件被路由到了手柄处理路径。解决方案是在InputDeviceClassifier中增加了一个启发式规则如果设备同时支持键盘和摇杆优先按键盘处理。坑二EV_SYN丢失另一个项目触摸屏偶尔会出现「卡死」现象——手指离开屏幕后系统仍然认为有触摸点。调试发现触摸屏驱动在某些情况下没有发送EV_SYN/SYN_REPORT事件。这导致InputReader认为当前批次的事件还没结束一直等待后续事件。最终超时后系统才强制重置触摸状态。这个问题的修复是在驱动层确保每次触摸事件序列都以EV_SYN结尾。坑三RawEvent缓冲区溢出嗯这个坑比较隐蔽。当系统负载很高时内核可能积压大量事件。如果InputReader读取不及时RawEvent缓冲区会溢出导致事件丢失。我记得有一次线上问题用户快速滑动屏幕时触摸轨迹出现断点。后来在EventHub中增加了动态缓冲区大小调整的逻辑才彻底解决。4.6 小结这一章我们聊了RawEvent的获取和初步分类。说白了就是从内核拿到最原始的数据然后根据事件类型和设备类型把数据分发给不同的处理模块。这个过程看似简单但涉及很多细节——设备能力位的解析、事件类型的路由、批量处理的时序控制等等。
返回列表