Chapter 164: Android Deep Path Touch Event
一次点击从手指接触屏幕到 View.onClick() 执行,会跨过触控控制器、Linux input 子系统、Android native input pipeline、窗口管理、跨进程 InputChannel、应用主线程和 View 树分发。读完本章后,读者应能追踪一次触摸事件的责任链,并能把“点击无响应”“触控延迟”“滑动掉帧”放回对应层级定位。
本章的贯穿材料是一段最常见的用户可见行为:用户在前台 Activity 中点击一个按钮,按钮位于普通 ViewGroup 内部,应用没有自定义输入驱动,没有全局悬浮窗拦截,也没有使用游戏引擎绕过 View 系统。这个材料覆盖移动 OS 输入链路中的关键边界:硬件负责采样,内核负责标准化事件,系统进程负责目标选择,应用进程负责消费事件,渲染系统负责把响应变成可见画面。
Android 官方的 AOSP Input 文档 把输入子系统描述为一条跨多层的 event pipeline:物理设备产生信号,内核驱动翻译成 Linux input protocol,EventHub 打开 evdev 设备读取事件,InputReader 解码并转换成 Android input event,InputDispatcher 再把事件发给合适窗口。Linux 侧的多点触控细节可回溯到 Linux Multi-touch Protocol,应用侧事件对象可回溯到 MotionEvent API。
本章以 Android 15 / Android 16 时代公开文档和 AOSP 输入栈常见结构为边界。不同厂商内核驱动、触控固件、SurfaceFlinger 调度策略和 framework 分支会调整具体实现,但“采样 → 标准化 → 解码 → 目标选择 → 跨进程投递 → 主线程处理 → 帧反馈”的责任顺序稳定。
164.1 Touch Event 的跨层路径总览
触摸事件的主路径可以先压缩成一句话:触控控制器把接触变化采样成硬件信号,内核 input driver 把信号翻译成 Linux input event,Android InputReader 把它变成 MotionEvent 语义,InputDispatcher 选中目标窗口并通过 InputChannel 投递到应用进程,应用主线程上的 ViewRootImpl 再把事件交给 DecorView、ViewGroup 和目标 View。
这个路径的关键在于每一层只持有一部分真相。硬件层知道电容矩阵、采样频率、接触面积和噪声过滤结果;内核层知道标准事件码、slot、tracking id 和设备节点;native input 层知道显示坐标、设备配置、窗口 token 和 dispatch timeout;窗口层知道焦点、可触摸区域、遮挡关系和 input channel;应用层知道 View 树、手势拦截和业务回调。把一次点击归因到某个层级时,应先确认该层是否拥有产生该现象的权力。
下面的图把贯穿材料放入完整路径。图中每个节点都对应后续小节的责任边界。
这条路径提供了最小判断顺序。先看现象是否全局存在:如果整机所有应用都有断触、漂移或多指错乱,优先看硬件、固件和内核事件。再看是否只影响某个窗口:如果状态栏、导航栏或其它应用响应正常,优先看窗口焦点、触摸目标和遮挡。最后看是否只影响某个页面或某个控件:如果系统已经把事件交给应用,优先看主线程、ViewGroup 拦截、手势状态机和绘制负载。
按钮点击的时间线也要分清“输入完成”和“反馈完成”。ACTION_DOWN 到达目标 View 只说明系统完成了输入投递;按钮状态变亮、点击动画播放、业务逻辑执行和下一帧显示还要经过主线程队列、Choreographer、RenderThread、BufferQueue、SurfaceFlinger 和显示刷新。用户说“点了没反应”时,问题可能发生在输入投递前,也可能发生在输入已经到达后但主线程或渲染链路阻塞。
164.2 Hardware Layer:Touch Controller 与 Raw Event
触控控制器是触摸链路的第一责任主体。它连接触控面板的感应矩阵,周期性测量电容变化,把接触位置、接触面积、压力近似值、工具类型和多点关系整理成设备可传输的数据。手机常见触控控制器通过 I2C、SPI 或厂商自定义总线向 SoC 发送中断或数据帧,具体协议由控制器、固件和板级设计共同决定。
硬件层输出的 raw event 还没有 Android View 语义。它只表达“某个接触点在某个采样时刻处于某个物理位置”,并附带面积、压力、椭圆方向、工具类型等可选信息。Linux 多点触控协议中,ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 表达接触中心坐标,ABS_MT_TOUCH_MAJOR 和 ABS_MT_TOUCH_MINOR 表达接触区域大小,ABS_MT_PRESSURE 可表达压力,ABS_MT_TRACKING_ID 用来在多次同步之间维持同一接触点身份。
固件过滤会显著改变上层看到的事件。控制器通常会做去噪、去抖、手掌抑制、边缘抑制、湿手模式、手套模式和采样降噪。过滤策略提升稳定性,同时也会带来延迟和误判。比如边缘滑动被固件抑制时,Android 上层可能只看到事件迟到或直接缺失;手掌抑制把大面积接触判成 palm 时,上层可能收到取消或工具类型变化;高刷新屏幕上采样率跟不上显示刷新时,滑动轨迹会呈现离散感。
触控时间戳从这一层开始产生意义。理想情况下,硬件采样时间应尽量接近手指接触发生时间,并沿驱动、InputReader、InputDispatcher、MotionEvent.getEventTime() 传递到应用侧。MotionEvent.getEventTime() 使用 SystemClock.uptimeMillis() 时间基,应用可以用它和当前时间比较,估算事件从采样到消费的延迟。这个估算只能反映到应用消费时的端到端滞后,不能单独拆出硬件、内核和系统服务各自耗时。
定位硬件层问题时,观察目标是“事件是否以正确形态进入内核”。如果多指 slot 经常复用错误、接触面积突变、tracking id 频繁断裂、边缘区域无事件、某个屏幕区域长期漂移,现象更接近控制器、固件、校准或排线问题。应用层改 ViewGroup 拦截逻辑无法修复这些输入事实,因为上层只消费内核已经暴露的标准事件。
164.3 Kernel Layer:Input Driver 与 /dev/input/event*
内核 input driver 的职责是把设备私有信号翻译成 Linux input protocol。AOSP Input 文档明确说明,设备驱动负责把 device-specific signals 转换成标准 input event format,事件类型和代码定义在 Linux linux/input.h 中。这样,Android framework 和应用无需理解 I2C 消息、GPIO 中断、HID usage 或厂商寄存器,只需面对标准化后的事件流。
/dev/input/event* 是用户空间读取输入事件的 evdev 节点。每个输入设备通常对应一个或多个 event 节点,节点权限由 Android 设备策略、SELinux 和系统进程权限控制。普通应用没有权限直接读取这些节点;系统输入服务通过 native 层打开它们,把 kernel event 转成 Android 层可分发的事件。
多点触控设备通常使用 Type B slot protocol。slot 表示一个可复用的接触槽位,ABS_MT_TRACKING_ID 表示该槽位当前接触点身份,SYN_REPORT 表示一组事件同步完成。两个手指按下时,驱动会依次更新 slot 0 和 slot 1 的 tracking id、位置和可选属性,再用一次同步事件结束本轮状态;某根手指抬起时,对应 slot 的 tracking id 变成 -1。上层看到的是一串状态更新,随后再被归并成 ACTION_DOWN、ACTION_POINTER_DOWN、ACTION_MOVE、ACTION_POINTER_UP、ACTION_UP 或 ACTION_CANCEL。
内核层的边界是“标准化”和“设备节点暴露”,它不负责决定哪个 Activity 收到事件。驱动会声明设备能力,例如绝对坐标范围、分辨率、slot 数量、压力轴、触摸面积轴和工具类型。Android native 层会根据这些能力、input device configuration、显示信息和窗口状态继续转换。坐标方向、比例、虚拟按键区域、触控面积解释等控制点可能分布在驱动配置、board property、.idc 文件和 framework 配置中。
贯穿材料中的按钮点击在内核层应表现为一组最小状态变化:某个 slot 获得非负 tracking id,报告 X/Y 坐标,随后同步;手指轻微移动时更新 X/Y 并同步;手指抬起时 tracking id 释放并同步。此时仍然没有“按钮”概念,也没有“点击”概念。按钮是应用 View 树中的目标对象,点击是应用手势状态机在 down / up 序列上做出的解释。
定位内核层问题的证据应围绕 event 节点事实展开。稳定的 raw event 说明硬件和驱动大概率已经把接触送入系统;slot 数量不足说明设备能力本身限制多指;坐标范围异常说明驱动能力声明或校准有问题;事件时间戳间隔抖动说明采样、驱动调度或系统负载存在影响。越过这一层直接分析 View 回调,会把输入事实和业务处理混在一起。
164.4 Native Layer:EventHub、InputReader、InputDispatcher
Android native input 层把 Linux input event 转成系统可路由的 Android input event。EventHub 负责发现和打开 evdev 设备,读取 kernel event;InputReader 负责按设备类别解码、应用设备配置、归一化坐标并生成内部事件;InputDispatcher 负责目标选择、队列管理、跨进程投递、响应等待和超时处理。AOSP Input 文档把这一段写成 EventHub → InputReader → InputDispatcher → appropriate window 的路径。
InputReader 的核心工作是把设备坐标放进 Android 显示和输入语境。触控面板上报的坐标范围可能与显示物理分辨率不同,也可能受旋转、裁剪、虚拟显示、外接输入设备和设备配置影响。InputReader 会结合设备能力、配置文件和显示信息,把原始轴转换成逻辑坐标,并生成带 source、device id、display id、pointer properties、pointer coords 和 event time 的事件。
InputDispatcher 的核心工作是把事件交给正确的窗口。它持有窗口列表、焦点窗口、可触摸区域、窗口 token、窗口 flag、display id、input channel 状态和 pending event 队列。对于按钮点击,第一次 ACTION_DOWN 决定了后续手势的 touch target;同一手势后续 move / up 通常沿着已确定目标投递,除非窗口消失、通道断开、策略取消或系统需要发出 ACTION_CANCEL。
系统输入策略也在这一层生效。电源键、返回手势、状态栏下拉、导航栏手势、系统级窗口、无障碍服务和输入法窗口都可能参与目标选择或预处理。普通应用看到的 MotionEvent 已经经过系统路由;应用无法从事件本身完全还原被系统消费的部分,也无法知道所有窗口遮挡和系统手势判定细节。
输入超时从 InputDispatcher 的角度看是“事件已经投递或正在等待投递,但目标无法按时处理”。Android Developers 的 ANR 文档说明,前台应用如果在 5 秒内没有响应输入事件,例如按键或屏幕触摸,会触发 Input dispatching timed out 类型 ANR。这个阈值让输入分发从“普通队列等待”升级为用户可见故障:系统会认为目标应用已经失去交互能力。
定位 native input 层问题时,先看三个问题:EventHub 是否持续读到设备事件,InputReader 是否把事件转换到正确 display 和坐标,InputDispatcher 是否选中了预期窗口并完成 dispatch。若 raw event 正常但所有应用都无法收到触摸,怀疑 native input 线程、system_server、窗口策略或显示映射。若只有某个窗口收不到,怀疑 focus、touchable region、窗口遮挡、input channel 或应用响应超时。
164.5 Window Layer:Focus Window、Touch Target、InputChannel
窗口层把“屏幕上的一个坐标”转换成“某个进程中的一个接收端”。Android 的输入分发面向窗口,而应用内的 View 分发面向 View 树。InputDispatcher 只能选择窗口级目标;它不会直接知道按钮、列表项或自定义控件。目标窗口确定后,事件通过该窗口对应的 InputChannel 送到应用进程,再由应用内部完成 View 级命中测试和手势分发。
焦点窗口决定键盘类输入和部分输入策略,触摸目标由坐标、窗口层级、可触摸区域、display id、窗口 flag 和策略共同决定。前台 Activity 的主窗口通常是按钮点击的目标;输入法窗口、弹窗、Toast 风格窗口、系统 overlay、无障碍 overlay、状态栏、导航栏和手势区域都可能改变目标窗口。用户看到按钮在屏幕上,不代表该按钮所在窗口一定是 touch target。
InputChannel 是窗口层和应用进程之间的跨进程通道。系统服务把事件写入通道,应用侧通过 native receiver 把事件放进主线程可处理的路径。通道断开、应用进程死亡、窗口移除、Activity pause、surface 生命周期变化或主线程长时间阻塞,都会让输入分发进入异常路径。系统可能向旧目标发送 ACTION_CANCEL,也可能清理通道并把后续事件交给新窗口。
按钮点击中的第一次 ACTION_DOWN 是窗口层的关键检查点。它通常会锁定本次 gesture 的 touch target,使后续 ACTION_MOVE 和 ACTION_UP 保持一致。这样可以防止手指滑过其它窗口时事件链路频繁切换,也能保证应用内手势状态机获得完整序列。若 ACTION_DOWN 到达 A 窗口,后续窗口层变化需要明确取消或转移,否则应用可能收到不完整序列。
窗口层问题的用户可见结果很具体。点击被透明 overlay 截获时,目标应用没有任何 ACTION_DOWN;点击输入法上方区域时,事件可能进入输入法窗口或被系统策略处理;Activity 启动切换期间,旧窗口可能收到 cancel,新窗口等待首帧后才接收后续输入;多窗口或画中画场景中,坐标空间和 display id 错配会导致命中目标异常。
排查窗口层时,判断顺序是:先确认当前前台窗口和 display,随后确认坐标是否落入目标窗口的 touchable region,再确认是否存在更高层级窗口或系统手势区域,最后确认 input channel 是否仍然有效。这个顺序比直接查看 View.onTouchEvent() 更稳定,因为 View 层只能证明“事件已经进了应用”,无法解释事件为何落入另一个窗口。
164.6 App Layer:ViewRootImpl、DecorView、ViewGroup、View
应用进程收到窗口级输入后,ViewRootImpl 是 View 系统入口。它连接 Window 与 View tree,接收来自 input channel 的事件,把事件放入应用主线程处理路径,并把 MotionEvent 交给顶层 DecorView。DecorView 是 Activity 窗口内容的根 View,再向下进入 ViewGroup.dispatchTouchEvent()、ViewGroup.onInterceptTouchEvent()、子 View 的 dispatchTouchEvent() 和 onTouchEvent()。
应用侧的核心语义是“命中测试”和“手势所有权”。ViewGroup 会根据坐标找到候选子 View,并可以通过 onInterceptTouchEvent() 在手势过程中接管事件。Android Developers 的 ViewGroup 文档说明,onInterceptTouchEvent() 允许容器观察发给子 View 的事件,并在任意时刻接管当前手势;dispatchTouchEvent() 则负责把触摸事件传给目标 View 或自身。这个机制让滚动容器、ViewPager、RecyclerView 和自定义手势容器可以在 down / move 序列中决定谁处理手势。
下面的简化代码展示了一个容器如何在横向滑动达到阈值后接管事件。示例只表达分发关系,真实控件还需要处理多指、速度、嵌套滑动、取消事件和无障碍语义。
class SwipeContainer(context: Context) : ViewGroup(context) {
private var startX = 0f
private var startY = 0f
private var dragging = false
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
startX = event.x
startY = event.y
dragging = false
return false
}
MotionEvent.ACTION_MOVE -> {
val dx = kotlin.math.abs(event.x - startX)
val dy = kotlin.math.abs(event.y - startY)
dragging = dx > touchSlop && dx > dy
return dragging
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
dragging = false
return false
}
}
return dragging
}
}
这段代码对应按钮点击中的一个常见分叉:ACTION_DOWN 先给子按钮,随后手指移动超过容器阈值,容器拦截后子按钮收到 ACTION_CANCEL,按钮点击状态被取消。用户看到的是“按下有反馈,滑一下按钮点击失效”。这个现象属于应用 View 树手势所有权变化,和硬件断触、窗口遮挡、input dispatcher 超时属于不同层级。
MotionEvent 的多指语义需要区分 pointer id 与 pointer index。pointer id 在一次接触生命周期内标识同一手指,pointer index 是当前事件数组中的位置,可能随手指按下和抬起变化。应用处理多指缩放或拖拽时,应使用 getPointerId() 建立手指身份,再用当前事件中的 index 读取坐标。把 index 当成稳定身份会在多指抬起后产生跳变。
应用层还必须处理 ACTION_CANCEL。Android MotionEvent 文档提醒,framework 会尽力给 View 提供一致事件流,但事件可能被容器修改或丢弃,View 应能处理 ACTION_CANCEL 和异常序列。对于按钮、拖拽、绘图板和游戏控件,cancel 表示当前手势被系统或父容器终止,控件应清理 pressed 状态、释放捕获资源,并停止把后续 up 当成点击完成信号。
164.7 Looper、MessageQueue、Main Thread 与事件处理
应用层输入处理最终落在主线程队列上。主线程的 Looper 从 MessageQueue 取任务,依次执行输入回调、生命周期回调、业务消息、布局、绘制、动画和其它 posted runnable。输入事件进入应用后并不会抢占正在运行的 Java / Kotlin 代码;如果主线程正在执行长任务,输入事件只能排队等待。
Choreographer 把输入、动画和绘制放入显示帧节奏中。Android Developers 的 Choreographer 文档说明,它协调 animation、input 和 drawing 的时机,接收来自显示子系统的 vsync timing pulse,并调度下一帧渲染工作。也就是说,按钮点击回调改变 View 状态后,还要等到相应帧阶段完成 measure / layout / draw 或 display list 更新,用户才会看到按钮反馈。
主线程队列里的输入与渲染共享同一执行资源。一个 onClick() 中执行 300 毫秒同步计算,会阻塞后续触摸事件,也会推迟下一帧提交;一个复杂 RecyclerView 布局占用一整帧预算,会让触摸反馈看起来迟钝;一个同步等待 Binder 或磁盘 I/O 的输入回调,会把输入延迟扩大成 ANR 风险。输入链路已经完成投递时,用户仍可能因为主线程繁忙感觉“触摸慢”。
移动设备上帧预算由刷新率决定。Android slow rendering 文档以 60Hz 为例说明,流畅渲染通常要求每帧约 16ms 内完成;90Hz 和 120Hz 下预算进一步收紧。输入响应的体感来自两个时间:事件到达主线程并被业务消费的时间,以及消费后第一帧可见反馈的时间。前者决定按钮是否及时执行逻辑,后者决定用户是否看到按下态、涟漪、滚动或动画。
主线程输入问题的判断证据应来自队列和帧阶段。若 MotionEvent.getEventTime() 与处理时刻差距较大,说明事件到达应用时已经积压。若 input callback 很快返回但下一帧延迟,说明输入消费完成后卡在布局、绘制、RenderThread、GPU 或合成路径。若用户连续点击后一次性触发多个回调,说明主线程长任务结束后队列集中释放。
工程上应把输入回调设计成短路径:更新最小 UI 状态,记录轻量事件,启动异步任务,把耗时计算、网络、数据库和图片处理转到合适线程。这个要求来自系统责任链:InputDispatcher 只负责把事件送到应用,应用主线程负责按时返回并提交反馈;主线程长期占用会让系统把输入故障归因给目标应用。
164.8 Input Latency、ANR、Frame Feedback 的定位路径
输入延迟定位要把“事件没到”“事件到达但没处理”“事件处理后没显示”分开。三类现象的责任主体不同:事件没到时看硬件、驱动、EventHub、InputReader、InputDispatcher 和窗口目标;事件到达但没处理时看应用主线程队列、锁等待、同步调用和回调耗时;事件处理后没显示时看 Choreographer、layout / draw、RenderThread、GPU、SurfaceFlinger 和 display present。
最小可复用定位顺序如下。第一步,判断故障范围:全局触控异常指向硬件到 native input;单应用异常指向窗口和应用主线程;单控件异常指向 View 分发和手势状态机。第二步,判断事件边界:应用是否收到 ACTION_DOWN,是否收到完整 down / move / up,是否收到 ACTION_CANCEL。第三步,判断时间边界:eventTime 到处理时间的差值、input callback 耗时、下一帧提交时间、最终 present 时间。第四步,判断用户可见结果:无响应、延迟响应、错误控件响应、滚动卡顿、点击态迟到或 ANR。
ANR 是输入路径的硬失败。Android ANR 文档说明,前台应用如果没有在 5 秒内响应输入事件,会触发 Input dispatching timed out;Android vitals 当前把这种类型计入 user-perceived ANR。用户可见结果是系统弹出“Application Not Responding”对话框,用户可以等待或强制关闭应用。这个结论的责任边界很清楚:系统输入分发已经把事件与目标应用关联,目标应用没有及时完成响应。
慢帧和冻结帧是输入反馈路径的软失败。Android slow rendering 文档把慢帧、冻结帧和 ANR 区分为不同级别:慢帧通常表现为滚动或动画不平滑,冻结帧会让界面近似停住,ANR 则说明应用停止响应输入或广播达到系统阈值。FrameTimeline 可以把一帧拆成 app、render、GPU、compose 和 display 等阶段,帮助判断点击反馈迟到究竟卡在应用绘制、渲染线程、GPU 执行、合成还是显示提交。
贯穿材料中的按钮点击可以形成一张定位表。它把用户描述转换成系统责任链中的检查点。
| 用户可见现象 | 优先检查层级 | 关键证据 | 常见责任主体 |
|---|---|---|---|
| 所有应用同一区域断触 | Hardware / Kernel | raw slot 缺失、坐标突变、tracking id 断裂 | 触控控制器、固件、input driver、校准 |
| 只有某个窗口无法点击 | Window / Dispatcher | 目标窗口不匹配、overlay 覆盖、touchable region 异常 | WindowManager、InputDispatcher、系统手势、悬浮窗 |
| 按钮按下后被取消 | App View Tree | ACTION_CANCEL、父容器拦截、手势所有权切换 | ViewGroup、RecyclerView、自定义手势容器 |
| 点击回调很晚执行 | Main Thread | eventTime 与处理时刻差距大、队列积压 | 主线程长任务、锁等待、同步 Binder、I/O |
| 回调执行后反馈很晚显示 | Frame Pipeline | input callback 快,下一帧或 present 慢 | layout / draw、RenderThread、GPU、SurfaceFlinger |
| 5 秒以上无响应并弹 ANR | Input Timeout | Input dispatching timed out、主线程堆栈阻塞 | 目标应用主线程、死锁、长同步任务 |
定位时不要把“输入”和“帧反馈”合成一个模糊问题。用户的手指动作先形成 input event,input event 再触发应用状态变化,状态变化再进入下一帧。一次点击是否可靠,取决于三个闭环:输入闭环要把事件送到正确窗口,应用闭环要按时消费事件,帧闭环要把消费结果显示出来。本章建立的判断框架,就是沿这三个闭环逐层缩小责任边界。
最小自检任务
给定一个现象:某 Android 应用的列表页中,用户点击列表项有时没有触发点击回调;滑动列表时偶尔出现明显卡顿;同一台设备上的系统桌面、设置页和其它应用触控正常。请写出一条定位路径,要求覆盖输入路径、窗口边界、应用 View 树、主线程队列和帧反馈,并说明每一步的判断依据。
答案要点
应先把故障范围缩小到单应用或单页面,因为系统桌面、设置页和其它应用触控正常,硬件、触控固件、内核 input driver 和全局 native input pipeline 的优先级降低。下一步确认目标 Activity 窗口是否稳定接收 ACTION_DOWN,并检查是否存在弹窗、输入法、系统 overlay、无障碍 overlay 或手势区域改变 touch target。若目标窗口稳定收到事件,再进入应用 View 树,观察列表项是否收到完整 down / up 序列,是否被父容器在 move 阶段拦截,是否收到 ACTION_CANCEL,以及 RecyclerView 或自定义 ViewGroup 是否把轻微滑动判成滚动手势。
若 View 树事件序列完整但点击回调执行晚,应比较 MotionEvent.getEventTime() 与回调处理时刻,并查看主线程是否被数据绑定、同步 I/O、锁等待、图片解码、列表 diff 或 Binder 调用占用。若点击回调及时执行但按下态、涟漪或页面跳转显示迟到,应把问题转入帧反馈链路,检查 layout / draw、RenderThread、GPU、SurfaceFlinger 和 FrameTimeline 中的 app、render、GPU、compose、display 阶段。卡顿与点击丢失同时出现时,最可疑路径是应用主线程和列表 View 树手势状态机:主线程积压会推迟输入处理,父容器拦截会取消点击,重布局和重绘会推迟用户可见反馈。
最终结论应包含责任边界:当前材料支持优先排查应用窗口内的 View 分发、主线程队列和渲染反馈;只有在 raw event 缺失、所有应用同区域异常、目标窗口选择错误或 input dispatcher 超时证据出现时,才把责任上移到硬件、内核、native input 或窗口层。
本章知识点总结
- 输入路径:触摸事件按硬件采样、内核标准化、native 解码、窗口分发、应用消费和帧反馈的顺序跨层传递。
- 层级真相:每一层只持有局部事实,硬件知道接触信号,窗口层知道目标窗口,应用层知道 View 树和业务回调。
- 硬件采样:触控控制器输出 raw event,固件过滤会改变上层看到的坐标、面积、工具类型和延迟。
- 内核标准化:input driver 把设备私有信号翻译成 Linux input protocol,并通过
/dev/input/event*暴露给系统输入服务。 - Slot 语义:Type B 多点触控用 slot 和 tracking id 维持接触点身份,Android 再把状态变化转换成
MotionEvent动作序列。 - Native 分发:
EventHub读取 evdev,InputReader解码和归一化,InputDispatcher选择目标窗口并管理投递和超时。 - 窗口目标:触摸先命中窗口,再进入应用 View 树;overlay、系统手势区域、输入法和窗口生命周期都会改变目标。
- InputChannel:
InputChannel连接系统服务和应用进程,通道断开、窗口移除或主线程阻塞都会影响事件消费。 - View 分发:
ViewRootImpl把事件交给DecorView,随后由ViewGroup和目标View完成命中测试、拦截和处理。 - 手势取消:
ACTION_CANCEL表示当前手势被父容器、窗口变化或系统策略终止,控件应清理按下态和手势状态。 - 主线程队列:输入回调、业务任务、布局、绘制和动画共享主线程队列,长任务会推迟事件处理和下一帧反馈。
- 帧节奏:Choreographer 协调 input、animation 和 drawing,点击反馈必须经过帧提交后才成为用户可见结果。
- ANR 边界:前台应用 5 秒内没有响应输入事件会触发 input dispatching timeout 类型 ANR。
- 定位顺序:先判断故障范围,再确认事件是否到窗口和 View,随后比较事件时间、回调耗时和帧反馈阶段。
- 反馈拆分:点击无响应要拆成输入未到达、应用未消费和消费后未显示三类问题,三类问题对应不同责任主体。