type
status
date
slug
summary
tags
category
icon
password

从输入硬件到Android窗口

这里直接从底层硬件分析到事件传递到View的流程,具体的调用链查看源码,这里是基于Android 10 的源码分析的.
notion image
主要流程如下:
  1. 输入设备产生信号,比如手触摸屏幕,触摸屏产生电流或者电压信号,传递给设备固件
  1. 设备固件对信号进行编码和处理,比如产生中断,比如发送USB HID报告
  1. Linux内核驱动程序接收到信号,并按照input.h中定义的协议,将信号转换成事件
  1. EventHub通过libevdev读取各个设备(如/dev/input/event4)的事件,并传递给InputReader
  1. InputReader将事件封装成Android事件流,传递给InputDispatcher进行分发
  1. InputDispatcher通过InputChannel与Java层的InputEventReceiver联系上,并调用它的方法进行事件分发
  1. 最终会进入到ViewRootImpl中,根据输入阶段不同,进行不同的处理,最终会调用到DecorView的dispatchTouchEvent()方法
  1. 再之后就是我们熟悉的View/ViewGroup事件分发流程了

View/ViewGroup事件分发

  1. 进入到DecorView中的dispatchTouchEvent(event)方法:
    1. Activity中:
      1. Window的实现类是PhoneWindow中,因此:
        1. DecorView中:
          1. ViewGroup中:
            1. 这里会进行一些判断(比如触摸点是否在对应的View内,避免无效计算),然后:
              这里就将事件分发给子View了.
          1. View#dispatchTouchEvent():
            1. 这里可以看到OnTouchListener#onTouch() 优先级 高于 onTouchEvent().
              View#onTouchEvent()中的逻辑也比较复杂,需要注意几个要点:
              • TouchDelegate具有很高的优先级,会最先进行判断,它是用来拓展View的可点击区域的(适合于View尺寸有限,但是需要扩大点击区域的场景)
              • 点击事件和长安事件目前都封装成了Runnable,在DOWN,MOVE,UP,CANCEL等几种事件里都有逻辑,本质就是通过view的延时post方法,即Handler的延时发送来进行调度的
          基本流程如下:
          1. DecorView -> Activity -> Window ->ViewGroup#dispatchTouchEvent()
          1. ViewGroup#onInterceptTouchEvent(ev),返回true,表示拦截,否则表示不拦截
          1. 如果不拦截,则调用dispatchTransformedTouchEvent(),child为null,则调用super.dispatchTouchEvent(),super是什么,是View,因此把当前ViewGroup当作View,并执行它的dispatchTouchEvent()方法;如果child不为null,则直接执行child的dispatchTouchEvent()方法,进入循环,遇到没有子View的时候执行View的dispatchTouchEvent()方法;
          1. 如果拦截,则调用自己的dispatchTransformedTouchEvent(),传入的child为null,因此会执行super.dispatchTouchEvent(),把自己当作View,执行View的dispatchTouchEvent()方法;
          1. 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: 显示时间戳

            参考文件

            1. https://source.android.com/devices/input
            使用VSCode阅读Android源码Android中的Handler
            Loading...