整个输入系统包括服务端和客户端两部分,服务端部分主要完成输入设备事件的读取、事件的映射、事件的插入、事件的过滤、事件的拦截等功能;客户端部分主要完成事件向焦点窗口和焦点视图的派发。
输入系统的整个架构采用的是管道过滤器模式(Pipe and Filter)架构模式。服务端的InputReader和InputDispatcher对象及客户端的InputEventReceiver对象及InputConsumer对象对应着过滤器构件,具有各自的输入、处理、输出单元,两者通过管道连接到一起。
下图是ANDROD4.4 版本的服务端的系统类图,4.4 版本的输入系统相对于4.0版本作了如下较大改动:输入管理服务从窗口管理服务中独立了出来, C++ 实现部分结构也作了优化,其各个模块部分之间采用了接口进行交互,包括输入事件读取接口InputReaderInterface、事件监听和提交接口InputListenerInterface和InputDispatcherInterface,以及原始事件接收接口EventHubInterface。
图中上面部分为JAVA部分对应的类,主要是一个输入管理服务InputManagerService和客户端的事件接收和事件派发类,下面部分为C++本地实现的部分,C++部分主要由InputReader、InputDispatcher两个过滤器构件和线程及辅助对象构成,两个过滤器构件的任务在对应的两个线程InputReaderThread和InputDispatcherThread内运行。
InputReader类是InputReaderInterface接口的实现,用来实现事件的读取功能。 InputDispatcher类是InputDispatcherInterface接口的实现类, 而InputDispatcherInterface 接口又是InputListenerInterface接口的派生接口,用来实现输入事件的监听和提交。
整个流程包括事件读取流程和事件提交两个流程,分别运行在InputReaderThread和InputDispatcherThread两个线程内。在输入读取线程内,InputReaderInterface接口对象通过EventHubInterface接口从驱动读取原始输入事件,经过事件映射后通过InputListenerInterface接口提交给InputDispatcher对象,在InputDispatcherThread线程内由对象InputDispatcher对象、InputPublisher
对象共同负责事件的提交,最后通过InputChannel对象发送输入事件到服务端的管道内,由客户端通过InputConsumer从管道的另一端读取事件。
InputReader、InputDispatcher对象以及InputReaderThread和InputDispatcherThread两个线程对象都由本地InputManager类创建和启动,而本地InputManager类则有上面框架中的InputManagerService通过JNI进行实例化,在其nativeInit JNI接口中创建一个NativeInputManager对象作为JAVA层与本地层的交互对象,NativeInputManager对象实例化时创建本地InputManager对象和EventHub对象。
输入系统管道的打开及服务端事件提交管道的注册则在WindowManagerService调用addWindow函数新建窗口时创建和注册;而客户端事件输入管道的注册由客户端ViewRootImpl对象调用SetView时注册。因此输入系统服务端运行在输入管理服务进程内,客户端运行在应用程序所在进程内。
一、 输入系统服务端事件读取和派发流程(正常情况)
整个流程由InputReaderThread线程触发,在线程中调用InputReader对象的loopOnce函数,在loopOnce函数中首先通过EventHubInterface接口的getEvents函数读取输入设备事件,经过处理后从事件中获得对应的deviceId,根据deviceId在mDevices数组中找到对应的输入设备对象或者构造新的InputDevice对象,然后调用InputDevice的process函数。
在InputDevice的process函数中对每个事件由InputDevice对象的mMappers数组中登记的每个InputMapper对象依次处理,分别调用其process函数,对原始输入事件进行映射。
不同的事件类型采用了不同的具体InputMapper对象进行映射,如按键事件对应的是KeyboadInputMapper对象,另外还有TouchInputMapper、CursorInputMapper、VibratorInputMapper、SwitchInputMapper、JoystickInputMapper等映射对象,这些对象的类都是InputMapper虚拟类的具体类。
一个支持多种类型的事件输入的输入设备,需要使用多个不同的输入映射对象分别进行映射。
在InputMapper对象的process函数中把事件封装成NotifyArgs对象,然后调用InputMapper的监听对象QueedInputListener的相关事件监听接口函数,如notifyKey函数。
在QueedInputListener的监听接口函数中作为参数传进来的NotifyArgs对象放入QueedInputListener的事件队列ArgsQueue中,然后返回并在loopOnce函数中调用QueuedListener对象的flush函数。
在flush函数中依次调用ArgsQueue队列中由事件封装成的NotifyArgs对象的notify函数,notify函数的参数也是一个监听对象,在QueuedInputListener对象实例化时赋值,对应的是一个InputDispatcher对象。NotifyArgs对象的notify函数调用其参数引用对象(InputDispatcher)的事件通知函数,以NotifyArgs对象为参数,如按键事件对应的notifyKey。
InputDispatcher对象的事件通知函数根据作为参数传进来的事件构造一个EventEntry对象,然后调用enqueueInboundEventLocked函数把构造的EventEntry事件对象放入内部事件队列中(mInboundQueue),最后调用Looper的wake函数,唤醒事件提交线程即InputDispatcherThread线程来进行事件向管道中的提交。
下面是InputDispatcherThread线程唤醒后的输入事件提交流程图:
InputReader读取的输入事件作为参数通过InputListenerInterface的接口函数被InputDispatcher接收。接收的事件构造为EventEntry对象放入InputDispatcher对象的EventEntry类型的队列mInboundQueue中,并唤醒InputDispatcherThread线程。
InputDispatcherThread线程不断从该队列中读取输入事件。首先调用InputDispatcher对象的dispatchOnce函数;dispatchOnce函数又调用dispatchOnceInnerLocked函数,在dispatchOnceInnerLocked函数中进行一系列判断后若是按键事件则调用dispatchKeyLocked函数;
在dispatchKeyLocked函数中使用findFocusedWindowTargetsLocked函数寻找焦点窗口,并把找到的焦点窗口通过调用函数addWindowTargetLocked放入mCurrentInputTargets数组中,然后调用addMonitoringTargetsLocked为刚才找到的焦点窗口绑定一个inputChannel通道,接着调用dispatchEventLocked函数;
在dispatchEventLocked函数中首先根据刚才焦点窗口绑定的inputChannel找到对应的一个Connection对象,然后调用prepareDispatchCycleLocked;
在prepareDispatchCycleLocked函数中调用enqueueDispatchEntryLocked函数,在enqueueDispatchEntryLocked函数中根据传进来的事件对应的对象eventEntry构造一个 DispatchEntry对象,DispatchEntry对象中包含inputTarget的信息,并放入connection对象outboundQueue队列;接着又调用startDispatchCycleLocked;
在startDispatchCycleLocked函数中通过connection对象中的inputPublisher对象引用调用inputPublisher对象的publishKeyEvent函数或publishMotionEvent函数向客户端发送事件,到这里就完成了服务端的输入事件处理。
服务端的输入事件处理流程中主要采用了Observer模式(也可称为监听者模式)和命令模式以及策略模式,如QueuedListener对象是InputMapper对象的监听对象,两个类之间的关系构成Observer模式;
InputMapper对象本身就是设备事件映射策略的实现,InputMapper是一个虚拟类,共有六个派生类,在InputReader对象的createDeviceLocked函数中根据设备的类型实例化不同的InputMapper的派生类,如KEYBOARD_TYPE对应的是KeyboardInputMapper;
而在InputMapper对象向InputDispatcher对象转发事件过程中采用了命令模式,模式类图如下:
二、输入系统客户端事件派发过程
相对于4.0以前的版本,4.4 版本的客户端向视图的事件派发机制也作了大的改动,如上图所示:客户端事件的派发任务主要由ViewRootImpl类承担,在ViewRootImpl类中添加了几个内部事件处理对象(InputStage的派生类),事件接收机制也更加有效。整个事件接收和派发流程如下:
客户端的事件接收通过InputEventReceiver对象(具体为ViewRootImpl在SetView函数中实例化的一个WindowInputEventReceiver类型的对象)和对应的本地NativeInputEventReceiver对象共同完成。
NativeInputEventReceiver对象在收到输入系统产生的输入事件时,通过jni 调用InputEventReceiver对象的dispatchInputEvent接口向JAVA传送底层来的输入事件,InputEventReceiver对象的dispatchInputEvent接口又调用其onInputEvent接口,在onInputEvent接口函数中调用ViewRootImpl对象的enqueueInputEvent函数放入ViewRootImpl对象的事件队列(QueuedInputEvent)中。然后调用doProcessInputEvents函数立即对队列中的事件进行事件处理或者调用scheduleProcessInputEvents来异步处理事件。
在 doProcessInputEvents函数调用deliverInputEvent函数来提交队列中的事件,在deliverInputEvent函数中调用InputStage对象的deliver函数在一个事件处理责任链中传递和处理事件。
如类图所示,NativePreImeInputStage、ViewPreImeInputStage、ImeInputStage、EarlyPostImeInputStage、NativePostImeInputStage、ViewPostImeInputStage、SyntheticInputStage构成一个输入事件责任处理链,如果本阶段对事件没有处理,则传递到下一个对象进行处理,直至事件被处理。NativePreImeInputStage、ViewPreImeInputStage、ImeInputStage三个类用来实现输入法的按键派发和处理,如果事件不传递到输入法服务中,这三个类可以跳过,直接从EarlyPostImeInputStage对象开始处理,在ViewPostImeInputStage对象处理阶段调用了主View 对象(对应PhoneWindow中的DecorView对象)的事件提交函数如(dispatchKeyEvent)函数向视图对象提交输入事件,在当前窗口的视图树中派发事件。派发完后返回进行其它处理如聚焦切换等;
主视图的dispatchKeyEvent函数首先调用dispatchKeyShortcutEvent及performPanelShortcut函数进行快捷键处理,然后调用getCallback返回视图的回调对象,ACTIVITY实现了 Window.Callback,因此如果主视图绑定有ACTIVITY,则getCallback返回主视图邦定的ACTIVITY对象,则主视图的dispatchKeyEvent函数继续调用ACTIVITY对象的dispatchKeyEvent函数进行事件派发,否则调用其父类的dispatchKeyEvent函数直接在视图树上向上传递事件。
在ACTIVITY的dispatchKeyEvent函数中首先通过getWindow函数获得ACTIVITY对象的Window,然后调用Window的superDispatchKeyEvent在视图树中派发事件,派发到各个焦点ViewGroup和Focuse View;如果视图树没有处理事件则调用event.dispatch函数派发事件由ACTIVITY对象本身处理,ACTIVITY对象的事件回调接口在这里被调用;
一步造成PhoneWindow的superDispatchKeyEvent函数被调用,其实际又调用的是主视图DecorView的superDispatchKeyEvent函数;DecorView的superDispatchKeyEvent函数调用其父类的dispatchKeyEvent函数,由于DecorView继承于ViewGroup,因此最终ViewGroup的dispatchKeyEvent函数被调用;
ViewGroup的dispatchKeyEvent函数首先调用其父类的dispatchKeyEvent函数向父类视图派发事件;父类的视图没有处理事件,再通过ViewGroup对象的mFocused(焦点子视图)成员向下一级焦点视图派发事件。这样一级级向各个焦点ViewGroup和Focuse View视图派发事件,完成整个视图树的事件派发。
如果ACTIVITY的dispatchKeyEvent函数和当前窗口的视图树都没有处理事件,则根据按键状态调用PhoneWindow的onKeyDown函数或onKeyUp函数。
以上事件在视图树的派发也是职责链模式(Chain of Responsibility)的采用,根据构成窗口的视图树完成事件的派发。
由服务端的inputPublisher对象和客户端的inputConsumer对象还共同构成了生产/消费者模式,通过inputPublisher对象发布事件, inputConsumer对象消费和读取事件。
三、事件的过滤和插入及事件的拦截流程
事件的过滤和拦截采用了策略模式机制进行不同的事件处理,由类图中的InputManagerService、InputFilter、PhoneWindowManager、WindowManagerCallback、InputMonitor等JAVA对象共同完成事件的过滤和拦截;WindowManagerCallback、IInputMonitor主要负责回调事件的转发,IInputFilter接口是事件过滤请求的处理接口;应用可以通过InputManager对象的injectInputEvent函数完成事件的插入;PhoneWindowManager对象最终负责派发事件的拦截。事件的过滤请求和拦截请求流程相似,只是目的地不一样。
事件的插入流程如下:InputManager对象的injectInputEvent函数调用InputManagerService服务的同名injectInputEvent函数,InputManagerService服务的injectInputEvent函数通过JNI调用InputDispatcher的injectInputEvent函数,在InputDispatcher的injectInputEvent函数中首先进行一些权限判断,然后根据注入的事件类型构造一个插入事件,如按键对应的KeyEntry对象,然后通过enqueueInboundEventLocked函数把插入事件注入mInboundQueue队列,然后唤醒派发流程,事件注入完成。
过滤请求和拦截请求流程都由InputDispatche对象发起,并通过InputDispatcherPolicyInterface接口函数向JAVA层传递请求。
InputDispatcherPolicyInterface接口有几个输入事件拦截回调接口函数(如按键事件对应的 interceptKeyBeforeQueueing、interceptKeyBeforeDispatching事件拦截接口),用来向java层传递事件队列前拦截请求和事件提交前拦截请求。InputDispatcherPolicyInterface接口还有一个事件过滤接口filterInputEvent 函数用来传递向java 层过滤请求。
NativeInputManager对象是InputDispatcherPolicyInterface接口的实现对象,而在InputDispatcher对象中则有一个指向NativeInputManager对象的引用字段mPolicy,用来在事件提交线程中通过InputDispatcherPolicyInterface接口并通过NativeInputManager对象向java层传送事件拦截和过滤请求。
事件监听请求在InputDispatche对象的函数notifyKey中发送,在事件输入线程接收到事件并调用InputDispatcher对象的函数notifyKey向其传送事件时,notifyKey函数根据过滤选项是否打开调用InputDispatcherPolicyInterface的接口函数filterInputEvent来发送事件过滤请求。
事件的拦截请求包括队列前的事件拦截和提交前的拦截,下面以按键事件的拦截为例进行说明。
对于队列前的按键事件拦截请求,在InputDispatche对象的notifyKey函数和injectInputEvent函数提交输入按键事件到队列之前,通过调用InputDispatcherPolicyInterface接口对象的interceptKeyBeforeQueueing函数发送事件的队列之前拦截请求;
而对于事件提交前的按键事件拦截通过调用InputDispatcherPolicyInterface接口对象的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函数中调用InputDispatcherPolicyInterface接口对象的interceptKeyBeforeDispatching事件拦截回调函数发送事件提交前的拦截请求。
InputDispatcherPolicyInterface接口对象在InputDispatcher对象构造时把对NativeInputManager对象的引用作为参数传入赋值给InputDispatcher对象的InputDispatcherPolicyInterface接口类型的字段mPolicy,因此上述的对InputDispatcherPolicyInterface接口事件的调用实际调用的是NativeInputManager对象的相应函数。
而NativeInputManage对象的拦截和过滤回调函数又通过JNI调用JAVA层InputManagerService服务的相应函数,如interceptKeyBeforeDispatching、interceptKeyBeforeQueueing和filterInputEvent函数。
InputManagerService服务的事件拦截函数实际调用的是WindowManagerCallbacks接口的相应接口函数,而InputMonitor对象是WindowManagerCallbacks接口的实现,因此对WindowManagerCallbacks接口函数的调用就是对InputMonitor对象的相应函数的调用。InputMonitor对象的对应函数又通过WindowManagerService窗口管理服务中的PhoneWindowManager对象引用调用PhoneWindowManager对象的对应函数,最终完成事件的拦截处理。
而InputManagerService服务的filterInputEvent函数调用IInputFilter接口对象的相应函数进行事件过滤处理。
InputFilter对象由InputManagerService服务的setInputFilter函数赋值,并通过JNI打开InputDispatche对象的FilterEnabled标志,完成过滤后的事件又在InputFilterHost对象中采用InputManager对象的injectInputEvent函数一样的流程重新注入InputDispatch对象的mInboundQueue队列。
版权所有,转载时请尊重原创显要处注明链接,谢谢!