一、整体流程
系统Input事件传递主要经过如下几个部分:
1.1输入系统部分
输入子系统
手机的输入设备(包括屏幕、键盘、鼠标等),当前可用,会在文件系统/dev/input中创建对应的设备节点,用户操作输入设备会产生输入事件(按键事件、触摸事件、鼠标事件)等。
/dev/input/event0
/dev/input/event1
/dev/input/event2
...
InputManagerService
IMS初始化过程主要构造了如下结构:
从结构看,IMS核心功能实现在Native层:
IMS由SystemServer创建。
在system_server进程中包含两个重要的线程InputReaderThread和InputDispatcherThread,其内部分别对应InputReader和InputDispatcher两个工作类。
InputReader负责读取底层收集的input事件:从EventHub读InputEvent并且传给InputDispatcher来进行分发。
InputDispatcher负责分发input事件到应用层。WindowManagerService在app端setView的时候就创建了一对Socket连接,InputDispatcher利用这个Socket连接和app端通信。
1.2 WMS处理部分
WMS的职责之一就是输入系统的中转站,WMS作为Window的管理者,会配合IMS将输入事件交由合适的Window来处理。
1.3 View处理部分
app端的ViewRootImpl里面的InputEventReceiver会接到从Socket得到的InputEvent。最终走APP的事件传递,消费事件。
二、InputManagerService初始化过程
通过流程图可以看出,这部分主要是做了一系列的初始化工作:
startOtherServices中,创建了IMS以及WMS,并将WMS中的monitor传给了IMS,作为回调,最后启动IMS。
IMS的初始化中执行了nativeInit,该方法中创建了一个NativeInputManager实例,并且和java层使用的是同一个looper。
在NativeInputManager的初始化中创建了一个Eventhub,同时将这个Eventhub传给新建的Inputmanager,Eventhub就是将数据从硬件驱动上读出来然后传递上来的通道。
InputManager初始化时创建了两个重要线程:InputReaderThread和InputDispatcherThread。
InputManager的start方法,让两个线程开启了循环执行操作。
三、InputReader处理InputEvent流程
简单总结:
InputReader启动后执行loopOnce,它是一个可阻塞循环。
loopOnce循环中会通过Eventhub调用getEvents,来获取底层input事件,getEvents其实分成了三部分,首先是进行device的读取和处理,扫描/dev/input/目录来生成device数据。二是看有没有需要处理的时间,如果有那么就处理了返回。最后是进行等待,等待对应事件的发生。
读到了事件就会调用processEventsLocked处理事件:循环获取EventHub给过来的事件,这里事件包括来自Kernel的input事件和对Input事件的插入和删除操作(这个不管),针对Kernel的input事件,交给processEventsForDeviceLocked处理。
processEventsForDeviceLocked 调用对应的InputDevice处理Input事件,而InputDevice又会去匹配上对应的InputMapper来处理对应事件。(在InputDevice中,存储着许多InputMapper,每种InputMapper对应一类Device,例如:Touch、Keyboard、Vibrator等等……)而调用InputDevice的process函数,就是将Input事件传递给每一个InputMapper,匹配的InputMapper就会对Input事件进行处理,不匹配的则会忽略。
InputMapper将数据综合打包成三种数据封装:NotifyKeyArgs、NotifyMotionArgs和NotifySwichArgs,分别对应key、Motion和Swich事件。
最后调用mQueuedListener->flush(),将事件队列中的所有事件交给在InputReader中注册过的InputDispatcher。InputDispatcher先于InputReader被创建,InputDispatcher没有输入事件处理时会进入睡眠状态,等待InputReader通知唤醒。InputDispatcher的notifyKey函数中会根据按键数据来判断InputDispatcher是否要被唤醒,InputDispatcher被唤醒后,会重新调用dispatchOnceInnerLocked函数将输入事件分发给合适的Window。
InputReader从EventHub获取input event,将input event打包成Args放到InputDispacher的mInboundQueue,然后通过notifyKey唤醒InputDispacher。
四、InputDispatch分发流程
简单总结:
上节InputReader把input event放入了mInboundQueue(NotifyMotionArgs转换为MotionEntry,添加到队尾)。InputDispatcherThread被唤醒后,通过InputDispatcher主要任务是找到对应的window,并建立进程间通信,把input event 传递过去。
- InputDispatcher中,由dispatchOnceInnerLocked处理input event:
1)从mInboundQueue取出事件
2)通过EventEntry的类型,对不同事件进行不同处理,下面以TYPE_KEY为例
3)TYPE_KEY对应会执行dispatchKeyLocked,将事件分发出去
- dispatchKeyLocked中做三件事情:
1)postCommandLocked 让policy处理Home、Menu等系统按键,policy对应的是NativeInputManager
2)findFocusedWindowTargetsLocked 判断发生按键事件的Window并得到对应的inputTargets
3)dispatchEventLocked 通过InputTarget获取对应的Connection,每个焦点窗口在InputDispacher里都有一个对应的Connection,通过这个Connection可以跟InputDispacher通信。然后发送事件EventEntry,先是将eventEntry放入Connection的outboundQueue,再通过InputPublisher将Entry发送给窗口,再将Entry从outboundQueue移到waitQueue里,最后由InputPublisher调用InputChanel的SendMessage(),SendMessage()再动用socket的send()函数,将打包好的Message发送给窗口。
- InputChannel封装了窗口与InputDispatcher间的跨进程通信
应用在ViewRootImpl的setView(),最终会调用IWindowSession的addToDisplay()函数,该函数带上了mInputChannel参数,向WMS注册Channel。
五、App端处理流程
简单总结:
WindowInputEventReceiver中的onInputEvent回调执行enqueueInputEvent,从队列中获取一个QueuedInputEvent,判断是立刻执行还是延迟执行,但是最终都会走doProcessInputEvents。
doProcessInputEvents中主要通过deliverInputEvent进行事件分发。这里核心是InputStage体系,责任链模式。最终会匹配上对应Stage来进行事件分发处理。
- 以Activity,View的按键分发流程相关的InputStage:ViewPostImeInputStage为例,执行ProcessKeyEvent
第一步是调用PhoneWindow.DecorView的dispatchKeyEvent函数,DecorView是View层次结构的根节点,按键从根节点开始按View的事件传递流程走。
第二步是判断按键是否是四向键,或者是TAB键,如果是则需要移动焦点。
本文只是参考了网上的文章,针对input系统总结了一个模糊的流程,input总体来看还是比较复杂的,想要深入学习还是需要针对源码进行详细分析。
参考
https://zhuanlan.zhihu.com/p/29152319
https://blog.csdn.net/urdfmqcul2/article/details/78146424
https://blog.csdn.net/xingchenxuanfeng/article/details/79208005
https://blog.csdn.net/chenweiaiyanyan/article/details/72884141