android的输入管理系统主要完成按键、触摸板、鼠标等输入设备的事件输入,功能包括,输入设备的事件输入及向焦点窗口和焦点视图的事件派发,事件的插入,事件的过滤,事件的拦截等功能。

整个输入系统包括服务端和客户端两部分,服务端部分主要完成输入设备事件的读取、事件的映射、事件的插入、事件的过滤、事件的拦截等功能;客户端部分主要完成事件向焦点窗口和焦点视图的派发。

输入系统的整个架构采用的是管道过滤器模式(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队列。

版权所有,转载时请尊重原创显要处注明链接,谢谢!




androd输入管理系统机制解析的更多相关文章

  1. 钟表维修管理系统技术解析(一) MVC架构搭建

    钟表维修管理系统技术解析(一)  MVC架构搭建 1.1新建项目 第一步:打开VS2010界面,点击左上角文件,点击新建,选择项目 1.1(图1) 第二步:点击网站Web类型,选择ASP.net MV ...

  2. 笔记:XML-解析文档-流机制解析器(SAX、StAX)

    DOM 解析器完整的读入XML文档,然后将其转换成一个树型的数据结构,对于大多数应用,DOM 都运行很好,但是,如果文档很大,并且处理算法又非常简单,可以在运行时解析节点,而不必看到完整的树形结构,那 ...

  3. 会员卡管理系统技术解析(十八)Timer定时监听

    会员卡管理系统技术解析(十八)Timer定时监听 在web应用中,有时候客户须要一些定时程序.不须要客户自己去操作.而是由应用程序自行触发(代理)运行某些操作. 这个时候监听与定时器的配合使用就基本能 ...

  4. 转 Java Classloader机制解析

    转 Java Classloader机制解析 发表于11个月前(2014-05-09 11:36)   阅读(693) | 评论(0) 9人收藏此文章, 我要收藏 赞1 慕课网,程序员升职加薪神器,点 ...

  5. Java并发编程:Concurrent锁机制解析

    Java并发编程:Concurrent锁机制解析 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: # ...

  6. 时序数据库连载系列: 时序数据库一哥InfluxDB之存储机制解析

    InfluxDB 的存储机制解析 本文介绍了InfluxDB对于时序数据的存储/索引的设计.由于InfluxDB的集群版已在0.12版就不再开源,因此如无特殊说明,本文的介绍对象都是指 InfluxD ...

  7. CDN 的缓存与回源机制解析

    CDN的缓存与回源机制解析 CDN (Content Delivery Network,即内容分发网络)指的是一组分布在各个地区的服务器.这些服务器存储着数据的副本,因此服务器可以根据哪些服务器与用户 ...

  8. 16.Spark Streaming源码解读之数据清理机制解析

    原创文章,转载请注明:转载自 听风居士博客(http://www.cnblogs.com/zhouyf/) 本期内容: 一.Spark Streaming 数据清理总览 二.Spark Streami ...

  9. 【转载】linux2.6内核initrd机制解析

    题记 很久之前就分析过这部分内容,但是那个时候不够深入,姑且知道这么个东西存在,到底怎么用,来龙去脉咋回事就不知道了.前段时间工作上遇到了一个initrd的问题,没办法只能再去研究研究,还好,有点眉目 ...

随机推荐

  1. 获取客户信息SQL

    /*取客户信息SQL*/ --客户信息 SELECT hou.name 业务实体, hca.account_number 客户编号, hp.party_name 客户名称, arp_addr_pkg. ...

  2. FORM的静态提交

     在form中进行保存时,如果使用commit_form的话会弹出信息提示"没有修改需要保存"或者"几条记录已保存"类似的字样,有时候不想被提示,可以使用A ...

  3. Swift变量名的一种玩法

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 是的,Swift的变量名可以用任何合法的Unicode字符,这 ...

  4. 第一行代码阅读笔记---AndroidMainfest.xml分析

    按照这本书的指引,我随作者一样创建了一个安卓应用,开始了安卓开发的启程. 找到AndroidMainfest.xml这个文件,打开后看到了我创建的Activity在这个文件里被成功注册,文件内容如下: ...

  5. Android Multimedia框架总结(一)MediaPlayer介绍之状态图及生命周期

    请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52349221 前言:从本篇开始,将进入Multimedia框架,包含 ...

  6. 插件占坑,四大组件动态注册前奏(二) 系统Service的启动流程

    转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52203903 前言:为什么要了解系统Activity,Service,BroadCas ...

  7. shell入门之流程控制语句

    1.case 脚本: #!/bin/bash #a test about case case $1 in "lenve") echo "input lenve" ...

  8. Cocos2D iOS之旅:如何写一个敲地鼠游戏(八):为动画建立属性列表

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  9. 输入过滤器——InputFilter

    一般情况下我们通过请求体读取器InputStreamInputBuffer获取的仅仅是源数据,即未经过任何处理发送方发来的字节.但有些时候在这个读取的过程中希望做一些额外的处理,并且这些额外处理可能是 ...

  10. UE4利用Save Game创建全局变量

    因为盲目的做了一个UE4的项目,没有用到UE4的无缝加载,我只能在一个个关卡中手动切换,然后每次的数据都会重置,这对于项目来说,造成了体验感的极度下降. 然而我查了一下怎样在UE4中创建全局变量,找到 ...