Skip to content

InputManagerService (一)

l-feng edited this page Apr 20, 2017 · 1 revision

一:简介 Android输入系统的最初输入事件是内核生成的原始事件,而最终交付给窗口的则是keyEvent

事件或者MotionEvent事件,因此android输入系统的主要工作是读取设备节点中的原始事件,

将其加工封装,然后派发给一个特定的窗口以及窗口的控件

总体流程如下:

Linux kernel ----------->设备节点---------------->IMS----------------

-->wms------------------>viewRootImpl

其中IMS是由EventHub------->InputReader------->InputDispatcher的流程执行

简单的来说,内核将原始事件写入设备节点中,InputReader不断的通过EventHub将

原始事件取出来并翻译加工成Android输入事件,然后交给InputDispatcher。

InputDisapatcher根据WMS提供的窗口信息将事件交付给合适的窗口。窗口的ViewRootImpl

对象在沿着控件树将事件派发给感兴趣的控件。控件对其收到的时间做出响应,更新自己的画面,

执行特定的动作。

二:IMS的构成

Ims服务是运行于SystemServer中的ServerThread线程中启动的。

1:主要分为两个阶段

1.1:新建IMS对象

InputManagerService inputManager =new InputManagerService (context,wmHander)

1.2:正式启动IMS

inputManager.start();

2:代码流程

2.1 IMS的创建过程

InputManagerService------->nativiInit()------------->NativeInputManager()----

------>InputManager()------->initialize()

1)InputManagerService:首先是在InputManagerService中使用wHandler的looper新建

一个InputManagerHandler,InputManagerHandler将运行在WMS的主线程中。然后调用

nativiInit创建一个对象mptr。 2)nativiInit:在nativiInit中创建了一个类型为NativeInputManager的对象。并且将

NativeInputManager对象的指针返回至java层的IMS,IMS将其保存在mPtr成员变量中。

3)NativeInputManager:在这个类中主要实现了InputReaderPolicyInterface和

InputDispatcherPolicyInterface两个接口,在NativeInputManager主要实现InputReaderPolicy

和InputDispatcherPolicy,这里不过是接口的实现,并不是策略的实施者。NativeInputManager

通过JNI回调java层的IMS。由他完成决策。在代码走读中可以看到这块实现了EventHub,以及创建了

native层的InputManager。

4)InputManager:在这个方法中主要创建了InputDispatcher以及InputReader,并且进行初始化

操作initialize

5)Initialize:创建了InputReader运行的线程InputReaderThread,以及InputDispatcher运行

的线程InputDisapatcherThread

至此,IMS的创建就完成了。在这和过程中,主要是参与输入系统的主要对象的创建

2.2 IMS的启动过程

在之前已经说过了,在SystemServer中的ServerThread线程中通过inputManager.service.start()

启动IMS,在InputManager的创建过程中分别为InputReader与InputDispatcher创建了承载他们的线程

,但是并没有启动这两个线程。这个时候所有的东西都在待命阶段

1)java层的InputManagerService:java层的IMS的主要工作就是为ReaderPolicy与DispatcherPolicy

提供实现,以及与android其系统服务进行协作,其中最主要的是协作者是WMS

2)Native层的nativeInputManager:NativieInputManager位于IMS的JNI层,负责Native层的组件与java

层的IMS的相互通信,同时,它位InputReader以及InputDispatcher提供策略服务请求的接口,策略请求被它

转发给java层的IMS,由IMS进行最终的定夺

3)Native层的InputManager:在这里主要说明,InputManager是由InputReader,InputDispatcher

以及EventHub构成的,是InputReader,InputDispatcher的容器,创建的线程分别承载他们进行运行

这就是IMS的结构体系,本来应该画图的,但是github上面无法粘贴照片,这块就这样理解了。

说完体系,就应该说说工作原理了,当两个线程启动之后,InputReader再其循环中不断地从EventHub中

抽取原始输入事件,进行加工处理之后,将所得的事件放入InputDispatcher的派发队列中,InputDispatcher

则在其循环中将派发队列的事件取出,查找合适的窗口,将事件写入到窗口的事件接收管道中,窗口的事件接受

线程的looper从管道中不断的将事件取出,交由事件处理函数进行事件响应,整个过程共有三个线程首尾相接,

