标题Android Input 子系统初探
Android系统是基于Linux内核构建的,作为操作系统的核心,内核负责管理和控制硬件设备的操作。它不仅驱动硬件,还为操作系统提供必要的核心功能。Android支持多种输入设备,如按键、触摸屏和手柄等。面对这些多样化的输入设备,内核通过一种抽象化的方式统一处理这些设备的核心流程,同时允许在具体实现上保留差异化的处理(通过不同类型的回调函数来实现)。这正是Input子系统的作用,主要包括以下几方面:
- 规范化定义Input设备及其数据报告格式;
- 规范化Input处理器(Handler)的定义及其回调实现;
- 为Input设备和Input处理器提供核心服务;
- 提供标准化的用户空间接口。
图1 Input事件处理整体框图
图1展示了Input事件处理的整体框架。本文将围绕这张图详细介绍Input子系统的各个方面,包括定义、初始化、注册与匹配、事件传递以及与用户空间的交互。由于个人理解有限,若有不当之处,敬请指正。
一.Input子系统相关定义
在之前的内容中,我们提到了Input Device、Input Handler等名词,但还没有详细说明。本节将介绍Input子系统中的几个重要结构体,以便于后续的理解。
1. Input_dev
struct input_dev
用于抽象所有输入设备。由于不同设备的事件报告形式和内容存在差异,input_dev
结构体必须包含这些差异化的内容。它的结构设计为通用部分(x)加上特定设备的差异部分(y1、y2、y3等)。因此,在实际的设备驱动开发中,只需填充相关的成员即可定义一个完整的input_dev
。具体成员定义如下图所示:
2. Input_handler
struct input_handler
用于抽象事件处理。不同输入设备的事件处理方式可能不同,Linux内核通过抽象input_handler
结构体来统一事件处理流程,而具体的处理细节则通过回调函数实现。该结构体的主要职责包括匹配、建立连接、事件传递和过滤等,具体成员如下图所示:
3. Input_handle
在抽象了input_dev
和input_handler
后,我们了解到一个input_dev
可能会报告多个事件,并由多个input_handler
处理。为管理这种多对多的关系,input_handle
作为桥梁,连接了设备和处理器。图2展示了这种关系:
图2 设备与处理器示意图
input_handle
的定义相对简单,具体成员如下:
二.Input子系统相关流程
主要流程包括Input核心初始化、输入设备注册、处理器注册、设备与处理器匹配,以及事件传递。
1. Input核心初始化
Input核心通过sybsys_initcall
注册,设定启动优先级,以确保其初始化早于设备和处理器的注册。初始化过程中主要完成以下任务:
- 注册
input
类; - 创建
proc
文件,用于查看处理器和设备信息; - 注册字符设备,主设备号为13。
2. Input_dev注册
通常,输入设备驱动程序需要处理设备的控制和响应报告,而响应报告通过注册的input_dev
来完成。在输入设备驱动的初始化过程中,通过调用input_register_device
函数完成input_dev
的注册,具体流程如下:
3. Input_handler注册
与设备注册相比,处理器的注册相对简单。填充input_handler
结构体后,直接调用input_register_handler
函数即可完成注册,具体流程如下:
4. Input_handler与Input_dev匹配
input_handler
与input_dev
的匹配通过调用input_attach_handler
函数完成。配对成功后,input_dev
、input_handler
和input_handle
之间的关系如图3所示。设备驱动和事件处理层驱动均可通过input_handle
访问相应的对象。匹配流程如下:
图3 input_handle与所有者关系图
如果handler
中没有实现match
回调,则根据input_dev
中的ID与input_handler
中id_table
中的ID进行匹配。例如,evdev_handler
可以匹配所有Input设备,因此所有Input设备都可以通过/dev/input/event*
路径获取原始数据。
在匹配成功后,将调用connect
回调,例如evdev_handler
中的connect
实现如下:
5. Input事件传递
不同设备上报的Input事件格式不同,例如触摸屏事件通常包含手指ID、x坐标和y坐标等信息。图4展示了B协议的
报点格式(A协议不需要ID,会在InputReader中重新分配)。
图4 触摸屏报点事件格式
每个事件通过input_event
接口上报。在判断事件类型是否支持后,调用input_handle_event
函数处理事件:
该接口中,根据type
和code
判断事件的disposition
。当disposition
为INPUT_PASS_TO_DEVICE
时,事件传递给设备本身处理;当为INPUT_PASS_TO_HANDLERS
时,事件传递给处理器处理,通常将事件存储在设备的vals
数组中。只有当disposition
为INPUT_FLUSH
或事件达到数组容量极限时,事件才会传递给处理层(在input_sync
时,disposition
值为INPUT_FLUSH
)。
input_get_disposition
函数根据type
和code
判定事件的disposition
,主要关心EV_SYN
、EV_KEY
、EV_ABS
事件。在EV_KEY
事件中,如果设置了按键自动重发并且当前事件与上次事件不同,则会将事件传递给处理层。
在收到同步事件或事件缓冲区接近满载时,会同步事件,input_pass_values
函数将事件传递给相应的处理器。如果输入设备支持EV_REP
事件,则会设置定时器自动重发按键事件。
所有Input设备都会与evdev_handler
匹配,假设匹配的处理器为evdev_handler
,则events
指向的函数为evdev_events
:
evdev_pass_values
函数将传入的所有事件存储在client->buffer
中;kill_fasync
函数发送通知事件,告知上层client->buffer
中有数据可读。
6. Input事件传递到用户空间
当应用层或框架层调用read
函数读取/dev/input/event*
文件时,会调用evdev_read
函数返回数据。event_fetch_next_event
函数判断client->buffer
这个循环缓冲区中的头尾指针是否相等。如果不相等,则从缓冲区中取出一个input_event
类型的事件放入event
中。input_event_to_user
函数将事件拷贝到应用层,input_event_size
函数用于获取input_event
事件的大小,循环复制client->buffer
中的事件到应用层的缓冲区中。
那么,谁会来打开文件读取这些事件呢?一种是getevent
工具,另一种是Android框架层的inputflinger
服务。inputflinger
服务主要创建两个线程:InputReader
和InputDispatcher
。InputReader
负责处理底层事件,通过eventHub
读取所有事件,然后通过设备属性或事件特征找到相应的映射器(mapper)将底层事件转换为Android设计的事件类型;InputDispatcher
负责与窗口交互,将事件派发给对应的窗口。
参考文献
- https://www.cnblogs.com/lifexy/p/7542989.html
- https://blog.csdn.net/qq_39937242/article/details/82631165