窗口管理的输入部分主要完成按键、触摸板、鼠标等输入设备的输入,功能包括,输入设备的输入及向焦点窗口和焦点视图的事件派发,事件的插入,事件的过滤,事件的拦截等功能。
整个输入系统包括服务端和客户端两部分,服务端部分主要完成输入设备事件的读取、事件的映射、事件的插入、事件的过滤、事件的拦截等功能;客户端部分主要完成事件向焦点窗口和焦点视图的派发。
输入系统整个架构采用的是管道过滤器模式(Pipe and Filter)架构模式。服务端的InputReader和InputDispatcher对象及客户端的InputQueue对象对应着过滤器构件,具有各自的输入、处理、输出单元,三者通过两个管道连接件连接到一起。
下图是服务端的系统类图,
输入系统服务端整个类图
图中蓝色部分为JAVA部分对应的类,浅色部分为C++部分的类,C++部分主要由InputReader、InputDispatcher两个过滤器构件及辅助对象构成,两个过滤器构件的任务在对应的两个线程InputReaderThread和InputDispatcherThread内运行,两个线程采用管道进行连接和通讯。InputReader、InputDispatcher对象以及InputReaderThread和InputDispatcherThread两个线程对象都由本地InputManager类创建和启动,而本地InputManager类则有上面框架中具有同样名称的InputManager类通过JNI进行实例化,框架层的InputManager类则有窗口管理服务WindowManagerService实例化时进行相应的实例化和启动。输入系统管道的打开及服务端管道的注册则在WindowManagerService调用addWindow函数新建窗口时创建和注册;而客户端管道的注册由客户端ViewRootImpl对象调用SetView时注册。因此输入系统服务端运行在窗口管理服务内,客户端运行在应用程序所在进程内
一、 输入系统服务端事件读取和派发流程(正常情况)
整个流程由InputReaderThread线程触发,在线程中调用InputReader对象的loopOnce函数,在loopOnce函数中首先通过EventHub对象的getEvents函数读取输入设备事件,经过处理后从事件中获得对应的deviceId,根据deviceId在mDevices数组中找到对应的输入设备对象,然后调用InputDevice的process函数,在InputDevice的process函数中对每个事件由InputDevice对象的mMappers数组中登记的每个InputMapper对象依次处理,分别调用其process函数,对于按键事件对应的是KeyboadInputMapper对象,在InputMapper对象的process函数中把事件封装成NotifyArgs对象,然后调用InputMapper的监听对象的notifyKey函数。InputMapper的监听对象对应的是QueuedListener,而在QueuedListener的notifyKey函数中把NotifyArgs对象放入QueuedListener的mArgsQueue队列。然后返回并在loopOnce函数中调用QueuedListener对象的flush函数。在flush函数中依次调用mArgsQueue队列中由事件封装成的NotifyArgs对象的notify函数,notify函数的参数也是一个监听对象,在QueuedInputListener对象实例化时赋值,对应的是一个InputDispatcher对象。NotifyArgs对象的notify函数调用其参数引用对象的notifyKey函数,也就是调用InputDispatcher对象notifyKey函数(以NotifyArgs对象为参数),InputDispatcher对象的notifyKey函数根据传进来的对象构造成KeyEntry对象然后调用enqueueInboundEventLocked函数放入mInboundQueue队列,最后调用Looper的wake函数,唤醒管道对方的线程即InputDispatcherThread线程。
下面是InputDispatcherThread线程唤醒后的派发流程图:
首先调用InputDispatcher对象的dispatchOnce函数;dispatchOnce函数又调用dispatchOnceInnerLocked函数,在dispatchOnceInnerLocked函数中进行一系列判断后若是按键事件调用dispatchKeyLocked函数;在dispatchKeyLocked函数中使用findFocusedWindowTargetsLocked函数寻找焦点窗口,并把找到的焦点窗口调用函数addWindowTargetLocked放入mCurrentInputTargets数组中,然后调用addMonitoringTargetsLocked为刚才找到的焦点窗口绑定一个inputChannel通道,接着调用dispatchEventToCurrentInputTargetsLocked函数;在dispatchEventToCurrentInputTargetsLocked函数中首先根据刚才焦点窗口绑定的inputChannel找到对应的一个Connection对象,然后调用prepareDispatchCycleLocked;在prepareDispatchCycleLocked函数中调用enqueueDispatchEntryLocked函数,在enqueueDispatchEntryLocked函数中根据传进来的事件对应的对象eventEntry构造一个 DispatchEntry对象,DispatchEntry对象包含inputTarget的信息,并放入connection 对象outboundQueue队列;接着prepareDispatchCycleLocked函又调用activateConnectionLocked把connection 对象放入mActiveConnections数组,接着又调用startDispatchCycleLocked;在startDispatchCycleLocked函数中首先调用connection对象中的inputPublisher对象的publishKeyEvent函数向客户端通过异步共享内存发布事件,然后调用inputPublisher对象的sendDispatchSignal函数通过管道向客户端发送DispatchSignal信号,告诉客户端由事件到达,触发客户端事件处理流程,到这里就完成了服务端的输入事件处理。
服务端的输入事件处理流程中主要采用了Observer模式(也可称为监听者模式)和命令模式以及策略模式,如QueuedListener对象是InputMapper对象的监听对象,两个类之间的关系构成Observer模式;InputMapper对象本身就是设备事件映射策略的实现,InputMapper是一个虚拟类,共有六个派生类,在InputReader对象的createDeviceLocked函数中根据设备的类型实例化不同的InputMapper的派生类,KEYBOARD_TYPE对应的是KeyboardInputMapper; 而在InputMapper对象向InputDispatcher对象转发事件过程中采用了命令模式,模式类图如下:
二、输入系统客户端事件派发过程
客户端输入派发相关的类图
上图是客户端相关对象类图,也有本地端和JAVA端对象构成。在客户端收到服务端管道发送的事件时,就触发客户端管道注册时注册的回调函数(NativeInputQueue对象的handleReceiveCallback函数)。整个事件接收和派发流程如下:
1、首先handleReceiveCallback根据回调来的接收管道参数receiveFd在NativeInputQueue队列中找到connectionIndex,根据connectionIndex找到对应的connection对象;然后调用connection对象中的inputConsumer对象的receiveDispatchSignal函数读取服务端通过管道发送的DispatchSignal信号,并判断发送信号标志是否正确;接着调用inputConsume对象的consume函数从共享内存中读取mSharedMessage消息,并根据消息类型构造事件,如按键对应的keyEvent事件,并转换为JAVA事件;然后通过JNI回调JAVA对象InputQueue的dispatchKeyEvent函数;
2、在InputQueue对象的dispatchKeyEvent函数中调用inputHandler对象的handleKey函数,inputHandler对象是ViewRootImpl对象中的内部对象;
3、inputHandler对象的handleKey函数又调用ViewRootImpl对象的dispatchKey函数;dispatchKey函数通过enqueueInputEvent向ViewRootImpl对象的事件处理线程发送异步消息;ViewRootImpl对象的事件处理函数handleMessage根据消息类型进行相应处理,这里调用的是deliverKeyEvent函数;deliverKeyEvent函数首先调用主视图的dispatchKeyEventPreIme函数,接着调用deliverKeyEventPostIme函数;deliverKeyEventPostIme函数调用主视图的dispatchKeyEvent函数在视图树中派发事件;dispatchKeyEvent函数派发完后返回进行其它处理如聚焦切换等;
4、主视图的dispatchKeyEvent函数首先判断是否进行快捷键调用,然后调用主视图绑定的ACTIVITY的dispatchKeyEvent函数;ACTIVITY的dispatchKeyEvent函数返回后如果ACTIVITY的dispatchKeyEvent函数没有处理事件,则根据按键状态调用PhoneWindow的onKeyDown函数或onKeyUp函数;
5、在ACTIVITY的dispatchKeyEvent函数中首先通过getWindow函数获得ACTIVITY对象的Window,然后调用Window的superDispatchKeyEvent在视图树中派发事件,派发到各个焦点ViewGroup和Focuse View;如果视图树没有处理事件则调用event.dispatch函数派发事件由ACTIVITY对象本身处理,ACTIVITY对象的事件回调接口在这里被调用;
6、上一步造成PhoneWindow的superDispatchKeyEvent函数被调用,其实际又调用的是主视图DecorView的superDispatchKeyEvent函数;DecorView的superDispatchKeyEvent函数调用其父类的dispatchKeyEvent函数,DecorView继承于ViewGroup,因此ViewGroup的dispatchKeyEvent函数被调用;
7、ViewGroup的dispatchKeyEvent函数首先调用其父类的dispatchKeyEvent函数向父类视图派发事件;父类的视图没有处理事件,再通过ViewGroup对象的mFocused(焦点子视图)成员向下一级焦点视图派发事件。这样一级级向各个焦点ViewGroup和Focuse View视图派发事件,完成整个视图树的事件派发。
客户端的事件派发主要用到了职责链模式(Chain of Responsibility)。根据整个窗口构成的视图树进行事件的派发。
由服务端的inputPublisher对象和客户端的inputConsumer对象还共同构成了生产/消费者模式,通过inputPublisher对象发布消息和事件, inputConsumer对象消费和读取消息。
职责链模式图
三 事件的过滤和插入及事件的拦截流程
事件的过滤和拦截采用了策略模式机制进行不同的事件处理,由类图中的InputManager、InputFilter、PhoneWindowManager、Callbacks、InputMonitor等JAVA对象共同完成事件的过滤和拦截;Callbacks、InputMonitor主要负责回调事件的转发,InputFilter负责事件过滤请求的处理;应用可以通过InputManager对象的injectInputEvent函数完成事件的插入;PhoneWindowManager对象负责派发事件的拦截。事件的过滤请求和拦截请求流程相似,只是目的地不一样。
InputManager对象的injectInputEvent函数通过JNI调用InputDispatcher的injectInputEvent函数,在InputDispatcher的injectInputEvent函数中首先进行一些权限判断,然后根据注入的事件类型构造一个插入事件,如按键对应的KeyEntry对象,然后通过enqueueInboundEventLocked函数把事件注入mInboundQueue队列,然后唤醒派发流程,事件注入完成。
过滤请求和拦截请求都由InputDispatche对象发起,在InputDispatche对象监听到事件调用其 notifyKey函数时根据过滤选项是否打开调用InputDispatche对象的mPolicy对象成员的filterInputEvent函数。而对于按键事件的拦截,mPolicy对象有两个拦截回调函数(interceptKeyBeforeQueueing及interceptKeyBeforeDispatching),interceptKeyBeforeQueueing也同样在InputDispatche对象的notifyKey函数中调用;而interceptKeyBeforeDispatching回调比较复杂,使用了命令模式。
在InputDispatche对象的dispatchKeyLocked函数中判断事件的policyFlags是否含有POLICY_FLAG_PASS_TO_USER,如果含有POLICY_FLAG_PASS_TO_USER就调用InputDispatche对象的postCommandLocked函数,postCommandLocked的参数为doInterceptKeyBeforeDispatchingLockedInterruptible函数指针。在postCommandLocked函数中构造一个CommandEntry对象,其command方法为参数传进来的函数指针,并放入mCommandQueue队列,然后函数dispatchKeyLocked返回,事件被拦截不再继续派发。
在函数dispatchKeyLocked返回到dispatchOnce,继续调用runCommandsLockedInterruptible函数进行mCommandQueue队列命令对象的处理,对于mCommandQueue队列中的每一个命令对象调用命令对象的command方法,对于刚才放入的命令实际调用的是作为postCommandLocke参数的InputDispatch::doInterceptKeyBeforeDispatchingLockedInterruptible函数指针;因此doInterceptKeyBeforeDispatchingLockedInterruptible函数被调用,在doInterceptKeyBeforeDispatchingLockedInterruptible函数中调用mPolicy对象的interceptKeyBeforeDispatching事件拦截回调。
mPolicy对象在InputDispatcher对象构造时作为参数传入,实际指向的是NativeInputManager对象,因此NativeInputManager对象的相应函数被调用。而NativeInputManage对象的这三个回调又都通过JNI调用上面JAVA层的InputManager对象的内部Callbacks 对象的相应函数。Callbacks 对象的filterInputEvent函数回调InputFilter对象的相应函数进行事件过滤处理,而对于Callbacks 对象的拦截回调函数回调的是 WindowManagerService窗口管理服务的InputMonitor对象的相应函数。InputMonitor对象的对应函数又回调WindowManagerService窗口管理服务的mPolicy对象的对应函数。
WindowManagerService窗口管理服务的mPolicy对象是PhoneWindowManager对象,由PolicyManager.makeNewWindowManager()构造,这里采用了简单工厂模式。因此上面的拦截回调最终调用的是PhoneWindowManager对象的相应函数,完成事件的拦截处理。
InputFilter对象由InputManager对象的setInputFilter函数赋值,并通过JNI打开InputDispatche对象的FilterEnabled标志,完成过滤后的事件又在InputFilterHost对象中采用InputManager对象的injectInputEvent函数一样的流程重新注入InputDispatch对象的mInboundQueue队列。