代码目录frameworks/native/services/inputflinger/InputReader.cpp
这个方法代码加注释大概60行,我全贴出来意义也不大.

古人能做到目无全牛是对牛的任何细微的肉都很熟悉吗?并不是,而是对骨架很熟悉,很清楚的明白在哪儿下刀.

代码研究也是如此,那么多细枝末节,如果一个个深入,你可能早就忘记了原先要研究的东西

所以,我们先要大致搞清楚这个方法做了哪些事儿,之后再来详细分析具体是怎么做的.

上文中一直都是分析的统一的输入事件,但在分发给应用处理之前,肯定是已经区分好了是什么输入设备.那么什么时候开始,事件有了鼠标的标识呢?

带着这个问题,继续研究

 1void InputReader::loopOnce() {
2    ...
3    { // acquire lock
4        AutoMutex _l(mLock);//为代码段加锁
5         ...
6        //第一步
7       //当输入设备的配置发生修改时(包括但不限于鼠标移动速度),要重新进行配置
8       refreshConfigurationLocked(changes);
9       ...
10       //增加一个timeout
11      timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
12    } // release lock//释放锁
13
14    //第二步
15   //重点来了,获取输入事件
16    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
17
18    { // acquire lock
19        AutoMutex _l(mLock);
20       ...
21        //第三步 事件处理
22        processEventsLocked(mEventBuffer, count);
23        ....
24       //第四步
25        timeoutExpiredLocked(now);
26      ...
27    } // release lock
28
29    // Send out a message that the describes the changed input devices.
30    ...
31         //第五步
32        //通知InputManager,输入设备发生了变化(add/remove)
33        mPolicy->notifyInputDevicesChanged(inputDevices);
34   ...
35
36     //第六步
37    //刷新dispatcherThread的事件队列,通知线程可以去取新的输入事件了
38    mQueuedListener->flush();
39}

上述代码片段中,我去掉了所有的判断条件,直接贴出来了处理事件,所以代码句之间不具有逻辑关系.

接下来挑几个目前我觉得很重要的来说.

  • 设备配置:refreshConfigurationLocked.当设备的配置发生改变时需要刷新配置.那么配置都有哪些呢?

  • 获取输入事件getEvent

  • 对输入事件进行一个处理:processEventsLocked

  • 通知IMS,设备发生了变化:notifyInputDevicesChanged

  • 刷新Dispatcher线程维护的队列,通知线程取数据flush

总结下来就是做三件事

  • 获取输入事件(getEvent)

  • 处理输入事件(process)

  • 通知listener

那么输入事件RawEvent有哪些?

  • 设备挂载

  • 设备卸载

  • 其它事件(比如鼠标点击)

至于适配的输入设备有哪些?看设备挂载时的分类就明白了.

获取输入事件

loopOnce中获取输入事件是从EventHub的getEvent开始的.出现了一个新的东西--EventHub

先来看下EventHub这个是什么东西?

EventHub:英文为事件集线器.相当于全国物流集散分拨中心.也就是所有的inputEvent都会集合到eventHub,之后分拨出去.

老习惯,先来看EventHub.h文件(目录/frameworks/native/services/inputflinger/EventHub.h)

翻译一下对于eventHub的说明

EventHub中汇集了系统所有已知的输入设备所发出的事件,输入设备也包括包括通过模拟器虚拟出来的设备.
同时,当add/remove一个输入设备时,eventHub会虚拟出一个输入事件来分发下去.
EventHub提供了一个输入事件流,可以通过getEvent来获取监听到当前的输入事件

说的啥?看不懂?去看看EventHub.h就明白了,代码就不贴了.相当于一个输入设备的管理者和输入事件的中转站.

好,来直接看getEvent是怎么监听输入事件的.

这个代码还是挺多的,分开讲
第一段,是否需要重新open所有设备

1// Reopen input devices if needed.
2if (mNeedToReopenDevices) {
3      mNeedToReopenDevices = false;
4      ALOGI("Reopening all input devices due to a configuration change.");
5      closeAllDevicesLocked();
6      ...
7      break// return to the caller before we actually rescan
8}

当设备的配置configuration(InputReaderConfiguration::CHANGE_MUST_REOPEN)发生改变时,表示所有的输入设备重新open,意思就是所有的设备需要先被remove掉,之后再被add上来.

