Android 源码 输入系统初识

触摸屏与键盘是 Android 最普遍也是最标准的输入设备。当输入设备可用时,Linux 内核会在 /dev/input/ 下创建对应的名为 event0 ~ eventN 或其他名称的设备节点。而当输入设备不可用时,则会将对应的节点删除。

当用户操作输入设备时,Linux 内核接收到相应的硬件中断,然后将中断加工成原始的输入事件数据并写入其对应的设备节点中,在用户空间可以通过 read() 函数将事件数据读出。

Android 输入系统的工作原理概括来说,就是监控 /dev/input/ 下的所有设备节点,当某个节点有数据可读时,将数据读出并进行一系列的翻译加工,然后在所有的窗口中寻找合适的事件接收者,并派发给它。

以 Nexus5 为例,其 /dev/input/ 下有event0 ~ event5 六个输入设备的节点。
Android 源码 输入系统初识
Android 系统提供了 getevent 工具供开发者从设备节点中直接读取输入事件。

getevent 监听输入设备节点的内容,当输入事件被写入到节点中时,getevent 会将其读出并打印在屏幕上。由于 getevent 不会对事件数据做任何加工,因此其输出的内容是由内核提供的最原始的事件。其用法如下:

adb shell getevent [-选项] [device_path]

其中 device_path 是可选参数,用以指明需要监听的设备节点路径。如果省略此参数,则监听所有设备节点的事件。

接下来我按下并松开 Power 键。
Android 源码 输入系统初识
其输出是十六进制的。每条数据有四项信息:产生事件的设备节点(/dev/input/event0)、事件类型(0x0001)、事件代码(0x0074)以及事件的值(0x00000001)。

所以上面 event 含义:类型 0x01 表示此事件为按键事件,代码 0x74 表示电源键的扫描码,值 0x00000001 表示按下,0x00000000 则表示抬起。另外 event0 为按键 qpnp_pon(按键 pwrkey),event2 为 gpio-keys(键盘keypad)。

与 event 对应的相关设备信息可以查看 /proc/bus/input/devices。
Android 源码 输入系统初识

输入事件的源头是位于 /dev/input/ 下的设备节点,而绝大多数输入事件的终点是由 WindowManagerService 管理的某个窗口处理。最初的输入事件为内核生成的原始事件,而最终交付给窗口的则是 KeyEvent 或 MotionEvent 对象。因此 Android 输入系统的主要工作是读取设备节点中的原始事件,将其加工封装,然后派发给一个特定的窗口以及窗口中的控件。这个过程由 InputManagerService 系统服务为核心的多个参与者共同完成。

InputManagerService,Android 系统服务之一,包装 C++ InputManager 并提供其回调。它分为 Java 层和 Native 层两部分。Java 层负责与 WindowManagerService 的通信。而 Native 层则是 InputReader 和 InputDispatcher 两个输入系统关键组件的运行容器。

EventHub 直接访问所有的设备节点。它通过一个名为 getEvents() 的函数将所有输入系统相关的待处理的底层事件返回给使用者。这些事件包括原始输入事件、设备节点的增删等。只要打开 /dev/input/ 下的所有 event* 设备文件,我们就可以获取所有输入设备的输入事件,不管它是触摸屏,还是按键或者红外遥控器。

InputReader,它运行于一个独立的线程中,负责管理输入设备的列表与配置,以及进行输入事件的加工处理。它通过其线程循环不断地通过 getEvents() 函数从 EventHub 中将事件取出并进行处理。对于设备节点的增删事件,它会更新输入设备列表于配置。对于原始输入事件,InputReader 对其进行翻译、组装和封装为包含了更多信息、更具可读性的输入事件,然后交给 InputDispatcher 进行派发。

InputReaderPolicy,它为 InputReader 的事件加工处理提供一些策略配置,例如键盘布局信息等。

InputDispatcher,它也运行于一个独立的线程中。InputDispatcher 中保管了来自 WindowManagerService 的所有窗口的信息,其收到来自 InputReader 的输入事件后,会在其保管的窗口中寻找合适的窗口,并将事件派发给此窗口。

InputDispatcherPolicy,它为 InputDispatcher 的派发过程提供策略控制。例如截取某些特定的输入事件用作特殊用途,或者阻止将某些事件派发给目标窗口。Power 键被 InputDispatcherPolicy 截取到 PhoneWindowManager 中进行处理。

WindowManagerService,虽说不是输入系统中的一员,但是它却对 InputDispatcher 的正常工作起到了至关重要的作用。当新建窗口时,WindowManagerService 为新窗口和 InputManagerService 创建了事件传递所用的通道。另外,,WindowManagerService 还将所有窗口的信息,包括窗口的可点击区域,焦点窗口等信息,实时地更新到 InputManagerService 的 InputDispatcher 中,使得 InputDispatcher 可以正确地将事件派发到指定的窗口。

ViewRootImpl,视图层次结构的顶部,在 View 和 WindowManager 之间实现所需的协议。对于某些窗口,如壁纸窗口、SurfaceView 的窗口来说,窗口即是输入事件派发的终点。而对于其他的如 Activity、对话框等使用了 Android 控件系统的窗口来说,输入事件的终点是控件(View)。ViewRootImpl 将窗口所接收到的输入事件沿着控件树将事件派发给感兴趣的控件。

简单来说,内核将原始事件写入到设备节点中,InputReader 不断地通过 EventHub 将原始事件取出来并翻译加工成 Android 输入事件,然后交给 InputDispatcher。InputDispatcher 根据 WindowManagerService 提供的窗口信息将事件交给合适的窗口。窗口的 ViewRootImpl 对象再沿着控件树将事件派发给感兴趣的控件。控件对其收到的事件作出响应,更新自己的画面、执行特定的动作。

参考资料:

1.张大伟 著 《深入理解Android 卷III》