前言
本文代码基于 Android 12 。
概述
Android 的事件输入可以简化为三部分:
- 物理输入设备 -> InputDispatcher
- InputDispatcher -> ViewRootImpl
- ViewRootImpl 事件派发
物理输入设备 -> InputDispatcher
这一部分主要有三个流程:
- 物理输入设备 -> 标准的 Linux 输入事件
- 标准的 Linux 输入事件 -> Android 输入事件
- Android 输入事件 -> Android 显示器上的窗口
物理输入设备 -> 标准的 Linux 输入事件
这一部分主要由 Linux 内核中的输入设备驱动程序完成:
物理设备生成输入事件信号后,会由设备固件编码成设备特有的事件信号,并传输给 Linux 系统,再由 Linux 内核中的输入设备驱动程序解码成标准的 Linux 输入事件格式。
标准的 Linux 输入事件 -> Android 输入事件
这一部分主要由 Android InputReader 实现:
Android EventHub 组件会打开与每个输入设备关联的 evdev 驱动程序并从 Linux 内核中读取这些标准的输入事件,再由 Android InputReader 组件根据设备类别解码成 Android 定义的事件。
Android 输入事件 -> Android 显示器上的窗口
该部分主要由 Android InputDispatcher 组件完成:
Android InputReader 会把生成的 Android 输入事件流发送给 InputDispatcher ,然后由 InputDispatcher 将这些事件转发给对应的窗口。
如图:

根据物理输入设备的不同,Android 输入事件主要分为 KeyEvent 和 MotionEvent 。

所以用来上传 KeyEvent 的设备有物理键盘设备、DPad 等。而上传 MotionEvent 的设备有显示屏设备、鼠标、手写笔等。
InputDispatcher -> ViewRootImpl
InputDispatcher 是怎么把事件转发给对应的窗口呢?
答:app 在向系统添加窗口时,会和系统进程建立 socket 连接,系统进程将 service 端的 socket 传送到 InputDispatcher ,把 client 端的 socket 返回给 app 。当事件到达时,InputDispatcher 找到此时具有焦点的窗口,通过 socket 把事件发送该 socket 。

在此过程中,socket 会被封装成 InputChannel :

设置 InputChannel 的流程:

可以看到,在添加窗口时,IMS 向 linux 系统请求创建一对 socket 并封装成 InputChannel ,server 端的留在 InputDispatcher ,client 端的传送给 app 端的 ViewRootImpl 。因此 events 可以直接从 InputDispatcher 分发给对应的 ViewRootImpl 。
简述:

那剩下就是如何找到合适的窗口(ViewRootImp)派发事件?
答:InputDispatcher 保存了系统中每个窗口 layer 的信息,包括这个窗口是否可见、是否可以接收输入事件、是否聚焦等。在事件派发时,只要找到当前聚焦的窗口的 InputChannel 并通过它发给目标窗口即可。而窗口的信息由 WMS 通过 InputMonitor 更新到 InputDispatcher 中。如图:

其中聚焦的信息封装在 FocusRequest 中:

其流程可概括为:

具体流程为:

总结,InputDispatcher 和 ViewRootImpl 之间的交互过程如图:

具体流程为:

ViewRootImpl 事件派发
窗口是怎么把事件转发给正确的 view 呢?
答:ViewRootImpl 封装了一系列的 InputStage 来处理输入事件,这些 InputStage 组成链式结构,如果上一个节点的 InputStage 没处理则传给下个节点处理。在 InputStage 中通过事件类型来区分怎么处理,一般来说 Listener 比 View 先处理。如,事件为 KeyEvent 时,OnKeyListener 先处理,再是 View 处理 onKeyDown、onKeyUp 等。
简要概括流程为:

其中 InputStage 的处理顺序为:

事件类型分为:

以 pointer 事件和 key 事件为例其具体流程如下:

总结
简要概述一下 Android 事件的流程就是:
- Linux 内核中的输入设备驱动程序将物理输入设备产生的输入信号转换成标准 Linux 输入事件格式;
- Android EventHub 组件打开与每个输入设备关联的 evdev 驱动程序,并从 Linux 内核中读取已转成标准 Linux 输入事件格式的输入信号,再通过 Android 的 InputReader 组件转码成 Android 事件输入流并发送给 InputDispatcher。
- InputDispatcher 将事件通过 socket 发送给系统当前聚焦的窗口。
- 窗口将事件派发给需要处理这个事件的 view,如 Touch Event 派发给当前 window 中管辖触摸点 [x,y] 的最小的可接收事件的 View ,而 Key Event 派发给当前窗口中聚焦的 View 。
参考
Android 官方:输入
《深入理解 Android 内核设计思想》上,著:林学森
Android 官方代码
原创文章,欢迎转载,但请注明出处
网友评论