像三天水泵似的一层层的将事件交付给事件处理函数,INputManagerService.start()函数的作用就如同按下

水泵的开关,然而looper这个水泵在窗口创立时候便已经处于运行状态了。

三:原始事件的读取与加工

1:了解INotify与Epoll机制

我们应该如监控设备节点的新建与删除动作以及如何确定节点中有内容可读呢,最简单的办法就是在线程循环

中不断的轮询,然而这会导致非常低下的效率,更会导致电量在无畏的轮询中消耗,于是Android引入了INotify

机制与Epoll机制

1.1 INotify:INotify是Linux内核所提供的一种文件系统变化的通知机制,他可以为应用程序监视文件系统,

如,文件的新建,删除,读写等。他有两个基本的对象,分别为inotify对象与watch对象。

1) inotify:该对象对应一个队列,应用程序可以向INotify对象添加多个监听。当监听事件发生时候,可以通过

read()函数从INotify对象中将事件信息读取出来。

2) watch:该对象用来描述文件系统的变化事件的监听。它包括事件的监听目标和事件掩码两个元素,其中监听

目标是文件系统的一个路径,可以是文件也可以是文件夹,而事件掩码则是需要监听的时间的类型,掩码中的每一个

元素代表一种事件。

总结一下INotify机制的使用过程:

1)通过inotify_init()创建一个inotify对象。

2)通过inotify_add_watch将一个或多个监听添加到inotify对象中

3)通过read()函数从inotify对象中读取监听事件。当没有新事件发生时候,inotify对象中无任何刻度数据。

INotify机制并不熟通过回调的方式通知事件,而需要使用者主动从inotify对象中进行事件的读取。那么何时读取

是最佳的呢。接下来就要Epoll机制上场了

1.2 Epoll :Epoll可以使用一次等待监听多个描述符的可读、可写状态。等待返回时写嗲了可读的描述符或者

自定义的数据,使用者据此读取所需要的数据可以再次进入等待。

简单介绍下Epoll机制的用法:

1)创建eppoll对象

首先通过epoll_create()函数创建一个epoll对象

Int epfd =epoll_create(MAX_FDS);

2)填充epoll_event结构体

接着为每一个需要监控的描述符填充epoll_event结构体。以描述监控事件,并通过epoll_ctl()函数将此

描述与epoll_event结构体注册进epoll对象。

3)使用epoll_wait()函数等待事件

Epoll_wait()函数将会使用调用者陷入某种等待的状态,知道注册的是事件之一发生之后才会返回,并且

携带刚刚发生的事件的详细信息。

下来总结下Epoll的使用步骤:

1)通过epoll_create()创建一个epoll对象

2)为需要监听的描述符填充epoll_event的结构体。并使用epoll_ctl注册到epoll对象中

3)使用epoll_wait等待对象事件的发生

4)根据epoll_wait()返回的epoll_events结构体数组判断事件的类型与来源并进行处理

5)继续使用epoll_wait等待新事件的发生

四:深入理解EventHub

EventHub的直接翻译是事件集线器,顾名思义,他将所有的输入事件通过一个接口getEvent

()把从多个输入设备节点中读取的事件交给inputReader,它是输入系统最底层的一个组件。

主要的流程有:

1)使用epoll_create()函数常见一个epoll对象mEpollFd

2)创建一个inotyfy对象这个inotify对象将被用来监听设备节点的增删事件

3)接下来将mInotifyFd作为epoll的一个监控对象,当inotify事件到来时候,epoll_wait()

将立刻返回,EventHub便可以mInotifyFd中读取设备节点的增删信息,并进行相应的处理

五:InputReader的总体流程

1:代码走向: ThreadLoop()----->loopOnce()------->getEvents()---------->processEventsLocked()

-------->flush() InputReaderThread启动之后,其线程循环不断的执行InputReader.loopOnce()函数。因此在loopOnce

函数中包含了InputReader的所有工作。

接下来看看loopOnce函数的流程

1)通过EventHub抽取事件列表,这里的事件包含两种事件,主要是设备节点读取的事件,以及输入设备可

用性变化的时间,简称为设备时间。

size_t count=mEventHub->getEvents(timeoutMils.,mEventBuffer,EVENT_BUFFER_SIZE)

