Android Q版本Input系统流程总结篇(终极面试总结)

第一章         文章简介... 3

第二章         InputManagerService启动... 3

第三章         触屏事件读取和分发... 4

第四章         详细源码分析... 5

第五章         结束语... 6

 

 

 

本章总结了Android Q上的InputManagerService的事件分发流程,其中以触屏事件为例讲解了整个触摸事件的分发流程,然后针对性的描述了如何去扩展事件分发这款的逻辑。InputManagerService在后文中用IMS代替。

IMS是在系统加载后,SystemServer中进行创建、初始化和启动的。在SystemServer中的startOtherService方法中创建了IMS对象,InputManagerService的构造方法中需要传入当前系统的context。在IMS的构造方法中首先通过DisplayThread创建了一个InputManagerHandler对象,用于控制输入设备改变、切换键盘布局、重新加载键盘、重新加载设备以及更新键盘布局的操作。另外调用了native方法nativeInit即就是jni层的com_android_server_input_InputManagerService文件中的nativeInit方法,该方法有三个参数、第一个是IMS服务本身,第二个是系统的Context,第三个是消息队列,改队列是创建的InputManagerHandler中的队列。用于缓冲输入事件。在nativeInit方法中创建了NativeInputManager对象,并将该对象使用集合存储,用于后期事件发送到上层Java层。

在NativeInputManager的构造方法中放入了通过InputManagerHandler创建的消息队列的Looper对象,用于操作消息队列中的消息,保证java层和nativie层的不断交互。同时在构造法中创建了InputManager对象,将NativeInputManager对象通过参数的形式传递,NativeInputManager实现了接口InputReaderPolicyInterface、InputDispatcherPolicyInterface、PointerControllerPolicyInterface,而InputManager中恰好就需要的是InputReaderPolicyInterface和InputDispatcherPolicyInterface对象,通过这两个对象利用工厂模式分别创建了InputReader、 InputDispatcher对象,初始化创建了两个线程InputReaderThread和InputDispatcherThread,InputReaderThread是负责事件读取的线程,InputDispatcherThread是负责事件派发的线程。而且在Android Q版本上单独创建了InputClassifier对象,其中InputReader中将InputDispatcher对象和InputClassifier作为传参保存到InputReader对象中,另外创建InputRedaer对象时创建了一个非常重要的对象EventHub,该对象是负责监听和读取驱动写入到/dev/input目录下的事件信息。

在EventHub构造方法中初始化了一些变量的值和注册了epoll机制、Inotify机制。比如打开设备变量mOpeningDevices默认值是空,关闭设备mClosingDevices链表变量默认值是空,是否扫描完成变量mNeedToSendFinishedDeviceScan默认值是false,是否需要重新打开变量mNeedToReopenDevices的值默认是false,是否需要重新扫描设备mNeedToScanDevices变量是true,事件数量变量mPendingEventCount默认是0,下一个事件下标变量mPendingEventIndex是0。其次利用linux中的inotify机制和epoll机制分别通过inotify_init、 epoll_create1方法创建了mEpollFd、mINotifyFd变量,然后将mINotifyFd通过inotify_add_watch方法注册了/dev/input目录的文件删除和创建,利用epoll_ctl对inotify进行控制。

在这里要说明的是在Android P版本上还没有采用工厂模式创建InputReader和InputDispatcher对象,而且在Android Q版本上在EventHub的构造方法中多注册了一个目录/dev,从代码中看是该目录下写入的时间是支持V4L机制的时间,V4L我暂时没时间去看,是一种Linux机制。到这里IMS对象就创建完成了。

回到SystemServer中,将刚刚创建的IMS对象使用传参的形式创建了WindowManagerService,目的是在WMS中保存IMS对象,当存在窗口发生变化的时候,及时将窗口信息传递给IMS中。同理将创建好的WMS对象中实现了InputManagerService.WindowManagerCallbacks接口的WindowManagerCallbacks对象设置给IMS,这样就能保证IMS能够直接将其中的事件回调给WMS中。最后一步,IMS调用start方法调用nativie层的方法,最后启动创建的InputReaderThread、InputDispatcherThread两个线程。

在第二章中我说到了Android中是使用linux中的inotify和epoll机制量监听和读取事件的,而且系统启动完成后就会启动两个线程。那么接下来我们就看一下事件是如何读取和分发的。

在InputReaderThread线程启动之后就会执行loopOnce方法,就和Java线程中的run方法一样。一开始InputReaderThread处于一个休眠状态,当/dev/Input目录下有文件创建或删除时就会被inotify检测到,通知给epoll机制并唤醒当前的InputReaderThread线程,从而执行loopOnce方法,在方法内会判断是否存在设备配置改变,然后做出相应的逻辑,如果有配配置刷新就先刷新配置,如果没有就会利用EventHub掉用getEvent方法往mEventBuffer中读取事件,mEventBuffer是一个链表,在读取时系统指定最大只能读取256个,getEvent总共有三个参数mEventBuffer只是其中之一,还有一个是超时时间timeoutMillis和bufferSize也就是刚刚的最大长度256.

