26、输入系统调试:InputReader、InputDispatcher、触摸事件、按键映射
输入系统,说白了就是Android怎么知道你点了哪里、按了什么键。我刚开始接触这部分时,觉得它就是个“事件搬运工”,后来才发现,这里面的门道深着呢。你想想看,从手指触摸屏幕,到应用响应,中间要经过多少层?每一层都可能出问题。
今天我们就来聊聊输入系统的两个核心组件:InputReader 和 InputDispatcher。再加上触摸事件和按键映射这两个高频调试场景。嗯,这些都是我平时排查问题时的“老熟人”了。
输入系统的整体架构
先看一张图,把整体脉络理清楚。我个人习惯,遇到复杂系统先画图,不然脑子容易乱。
从图上可以看得很清楚:硬件产生原始事件,InputReader负责读取和解析,InputDispatcher负责分发。这两个家伙分工明确,各管一摊。
InputReader:从硬件到事件的“翻译官”
InputReader运行在InputReader线程中。它的核心工作就是轮询EventHub,读取原始输入事件,然后转换成Android能理解的KeyEvent或MotionEvent。
我遇到过一个问题:某款平板触摸不灵敏,点上去没反应。抓了log一看,InputReader一直在报“readEvents: timeout”。后来发现是触控IC的I2C总线被其他设备占用了,导致读取超时。嗯,这种问题光看应用层是看不出来的。
EventHub的读取机制
EventHub通过epoll监听所有输入设备节点(/dev/input/event*)。一旦有数据,就调用readEvents()读取原始input_event结构体。
// 原始input_event结构
struct input_event {
struct timeval time; // 时间戳
__u16 type; // 事件类型 (EV_KEY, EV_ABS, EV_REL...)
__u16 code; // 按键码或轴编号
__s32 value; // 值 (按下/抬起/坐标)
};
这里有个坑:时间戳。我曾经排查过一个游戏触控延迟问题,发现InputReader读到的时间戳比实际时间晚了200ms。原因是驱动层上报的时间戳用的是系统启动时间,但上层期望的是CLOCK_MONOTONIC。这种时间偏差,在普通应用中感觉不到,但在高帧率游戏里就非常明显。
dumpsys input中的事件时间戳。如果时间戳异常,问题大概率出在驱动或HAL层,而不是InputReader本身。
InputMapper:事件的“翻译官”
InputReader内部有一组InputMapper,负责把原始事件转换成标准化的Android事件。常见的Mapper有:
- KeyboardInputMapper:处理按键事件,负责按键映射
- TouchInputMapper:处理触摸事件,负责坐标转换、多点触控
- JoystickInputMapper:处理游戏手柄事件
每个Mapper都对应一个设备类型。InputReader根据设备信息(比如idc配置文件)选择合适的Mapper。
InputDispatcher:事件分发的“交通警察”
InputDispatcher运行在单独的Dispatcher线程中。它从InputReader那里拿到事件后,要决定发给哪个窗口、什么时候发、怎么发。
说白了,InputDispatcher就是个“调度中心”。它维护着所有窗口的输入通道(InputChannel),以及窗口的焦点状态。
分发策略
InputDispatcher的分发策略,我总结为三步:
- 找目标:根据触摸坐标找到对应的窗口(通过WindowManagerService的窗口层级信息)
- 排队:把事件放入目标窗口的输入队列
- 发送:通过InputChannel把事件发送到应用进程
这里有个关键点:ANR。如果应用在5秒内没有消费完上一个事件,InputDispatcher就会触发ANR。我在项目中遇到过一种情况:某个页面加载时主线程卡住了,但触摸事件已经发过去了。结果就是用户点了一下,5秒后弹出ANR对话框。嗯,这种问题其实可以通过优化应用启动逻辑来避免。
dumpsys input可以查看当前InputDispatcher的状态,包括等待队列、正在处理的事件、每个窗口的输入通道状态。这是排查输入ANR的第一利器。
触摸事件调试
触摸事件调试,说白了就是搞清楚“用户点的是A,但应用收到的是B”这种问题。我把它分为两类:坐标不准和事件丢失。
坐标不准
坐标不准通常和屏幕校准有关。Android通过idc文件(Input Device Configuration)来配置设备的参数。比如:
# 一个典型的触摸屏idc配置
touch.deviceType = touchScreen
touch.orientationAware = 1
touch.size.calibration = diameter
touch.pressure.calibration = amplitude
touch.pressure.scale = 0.005
如果坐标不准,先检查idc文件是否正确。我遇到过一台设备,触摸坐标总是偏右50像素。查了半天,发现是touch.orientationAware设置成了0,导致旋转后坐标没有做相应转换。
事件丢失
事件丢失更隐蔽。用户明明滑动了,但应用只收到部分事件。这种情况,我建议先抓getevent日志:
# 抓取原始输入事件
adb shell getevent -lt /dev/input/event2
如果getevent显示事件是连续的,但应用收不到,那问题出在InputDispatcher或应用自身。如果getevent本身就有丢事件,那问题在驱动层。
我曾经遇到一个案例:某款手机在快速滑动时,触摸事件会“跳点”。用getevent一看,发现驱动上报的事件间隔不均匀,有时两帧之间差了30ms。这就是驱动层的采样率不够,或者中断处理有延迟。
按键映射调试
按键映射,说白了就是把物理按键的扫描码(scancode)转换成Android的按键码(keycode)。这个映射关系定义在.kl文件(Key Layout)中。
kl文件示例
# 一个标准的kl文件片段
key 114 VOLUME_DOWN
key 115 VOLUME_UP
key 116 POWER
key 158 BACK
key 172 HOME
key 217 MENU
左边是扫描码,右边是Android按键码。如果按键没反应,先检查kl文件是否存在、是否被正确加载。
我遇到过一个问题:某款外接键盘的“ESC”键按了没反应。用getevent查看,发现扫描码是1,但kl文件里没有定义这个扫描码。加上key 1 ESC后,问题解决。
adb shell getevent:查看原始输入事件adb shell dumpsys input:查看InputDispatcher状态adb shell dumpsys window windows:查看窗口层级adb shell cat /proc/bus/input/devices:查看输入设备列表adb shell ls /system/usr/keylayout/:查看按键映射文件
实战:一个触摸不灵的排查过程
最后,分享一个我实际排查过的案例。用户反馈:手机横屏玩游戏时,触摸偶尔失灵。
排查步骤:
- 抓取dumpsys input:发现事件正常上报,没有丢事件
- 检查窗口焦点:发现焦点在游戏窗口上,没问题
- 查看事件时间戳:发现事件时间戳有跳跃,有时两帧间隔100ms
- 抓取getevent:确认驱动上报的事件本身就有间隔不均的问题
- 分析驱动日志:发现触控IC在横屏模式下,因为散热问题导致采样率下降
最终结论是硬件问题,不是软件能解决的。但通过这个排查过程,我排除了InputReader和InputDispatcher的嫌疑,帮硬件团队缩小了范围。
嗯,输入系统调试就是这样,一层一层往下挖。从应用层到框架层,再到驱动层,每个环节都可能出问题。掌握了InputReader和InputDispatcher的工作原理,你就能快速定位问题出在哪一层。
公众号:蓝海资料掘金营,微信deep3321