2)如果有抽取事件,则通过processEventsLocked()函数对事件进行加工处理,对于设备事件,

此函数根据设备的可用性加载或者移除设备对用的配置信息。对于原始输入事件,则在进行转译,

封装与加工后存储在mQueuedListener中

3)发布事件,通过mQueuedListener.flush()函数将处理好的事件交由inputDispacter。

2:getEvents()函数解析

在上面的inputreader中,我们可以看到getEvent函数的作用,它是EventHub的动力所在,几乎

所有的操作都在getEventHub中进行。getEventHub的本质是处理Epoll事件和INotify事件。接下

来我们看看getEventHub主要进行的工作。

1)首先进行与设备相关的工作,某些情况下,如EventHub创建后第一次执行getEvents()函数时候

,需要扫描/dev/input文件夹下面的所有设备的节点并将这些节点的设备打开,另外,当设备节点的

增删事件发生后,会将事件存入buffer中

2)处理未被inputReader取走的输入事件和设备事件。

3)如果mInotifyFd有数据可读,则说明设备节点发生了增删事件

4)如果此次getEvents()没有取到相关的事件,说明mpendingEventsItems中没有可以读取的事件

getEvents()函数的本质是通过epoll_wait()获取Epoll事件到事件池,并对事件池的事件进行消费的过程。

六:深入理解InputReader

这节我们主要从loopOnce函数说起,我们知道loopOnce函数有三个主要的步骤:

1)通过调用mEventHub.getEvents()获取事件列表

2)通过processEventsLocked()函数中处理列表的所有事件

3)调用mQueuedListener.flush()将事件注入到inputDispatcher的派发队列中

getEvents()在前面已经简单的看过了,这下我们需要着重看下其他的部分,

1:原始输入事件的加工processEventsLocked()

代码走向:

processEventsLocked()------>processEventsForDeviceLocked()-------->process()

其中processEventsLocked代码如下:

Void InputReader:processEventslocked(const RawEvent * rawEvent,size_t count){

  For(const RawEvent* rawEvent=rawEvents; count){

     Int32_t type=rawEvent->type;

     If(type<EventhubInterface::FIRST_SYNTHETIC_EVENT){

      ..............

   //处理某一设备的一批事件,batchSize属于输入事件的个数

       processEventsForDeviceLocked(deviceId ,rawEvent,batchSize);

   }else{

   }

} 在processEventsLocked()主要为原始事件加工处理的入口,在其中通过processEventsForDeviceLocked()

来处理某一设备的一批事件,在处理的过程中,主要调用的是process()针对这批事件逐一进行处理。

在inputreader中有一个类来存储输入设备的信息,他就是InputDevice。与EventHub一样,InputDevice描述了

一个输入设备,并且以设备的Id作为键保存在mdevices字典中,InputDevice与EventHub中的device结构体类似

,也保存了设备的id,厂商信息,以及设备的类别,但是InputDevice与device多出了一个inputmapper列表,

他实际上是inputReader中实际进行原始输入事件加工的场所,他有一系列的子类,分别用于加工不同类型的原始事件。

2:InputDevice与inputMapper

2.1 InputDevice创建过程

addDeviceLocked()------>createdeviceLocked()------->addMapper()

addDeviceLocked()主要实现:

1)从EventHub获取厂商信息和设备类型

2)通过createdeviceLocked()创建一个InputDevice对象

3)使用InputReader中的策略配比信息对新建的InputDevice进行策略配置,并且通过reset()进行设备重置

4)将设备放入device中

createdeviceLocked()主要实现:

1)通过ID,厂商信息等是创建一个InputDevice

2)后续将按照设备类型为InputDevice添加特定的InputMapper

3:InputMapper的分配

在InputMapper中完成了时间的加工处理,因此有必要了解下InputMapper,在InputMapper中是根据事件掩码来

确认上报的事件的。EV_KEY(按键类型的时间),EV_ABS(绝对坐标的掩码),EV_REL(相对坐标的掩码),EV_SW(开关

类型的时间掩码),ledBitmask(设备是否则支持光反馈),ffBitmask(是否支持力反馈).

具体事件的加工处理可以参考下keyBoard以及touch事件的加工处理。

Clone this wiki locally