Jimmy Chen

A Programmer

(转载)Input系统: InputReader处理合成事件

Input系统: InputReader 概要性分析 把 InputReader 的事件分为了两类,一类是合成事件,例如设备的增、删事件,另一类是元输入事件,也就是操作设备产生的事件,例如手指在触摸屏上滑动。

本文承接前文,以设备的扫描过程为例,分析合成事件的产生与处理过程。虽然设备的扫描过程只会生成部分合成事件,但是只要我们掌握了这个过程,其他的合成事件的生成以及处理也是水到渠成的事。

生成合成事件

EventHub 扫描设备以及生成合成事件的过程如下

EventHub 扫描设备以及生成合成事件的过程如下

  1. 通过 scanDevicesLocked() 扫描输入设备。扫描过程中会把设备保存到 mOpeningDevices 中。
  2. 为每一个输入设备,向 InputReader 提供的 buffer 中,填充一个类型为 DEVICE_ADDED 的事件。并且还要注意,EventHub 还会把输入设备 Device 保存到 mDevices 中。
  3. 向 InputReader 提供的 buffer 中,填充一个类型为 FINISHED_DEVICE_SCAN 的事件。
  4. 现在 InputReader 提供的 buffer 中已经有数据了,是时候返回给 InputReader 进行处理了。
  5. 返回要处理事件的数量给 InputReader。

虽然,从这里我们已经可以明确知道生成了什么类型的合成事件,但是我们的目的不止于此,因此我们深入看看 scanDevicesLocked() 是如何完成设备的扫描的

设备的扫描过程如下

  1. 从驱动中输入设备厂商信息,并保存到 InputDeviceIdentifier 中。
  2. 创建代表输入设备 Device 对象,并更新输入设备的信息
    • 加载并解析输入设备的配置文件(不包括键盘配置文件),请参考【加载并解析输入设备的配置】。
    • 更新输入设备能报告的事件类型,例如手机上触摸屏能报告 EV_ABS 类型的事件,它是一个绝对坐标事件。
    • 根据设备能报告的事件类型,判断输入设备的类型。例如,设备能报告 EV_ABS 类型事件,那么它肯定是一个触摸设备,类型肯定有 InputDeviceClass::TOUCH。
  3. epoll 监听输入设备描述符的事件,其实就是实时监听输入设备的输入事件的到来。
  4. 保存正在打开输入设备。其实就是把创建的 Device 对象保存到 mOpeningDevices 中。

EventHub 所创建的输入设备的信息,可以通过 adb shell dumpsys input 查看

可以通过 dumpsys input 查看 EventHub 获取的设备信息

有几点要说明下

  1. 创建 Device 后,加载的配置文件,对应于这里的 ConfigurationFile。属性 KeyLayoutFile 和 KeyCharacterMapFile 是代表键盘类输入设备的按键布局文件和按键字符映射文件,它们是在按键事件的处理过程中加载的。
  2. 输入设备 XXXTouchScreen 的类型有三个 KEYBOARD,TOUCH,TOUCH_MT,从这可以看出,一个输入设备可以报告多种类型的事件。

加载并解析输入设备的配置

加载并解析输入设备的配置文件的过程如下

  1. 获取配置文件并保存到 EventHub::configurationFile
  2. 解析并保存数据保存到 EventHub::configuration

下面分析的几个函数,是加载所有配置文件的基本函数

所有的配置文件的路径有很多,我用两个正则表达式表示

  1. /[product|system_ext|odm|vendor|system]/usr/[idc|keylayout|keychars]/name.[idc|kl|kcm]
  2. /data/system/devices/[idc|keylayout|keychars]/name.[idc|kl|kcm]

idc 全称是 input device configuration,用于定义输入设备的基本配置。

kl 全称是 key layout,用于定义按键布局。

kcm 全称是 key character map,用于定义按键字符映射。

对于文件名 name,有如下几种情况

  1. Vendor_XXX_Product_XXX_Version_XXX(Version不为0的情况下)
  2. Vendor_XXX_Product_XXX(Version为0的情况下)
  3. 修正后的设备名(设备名中除了字母、数字、下划线、破折号,其实字符都替换为下划线),。
  4. 对于键盘类型输入设备,按键配置文件的名字还可以为 Generic。

可以通过 dumpsys input 查看输入设备的所有配置文件

我使用的触摸屏是 XXXTouchScreen ,它的配置文件的文件名可以为 Vendor_0000_Product_0000.idcXXXTouchScreen.idc

InputReader 处理合成事件

InputReader 处理合成事件的过程如下

  1. 使用 processEventsLocked() 处理合成事件。
  2. 如果设备发生改变,更新输入设备信息后,通知监听者。其实就是把输入设备信息更新到上层的 InputManagerService。
  3. 刷新队列,分发事件给 InputClassifier。

本文只分析 processEventsLocked() 如何处理合成事件。

EventHub 的设备扫描,生成了两种类型的事件,一种为 EventHubInterface::DEVICE_ADDED, 另一种为 EventHubInterface::FINISHED_DEVICE_SCAN。现在看下这两种事件的处理过程

EventHubInterface::FINISHED_DEVICE_SCAN 类型事件的处理比较简单,如下

EventHubInterface::DEVICE_ADDED 类型事件的处理才是重点,如下

InputReader 对 EventHubInterface::DEVICE_ADDED 类型的事件的处理过程如下

  1. 创建代表输入设备的 InputDevice,然后使用 InputReader 的配置对它进行配置。
  2. 用数据结构保存 InputDevice,最主要是使用 mDevices 保存。

