type
status
date
slug
summary
tags
category
icon
password
从输入硬件到Android窗口
这里直接从底层硬件分析到事件传递到View的流程,具体的调用链查看源码,这里是基于Android 10 的源码分析的.

主要流程如下:
- 输入设备产生信号,比如手触摸屏幕,触摸屏产生电流或者电压信号,传递给设备固件
- 设备固件对信号进行编码和处理,比如产生中断,比如发送USB HID报告
- Linux内核驱动程序接收到信号,并按照input.h中定义的协议,将信号转换成事件
- EventHub通过libevdev读取各个设备(如/dev/input/event4)的事件,并传递给InputReader
- InputReader将事件封装成Android事件流,传递给InputDispatcher进行分发
- InputDispatcher通过InputChannel与Java层的InputEventReceiver联系上,并调用它的方法进行事件分发
- 最终会进入到ViewRootImpl中,根据输入阶段不同,进行不同的处理,最终会调用到DecorView的dispatchTouchEvent()方法
- 再之后就是我们熟悉的View/ViewGroup事件分发流程了
View/ViewGroup事件分发
- 进入到DecorView中的dispatchTouchEvent(event)方法:
- Activity中:
- Window的实现类是PhoneWindow中,因此:
- DecorView中:
- ViewGroup中:
这里会进行一些判断(比如触摸点是否在对应的View内,避免无效计算),然后:
这里就将事件分发给子View了.
- View#dispatchTouchEvent():
- TouchDelegate具有很高的优先级,会最先进行判断,它是用来拓展View的可点击区域的(适合于View尺寸有限,但是需要扩大点击区域的场景)
- 点击事件和长安事件目前都封装成了Runnable,在DOWN,MOVE,UP,CANCEL等几种事件里都有逻辑,本质就是通过view的延时post方法,即Handler的延时发送来进行调度的
这里可以看到OnTouchListener#onTouch() 优先级 高于 onTouchEvent().
View#onTouchEvent()中的逻辑也比较复杂,需要注意几个要点:
基本流程如下:
- DecorView -> Activity -> Window ->ViewGroup#dispatchTouchEvent()
- ViewGroup#onInterceptTouchEvent(ev),返回true,表示拦截,否则表示不拦截
- 如果不拦截,则调用dispatchTransformedTouchEvent(),child为null,则调用super.dispatchTouchEvent(),super是什么,是View,因此把当前ViewGroup当作View,并执行它的dispatchTouchEvent()方法;如果child不为null,则直接执行child的dispatchTouchEvent()方法,进入循环,遇到没有子View的时候执行View的dispatchTouchEvent()方法;
- 如果拦截,则调用自己的dispatchTransformedTouchEvent(),传入的child为null,因此会执行super.dispatchTouchEvent(),把自己当作View,执行View的dispatchTouchEvent()方法;
- View#dispatchTouchEvent()方法中会执行
onTouchEvent()
方法;
到这里整个流程其实就走通了,唯一可能导致疑惑的地方其实就是ViewGroup如何调用自身的onTouchEvent(),其实这里记住一点即可:
ViewGroup的super就是View,View中才有默认实现的onTouchEvent()
.优先级问题
从上面的源码分析可以看到:
OnTouchListener#onTouch() 大于 View#onTouchEvent() 大于 OnClickListener#onClick()
View#onTouchEvent()返回值问题
View如果消费了ACTION_DOWN,则后续的一系列ACTION都会传给这个View的onTouchEvent()来处理; View如果不消费ACTION_DOWN,则后续的一系列ACTION都不会交给它的onTouchEvent()处理;如果当前View和它的兄弟View都无法消费ACTION_DOWN,则会找它的父布局的onTouchEvent()去消费
所谓消费,指的就是View#onTouchEvent()的返回值,返回true,表示消费了,否则表示没消费
ViewGroup#onInterceptTouchEvent(ev)的问题
从dispatchTouchEvent()开始,一层层的调用,直到View#onTouchEvent(),返回值其实是ViewGroup#dispatchTouchEvent()最终使用的:
如果返回fasle,则不会为mFirstTouchTarget赋值,否则将child赋值给mFristTouchTarget.那这又影响了什么呢?
如果不调用API请求不拦截事件,则只有两种情况下的任意一种,才会触发拦截:
- 当前事件是ACTION_DOWN
- mFirstTouchTarget != null (当前ViewGroup的所有子View的onTouchEvent()均返回false)
因此得出结论:
- 只有ViewGroup才有onInterceptTouchEvent(ev)方法
- 只要是ACTION_DOWN事件,ViewGroup都会执行onInterceptTouchEvent(ev)方法
- 如果不是ACTION_DOWN事件,则要判断该ViewGroup的所有子View中是否能消耗ACTION_DOWN事件,即子View中是否有onTouchEvent()返回为true,如果有,则还会执行onInterceptTouchEvent(ev)方法,如果一个都没有,则不会执行onInterceptTouchEvent(ev)方法
- 如果ViewGroup的onInterceptTouchEvent(ev)返回了true,则其子View一种事件都获取不到,因为不会执行子View的dispatchTouchEvent()方法,就算仅仅是拦截了ACTION_DOWN;如果不拦截ACTION_DOWN,而拦截ACTION_MOVE,则子View可以正常捕获到ACTION_DOWN,但是后面会主动执行一个ACTION_CANCEL,就不再接收其他事件了.
adb查看工具
getevent
: 输出所有event设备的基本信息
此时如果触摸屏幕或者按键,会输出:
格式为:
设备名: 事件type 事件code 事件value
(这里的数字是16进制的)如果使用
getevent -l
,则会输出具体的常量名称,比较易于理解:这些常量是定义在
sysroot/usr/include/linux/input.h
中的.sendevent
: 模拟发送事件 (可能需要权限)
格式为:
如:
这里面的值是由16进制转换而成的10进制数.
getevent -p
: 查看详细的交互设备信息
输出如下:
getevent -lp
或者getevent -lp /dev/input/event4
: 输出交互设备信息文字标签信息
getevent -i
: 显示更加详细的信息
getevent -t
: 显示时间戳
参考文件
- 作者:姜康
- 链接:https://jiangkang.tech/article/43cb9a5e-61f2-4c2d-b4be-9d9b095cc742
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。