EventHub代码原理详解

537次阅读  |  发布于2年以前

一、EventHub简述

Android系统基于Linux系统,由多个子系统组合而成,各子系统分工合作,在各自功能域中扮演关键角色。其中一个比较重要的子系统是Input子系统,正如其名地,挂载于Android的各输入设备的输入事件,会通过Input子系统传输到上层(Android框架层或事件处理层)执行处理流程。

EventHub在Input子系统中可以看做起到连结上下层的一个重要模块:对下,它监听Input设备的加入与删除,获取Input设备的输入事件;往上,它将输入事件整合由InputReader获取并分发至各Android上层服务或应用。简单来说,EventHub的工作原理是,InputReader中持续循环线程loopOnce()调用EventHub的getEvents(),该函数epoll_wait等待着Input设备的输入事件到来。EventHub的工作又可以细分为监听新设备加入与监听设备事件获取两部分。

1、新设备加入

(1)/dev/input/路径下有新设备加入

(2)该路径受mINotifyFd监听,设备加入后得到IN_CREATE事件

(3)mINotifyFd受mEpollFd监听,得知获得新事件,mEpollFd从epoll_wait跳出

(4)得知事件来自mINotifyFd,mPendingINotify设true,下一轮触发readNotifyLocked()

(5)调用openDeviceLocked()打开设备,包括配置identifier、classes、keymap等

(6)将设备交由mEpollFd监听,并加入mDevices与mOpeningDevices

(7)下一轮发现mOpeningDevices里有设备,建立事件

2、设备事件获取

(1)设备来了新事件

(2)设备在加入时都加入mEpollFd监听,此时mEpollFd得知有新事件,从epoll_wait跳出

(3)Device*device = getDeviceByFdLocked(eventItem.data.fd)根据事件的fd定位设备

(4)read(device->fd,readBuffer, sizeof(struct input_event) * capacity)——从设备读取输入事件 count = size_t(readSize) / sizeof(structinput_event)——计算事件的数量

(5)轮询解析事件参数

上述流程中,event+=1后,event != buffer ,此时说明有新事件产生,退出getEvents(),新事件返回至调用者InputReader,待处理完毕后再次进入getEvents()。

EventHub的详细流程图如下:

二、EventHub():构造函数

EventHub的构造函数通过注册INotify与Epoll监听设备的增删事件及输入事件。INotify作为文件系统变化通知机制,可以监控文件系统的变化,包括Input设备文件节点的新建、删除、读写等等,当被监听的事件发生时,可以通过read()函数从inotify对象中将事件信息读取出来。Epoll可以使用一次等待监听多个描述符的可读可写状态。

构造函数的代码流程为

(1)各种变量初始化

(2)创建epoll对象与inotify对象

(3)将mINotifyFd作为mEpollFd的一个监控对象

(4)创建唤醒pipe,EventHub唤醒时(wake()),往writepipe写值,readpipe受mEpollFd监控

三、getEvents():输入设备事件的监听与读取

getEvents()于InputReader的循环函数中调用,其实质也是个死循环,由循环末端的epoll_wait执行等待。每当有新的输入事件到来时,函数会从等待中跳出,根据事件的类型执行相应的处理,并通过buffer参数更新事件存储结构体RawEvent;处理完毕后重新进入等待,如此反复。

它处理的事件有如下类型:

1.设备关闭:遍历所有待关闭设备逐一移除

2.设备重打开:先关闭所有设备,进入下次循环扫描所有可用输入设备

3.设备扫描与打开(1)scanDevicesLocked()扫描所有可用输入设备,加入Device结构体

(2)扫描发现处于打开中状态的设备,遍历设备,创建DEVICE_ADDED事件

(3)扫描结束,创建FINISHED_DEVICE_SCAN事件

4.Input事件处理

(1)遍历所有待处理事件

(2)通过epoll事件的data字段确认事件来源,看是否EventHub自身注册的INotify事件或wake唤醒事件

(3)如非上述EventHub事件,getDeviceByFdLocked()获知事件的设备来源,对输入事件执行处理。事件为EPOLLIN类型时,从读fd取输入事件,轮询各input_event结构体数据,转换为RawEvent对象,通过buffer参数返回调用者InputReader

事件为EPOLLHUP类型时,表示设备节点已挂断(hung up),关闭设备。

5.在上面的处理中,如果事件是来自INotify的EPOLLIN,说明设备节点发生了增删操作,调用readNotifyLocked()读取事件,完成设备的加载与卸载。

6.事件处理完毕,进入epoll_wait,等待新事件

四、scanDevicesLocked() & openDeviceLocked():可用设备扫描及加载

1. scanDevicesLocked()

通过scanDirLocked()扫描目录DEVICE_PATH(/dev/input)下的所有设备,调用 openDeviceLocked()全部打开

2 . openDeviceLocked()根据上面扫描到的设备路径打开设备节点,获取输入设备的InputDeviceIdentifier结构体,其中包括如下信息

输入设备注册时会传入Bitmask,根据Bitmask可将输入设备分为以下类型:

输入设备类型的不同,设备的配置会有一定的区别,此处不展开说明,可参照上面的流程图。

在设备加载的最后阶段,都会统一调用registerDeviceForEpollLocked(device)注册设备,并addDeviceLocked()将设备加入至设备列表。

而registerDeviceForEpollLocked()的本质是将设备fd加入epoll监控。

另外,在上面getEvents()流程中,提及收到INotify事件时会执行readNotifyLocked()函数,完成设备的加载与卸载,此处的加载流程是也是通过调用openDeviceLocked()实现的;而卸载流程调用的closeDeviceLocked()实现逻辑更为简单:

五、小结

EventHub虽然代码简单,但它是Android输入子系统必不可少的一环,是连结底层输入设备与上层InputReader的纽带,了解EventHub的工作原理对了解Android输入子系统很有帮助。参考资料:

1、《深入理解Android 卷III》

2、https://android.googlesource.com/platform/frameworks/native/+/refs/heads/master/services/inputflinger/reader/EventHub.cpp

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8