第二段,是否又需要被remove的设备

 1while (mClosingDevices) {
2    Device* device = mClosingDevices;
3    ...
4    mClosingDevices = device->next;
5   ...
6   event->type = DEVICE_REMOVED;
7   event += 1;
8   delete device;
9   mNeedToSendFinishedDeviceScan = true;
10    ...
11}

如果有需要被remove的设备,那么就需要将event的type设置为DEVICE_REMOVED来通知inputReader,移除掉输入设备

第三段,就是是否需要再次扫描设备

1 if (mNeedToScanDevices) {
2     mNeedToScanDevices = false;
3     scanDevicesLocked();
4     mNeedToSendFinishedDeviceScan = true;
5  }

这个判断条件由谁决定的?在第一段代码中,如果需要重新open所有的输入设备,那么在设备被remove之后,就需要去扫描目前dev/input节点下是否有挂载着输入设备.

注意,重点来了,设备是如何查询是否有挂载输入设备的呢?

最关键一句代码就是scanDevicesLocked(),该方法就是去扫描device了.怎么扫描的呢?

继续查看发现调用的是scanDirLocked(DEVICE_PATH).这个方法什么意思呢?

就是去扫描DEVICE_PATH目录节点所挂载的输入设备,并且把扫描到的输入设备添加到mOpeningDevices.
注意一下mOpeningDevices和mClosingDevices都是以节点方式存储device的.

Android系统底层说白了也是linux实现的,输入设备是如何挂载的?是需要uid(蓝牙鼠标的话是mac地址)和挂载点的.输入设备都是挂载到dev/input目录下的.

使用Ubuntu系统的同学应该都懂这个吧,和挂载数据盘是一个道理,在fstab文件中写上uuid和挂载点,就可以把数据盘挂载上了.和生活中常见的场景类比一下就很好理解.

而scanDirLocked就是一个扫描文件夹的过程,发现所挂载节点下的输入设备,并且加载出来.

至于输入设备是如何挂载到dev/input下的,这个目前没研究,猜测应该是通过mount命令挂载的

等到第三段扫描结束后,就可以去执行add的操作了.也就是第四段代码

第四段,add输入设备

1 while (mOpeningDevices != NULL) {
2     ...
3    event->type = DEVICE_ADDED;
4    event += 1;
5    mNeedToSendFinishedDeviceScan = true;
6   ...
7 }

在第三段,会将扫描到的设备添加到mOpeningDevices列表中,也就是说如果有设备挂载上,但是没有下发给inputReader的话,那么就需要DEVICE_ADD通知一下inputReader设备已经挂载,通知system.

如果真的有输入设备需要加载,那么到现在,已经通知inputReader,有新的输入设备挂载上了.这还不算完,因为还要配置输入设备.所以就有了接下来的代码

第五段

1if (mNeedToSendFinishedDeviceScan) {
2     ...
3    event->type = FINISHED_DEVICE_SCAN;
4   ...
5 }

发送finished_device_scan类型的event,来通知inputReader,输入设备已经挂载成功,并且扫描节点也已经结束了,现在你可以开始配置这个输入设备了.

可以配置哪些参数呢?

这些配置参数是定义在InputReader.h文件中的,共有11个取值

Android鼠标源码研究(三)---获取输入事件_java

把需要reopen的设备remove并重新add对于这些参数配置就不一一解释了,很多我目前也没接触过,一般也都是使用默认的.

总结一下到目前为止,getEvents干了什么?

  • 扫描dev/input节点下看是否新的设备,若有则add

  • 在设备add之后,要通知inputReader去配置inputDevice

也就是说,eventHub自己虚拟了add/remove/config事件,发送给InputReader.

但是其他输入事件呢?也就是设备挂载之后使用过程中的输入事件呢?当然是继续看了..哈哈哈

接着看下一个条件

1while (mPendingEventIndex < mPendingEventCount) {
2 ....
3}

这个是什么意思?判断两个int型的值的大小有什么意义?

意义就在于这两个值的取值,mPendingEventCount很明显是event的数量啊,而mPendingEventIndex顾名思义那就是目前正在处理的event的index了