在读取过程中会根据EventHub创建时变量的值判断是否需要重新打开设备,如果需要重新打开设备就要修改mNeedToReopenDevices和mNeedToScanDevices值,也就说只要打开设备就要重新扫描,另外要调用closeAllDevicesLocked方法遍历系统当前存在的设备列表mDevices将每一个设备进行关闭,解注册epoll、释放设备序号、释放指针、从列表移除以及将关闭的设备添加到mClosingDevices链表中,然后遍历mClosingDevices链表将每一个设备关闭封装成一个Event放入到buffer中继续下面的扫描设备操作。设备扫描和设备关闭一样,需要修改mNeedToScanDevices和mNeedToSendFinishedDeviceScan两个值,然后调用scanDevicesLocked方法从dev/input下读取设备信息,添加序号、注册epoll、加载配置以及其他操作,最后将设备对象放入到mDevices链表中,同时也添加到mOpeningDevices中,然后将遍历mOpeningDevices链表将其中每个设备封装成为一个打开设备的Event放到buffer中,最后能还将 生成一个臊面设备完成的事件,也放入到buffer中。这些都是设备事件,分为三种设备移除、设备添加、设备扫描。

再来看一下事件读取,对于触屏事件的读取是采用缓冲区的方式调用epoll_wait向mPendingEventItems链表中一次性读取足够多的事件,默认值EPOLL_MAX_EVENTS是16个。然后遍历出mPendingEventItems事件进行校验等各种一系列操作,然后符合条件的进行添加封装为Event事件放入到buffer中,然后将buffer和事件的数量做差值算出实际读取到的数据数量。当在缓冲区读到一定到事件就会调用processEventsLocked方法将设备事件和触屏事件分别进行处理,此处就不在详细描述,过于细化。在此处就会判断是多指操作还是触摸屏轨迹球,调用process、mapper将事件分为key和motion最后以notifyMotion方式放入到QueuedInputListener中的mArgsQueue中,然后调用flush方法从队列中取出事件,利用保存在InputReader中的InputDispatcher对象调用notifyMotion方法将事件传递给InputDispatcher在该方法中校验然后利用WMS对象通过interceptMotionBeforeQueueing将事件交给WMS先处理,如果事件不被处理,那么就将事件封装成为MotionEntry然后放入到mInboundQueue队列,然后唤醒分发线程InputDispatcherThread进行分发,注意该操作是在InputReaderThread中执行的,最后当事件解析完成之后,将线程休眠时间改为C++中的最大值,读取线程进入休眠状态。

当InputDispatcherThread被唤醒就会执行dispatchOnce方法。在分发过程中会先检测是否有CommandEntry,来更新PowerManagerService中的屏幕状态。如果没有就调用dispatchOnceInnerLocked进行分发。在配发过程中会判断配发线程是否可用、是否被冻结配发,是否有电源键、swtichapp键等,判断mPendingEvent是否为空,如果为空且满足上述条件,就会存mInboundQueue队列中取出一个事件进行分发,在配发过程中会先重置当前ANR,判断当前事件是否被丢弃,等操作,如果不是则调用dispatchMotionLocked继续分发,下一步就是寻找合适的窗口将事件分发给对应窗口,调用findFocusedWindowTargetsLocked遍历所有的InputWindowHandle信息,找到聚焦窗口放入到inputTargets集合中进行派发,当配发开始后要修改配发进度,调用prepareDispatchCycleLocked获取窗口的InputChannel、connect的信息,调用enqueueDispatchEntriesLocked、startDispatchCycleLocked、publishKeyEvent、sendMessage方法将事件放入到InputManagerHandler的消息队列中,Looper也是利用epoll机制,监听并存消息队列中取出事件,回调给ViewRootImpl,最终通过一系列diliver等操作,将事件交给View,然后View做出点击、长按、等逻辑。最终View接收到消息后会将结果通过消息机制传送给InputDispatcher,收到消息后将当前事件从mInboundQueue中取出放入到mWaitQueue中。然后通过CommandEntry方式调用prepareDispatchCycleLocked方法继续发送下一条事件直到事件完全派发完毕,进入休眠状态。

InputManagerService入门之Epoll&INotify机制

https://blog.csdn.net/chen364567628/article/details/102790372

一张图带你掌握androidQ的InputManagerService启动

https://blog.csdn.net/chen364567628/article/details/102811219

一张图带你掌握InputReader事件读取流程

https://blog.csdn.net/chen364567628/article/details/102998165

一张图带你掌握Android Q上InputDispatcher事件分发流程(系统层)

https://blog.csdn.net/chen364567628/article/details/103467399

一张图带你掌握Android Q上InputDispatcher事件分发流程(View层)

https://blog.csdn.net/chen364567628/article/details/103589037

 

希望我的文章能为广大的Android开发者带来一点思路,觉得我写的好的评论、关注我。在学习Android源码这方面我拥有极大的兴趣。我希望通过自己的学习和分享能为中国的Android开发者,以及操作系统贡献一份力量。当然了由于我个人的能力有限、在文章中可能出现一些对源码理解不到位的地方,希望在这方面精通的专家能够指出我的不足,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值