分析到这里,其实已经明白了整个设备扫描的过程。最后,我们重点分析下 EventHub 如何创建输入设备 InputDevice,以及如何配置它。详见【创建与配置 InputDevice

创建与配置 InputDevice

InputReade 创建 InputDevice 的过程如下

  1. 创建 InputDevice 实例。
  2. 为 InputDevice 创建 InputMapper 集合。

由于一个输入设备可能有多种类型,而每一种类型对应一个 InputMapper,因此 InputDevice 会拥有多个 InputMapper。当一个输入设备上报某一种类型的事件时,InputDevice 会把这个事件交给所有的 InputMapper 处理,而 InputMapper 根据事件的类型就知道是不是要处理这个事件。

InputMapper 的作用是对输入事件进行加工,例如,把触摸屏的坐标转换为显示屏的坐标,然后把加工后的事件放入 QueuedInputListener 的队列中。InputReader 把从 EventHub 获取的所有事件都处理完毕后,会刷新 QueuedInputListener 的队列,也就是把事件发送给 InputClassifier。

EventHub 中使用 Device 代表输入设备,而 InputReader 使用 InputDevice 代表输入设备,它们之间的差别就是这个 InputMapper。也就是说 InputDevice 能处理事件,而 Device 只能保存输入设备信息。

现在看下如何对 InputDevice 进行配置

对 InputDevice 进行配置的数据源有如下几个

  1. EventHub。
  2. 上层的 InputManagerService。
  3. InputReader 的配置 mConfig。

我们不用纠结配置 InputDevice 的数据是什么,因为可以通过 adb shell dumpsys input 导出 InputDevice 的配置

在对 InputDevice 的配置过程中,有一个很重要的地方,那就是对 InputMapper 进行配置。

对于触摸屏设备而言,它的所有的 InputMapper 都有一个公共的父类 TouchInputMapper,如果这个触摸屏支持多点触摸,这个 InputMapper 就是 MultiTouchInputMapper,而如果只支持单点触摸,那么这个 InputMapper 就是 SingleTouchInputMapper。

我们以手机的触摸屏为例,它的 InputMapper 的配置过程是在 TouchInputMapper 中完成的

对于触摸屏而言,配置 InputMapper 的主要过程如下

  1. 通过 configureParameters() 配置基本的参数。什么是基本参数?按我的理解,就是保持不变的参数。这些基本参数会保存到 InputDevice::mParameters 中。详见【配置基本参数
  2. 通过 configureRawPointerAxes() 配置坐标系,不过这个函数由子类实现,对于支持多点触摸的输入设备,子类就是 MultiTouchInputMapper。其实就是获取触摸屏的坐标系信息,例如 x, y 方向的坐标点的信息(例如,最大值,最小值)。详见【配置坐标系
  3. 通过 configureSurface() 配置 surface 的参数。这些参数决定了如何从触摸屏坐标转换为显示屏坐标。【配置 Surface

配置基本参数

基本参数 InputDevice::mParameters 基本是由配置文件决定的,还有一部分是从驱动获取的,这些信息应该都是“死”的。

同样,InputDevice::mParameters 数据也是可以通过 adb shell dumpsys input 查看

配置坐标系

InputMapper 配置坐标系的过程如下

  1. 通过 getAbsoluteAxisInfo() 从驱动获取坐标系的信息,然后保存到 MultiTouchInputMapper::mRawPointerAxes
  2. 对触摸事件的累加器进行配置

介绍下坐标系的信息结构,mRawPointerAxes 的类型为 RawPointerAxes,代表一个坐标系,如下

每一个成员变量都代表坐标系的某一种信息,但是非常有意思的事情是,所有成员变量的类型都为 RawAbsoluteAxisInfo,如下

虽然坐标系的每一种信息都由 RawAbsoluteAxisInfo 表示,但是并不是 RawAbsoluteAxisInfo 所有的成员变量都有效,毕竟信息都有差异的。这里介绍几个与触摸屏相关的成员变量所代表的意思

  1. RawAbsoluteAxisInfo:valid 表示坐标系是否支持此信息。
  2. RawAbsoluteAxisInfo::minValue 和 RawAbsoluteAxisInfo::maxValue,它们表示 x 轴和 y 轴的坐标范围。

同样地,可以通过 adb shell dumpsys input 把坐标系的信息导出来

现在接着看下如何对触摸事件累加器进行配置

触摸事件累加器的配置过程非常简单,就是创建 Slot 数组。当手指在触摸屏上滑动时,Slot 数组中的每一个元素对应一个手指,保存相应手指所产生的一连串的坐标事件。

配置 Surface

这些信息太复杂了,不过还是可以通过 adb shell dupmsys input 导出来

总结

设备的扫描与合成事件的处理过程如下

  1. EventHub 创建并保存输入设备 Deivce 到 mDevices 中,其中包括为输入设备加载配置文件。
  2. InputReader 创建并保存输入设备 InputDevice 到 mDevices 中,其中包括对 InputDevice 进行配置,对 InputDevice 所保存的 InputMapper 进行配置。

结束

设备的扫描与合成事件的处理过程并不复杂,但是对输入设备是极其繁琐的,这些配置数据到底有何用,我们在后面分析输入事件的处理过程中,再见分晓。

本文转自 https://juejin.cn/post/7164338036052852744,如有侵权,请联系删除。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注