如果index<count,那就说明还有event事件未处理,也就是说,你还有事儿没处理完呢.

那接下来看两个问题

  • count的改变,也就是事件event的获取(这里的获取指的是从内核获取)

  • index的改变,也就是事件event的处理(这里的处理指的是分发给inputReader)

那么先来第一个问题,事件的获取

event的获取

count就标志着是否有event的事件,所以直接看count的赋值.

count的赋值getEvents的最后

1// Some events occurred.
2mPendingEventCount = size_t(pollResult);

pollResult的赋值也很好找

1int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);

这句话,才是最关键的,要读取输入事件.这个epoll_wai属于api方法,方法有传一些参数,来分别看什么意思

  • 参数1,mEpollFd,所要读取event事件的fd

  • 参数2,mPendingEventItems:用来存储event事件

  • 参数3,EPOLL_MAX_EVENTS:所接受的event事件数量的最大值

  • 参数4,timeoutMillis:阻塞超时事件设置,该操作属于阻塞操作

  • 返回值,pollResult:从内核中读取到的event事件数目

好了,点到为止,分析epoll_wait也不是本文的重点.
只需要明白,该方法返回值是event的数量,mPendingEventItems存储的是event事件.

至此,event的获取结束

event的处理

那就是while循环里的东西了

总结来看,这个while基本就做两件事

  • 获取到epoll_event事件

  • 构造rawEvent事件

都是两个结构体,很好理解.接下来开始处理epoll_event事件

第一步,排除掉不需要发送给inputReader的事件

 1if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {
2    ...
3    if (eventItem.events & EPOLLIN) {
4        ....
5        mPendingINotify = true;
6    }
7    continue;
8}
9
10if (eventItem.data.u32 == EPOLL_ID_WAKE) {
11     if (eventItem.events & EPOLLIN) {
12         ...
13        awoken = true;
14        ...
15      }
16      continue;
17 }

如果epoll_event事件的data.u32的值是EPOLL_ID_INOTIFY/EPOLL_ID_WAKE时,就不会再构造rawEvent事件,直接被continue.

这两个值表示什么意思呢?
EPOLL_ID_INOTIFY:标识设备挂载/卸载事件,所以如果接收到这个事件,就表示输入设备的挂载状态发生了改变,

但此时,还不能立即移除掉某个输入设备.必须要把所有epoll_event全都读取出来,才能通知inputReader说设备已经卸载.所以先记下来一个标志位mPendingINotify.

这个逻辑也很好理解.你想,如果设备的输入事件还没有处理完成,此时直接通知remove掉输入设备.那么这些event怎么办?

这些event相当于寄生虫,这种情况下找不到宿主了要.而且这些event一直堵塞在那儿,占据空间

EPOLL_ID_WAKE:这个表示要唤醒系统.

接下来其他的event都是需要inputReader处理的event了

 1if (eventItem.events & EPOLLIN) {
2   int32_t readSize = read(device->fd, readBuffer,
3          sizeof(struct input_event) * capacity);
4   if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
5         // Device was removed before INotify noticed.
6          ...
7   } else if (readSize < 0) {
8        ...
9   } else if ((readSize % sizeof(struct input_event)) != 0) {
10         ALOGE("could not get event (wrong size: %d)", readSize);
11   } else {
12      ....
13   }
14}

首先读取event的size-->readSize.

什么情况下需要通知inputReader去处理呢?那就是读取到的是input_event的整数倍.也就是读取到的是一个或多个input_event.

所以,排除掉readSize<=0的情况.再排除掉非input_event的情况,接下来就是需要处理的event.

input_event也很简单,总共有四个field:time\type\code\value.根据input_event信息,就可以构造出rawEvent,直接交给inputReader处理即可.

至于inputReader如何处理的,下回分解.

总结一下本文的内容:

本文主要分析了EventHub的作用,以及获取监听事件getEvents的过程.

EventHub相当于一个转接口,将从kernel读取的input_event转换成rawEvent,交给inputReader处理.

EventHub会监听dev/input节点,监听输入设备的挂载/卸载/其他events事件
总体来看,相当于一个中转站的作用了.

 

https://mp.weixin.qq.com/s/kMfAQ5p7ziGYiLr9S3cu_A