Android输入系统-IMS

前言

上次系统学习了下WMS计算位置和大小的流程,在调整系统window窗口位置后,输入系统和UI显示对不上,最近有时间,再把Android输入系统完整分析下,如果只是解决一个问题,不用这么费劲,但是完整流程学习一遍,还是收益很多,特别是对事件处理的设计模式和多个模块协调完美配合。
学习Andrid输入系统,网上资料也基本都是参考《深入理解Android 卷III》,它写的特别好,代码/原理都写的很清楚,自己花了三天时间完整把这一章节看完,本书把输入系统核心写的很清楚,其他一些输入模块,比如鼠标绘制,View里面事件派发,Policy策略处理,以及更底层kernel输入框架(扯的有点远)等 并没有写,后面有机会补充这些子模块的分析。
看完总想留下点什么,就把部分流程图自己画下,算是学习笔记了,如果想学习Android输入系统,强烈推荐看下这本书。网上都是部分节选,完整版下载电子或者购买吧。
深入理解Android 卷III
《深入理解Android 卷III》第五章 深入理解Android输入系统

Android输入系统简介

对应用层而言,输入事件的源头是位于/dev/input下的设备节点,而输入系统的终点是WMS管理的某个窗口。最初的输入事件位内核生成的原始事件,而最终交付给窗口的则是KeyEvent或MotionEvent对象。因此Android输入系统的主要工作是读取设备节点中的原始事件,将其加工封装,然后派发给一个特点的窗口以及窗口中的控件。这个过程由InputManagerService(以下简称IMS)系统服务为核心的多个参与者共同完成。


Android输入系统的总体流程.png

输入系统中最基本的参与者和模块

  • Linux内核,接受输入设备的中断,并将原始事件的数据写入设备节点中
    设备接电,作为内核与IMS的桥梁,将原始事件的数据暴露给用户空间,以便IMS可以从中读取事件
  • InputManagerService,一个android系统服务,分为Java层和Native层两部分,java层负责与WMS通信,而Native层则是InputReader和InputDispatcher两个输入系统关键组件的运行容器
  • EventHub,直接访问所有的设备节点,通过一个名为getEvents()的函数将所有输入系统相关的待处理的底层事件返回给使用者,包括原始输入事件,设备节点的增删等
  • InputReader,是IMS中的关键组件之一,它运行一个独立的线程中,负责管理输入设备的列表和配置,以及进行输入事件的加工处理,它通过其线程循环不断地通过getEvents()函数从EventHub中将事件取出并进行处理,对于设备节点的增删事件,它会更新输入设备列表与配置,对于原始输入事件,InputReader对其进行翻译,组装,封装为包含更多信息,更多可读性的输入事件,然后交给InputDispatcher进行派发
  • InputReaderPolicy,为InputReader的事件加工处理提供一些策略配置
  • InputDispatcher,是IMS中的另一个关键组件,运行于一个独立的线程中, InputDispatcher中保管来自WMS的所有窗口的信息,收到InputReader的输入事件后,会在其保管的窗口中寻找合适的窗口,并将事件派发给此窗口
  • InputDispatcherPolicy,为InputDispatcher的派发过程提供策略控制,例如HOME键被InputDispatcherPolicy截取到PhoneWindowManager中处理,并阻止窗口收到HOME键按下的事件
  • WMS,并不是输入系统的一员,新建窗口时,WMS为新窗口和IMS创建了事件传递所用的通道,会将窗口的可点击区域,焦点窗口等信息实时更新到IMS的InputDispatcher中,使得InputDispatcher可以正确将事件派发到指定窗口
    ViewRootImpl,对某些窗口,如壁纸窗口,SurfaceView的窗口来说,窗口就是输入事件派发的终点,而对其他的如Activity,对话框等使用了Android控件系统的窗口来说,输入事件的终点是控件

IMS体系结构

IMS服务的大部分工作是在Native层完成的,Java层基本是Native层的封装,并负责和WMS服务对接。
看下面框架图,IMS的启动主要是创建InputReader线程和InputDispather线程,后面主要的读取输入事件和分发事件都是在这两个线程中完成的。


IMS框架图

三个线程、三台水泵

InputReader在其线程循环中不断地从EventHub中抽取原始输入事件,进行加工处理后将加工所得的事件放入InputDispatcher的派发队列中。InputDispatcher则在其线程循环中将派发队列的事件取出,查找合适的窗口,将事件写入创建的事件接受管道中。窗口事件接受线程的Looper从管道中将事件取出,交由事件处理函数进行事件的响应。整个过程共有三个线程收尾相连,像三台水泵似的一层一层将事件交付给事件处理函数。


IMS三个线程,三台水泵

IMS成员关系

IMS内部做了很多的抽象工作,EventHub、InputReader以及InputDispatcher等实际上都是继承自相应的名为XXXInterface接口,并且仅通过接口进行相互之间的引用。下图左侧部分为Reader子系统作为第一台水泵,右侧为Dispatcher子系统,作为第二台水泵。


IMS成员关系

EventHub事件生成流程

EventHub的直译是事件的集线器,顾名思义,它将所有的输入事件通过一个接口getEvents()把从多个输入设备节点中读取的事件交给InputReader,它是输入系统最底层的一个组件。


EventHub的事件生成流程

InputMapper分配

Device结构体的事件位掩码描述了4种类型的输入事件:

  • EV_KEY, 按键类型的事件。能够上报这类事件的设备有键盘,鼠标,手柄,手写板等一切拥有按钮的设备(也包括手机上实体按键)。在Device结构体中,对应的事件位掩码keyBitMask描述了设备可以产生的按键事件的集合。按键事件的全集包括字符按键,方向按键,控制键,鼠标键,游戏按键等。
  • EV_ABS, 绝对坐标类型的事件。这类事件描述了再空间中的一个点,接触板,触摸屏等使用绝对坐标的输入设备可以上报这类事件。事件位掩码absBitmask描述了设备可以上报的事件的维度信息(ABS_X,ABS_Y,ABS_Z),以及是否支持多点事件。
  • EV_REL, 相对坐标类型事件。这类事件描述了事件在空间中相对于上次事件的偏移量。鼠标,轨迹球等基于游标指针的设备可以上报此类事件。事件位掩码relBitmask描述了设备可以上报事件的维度信息(REL_X,REL_Y,REL_Z)。
  • EV_SW,开关类型的事件。这类事件描述了若干固定状态直接的切换。手机上的静音模式开关按钮、模式切换键盘灯设备可以上报此类事件。事件位掩码swBitmask表示了设备可以切换的状态列表。
    在EventHub的openDeviceLocked()接口通过事件位掩码确定了设备可以上报的事件类型后,便可以据此确定设备的类型了。Android在EventHub.h中定义了12中设备类型,为了叙述简介,我们省略了INPUT_DEVICE_CLASS_前缀:
  • KEYBOARD, 可以上报鼠标按键以外的EV_KEY类型事件的设备都属于此类,如键盘,机身按钮(音量键,电源键等)。
  • ALPHAKEY, 可以上报字符按键的设备,例如键盘,此类型的设备必定属于KEYBOARD。
  • DPAD, 可以上报方向键的设备。例如键盘,手机导航键等。这类设备也同属于KEYBOARD。
  • GAMEPAD, 可以上报游戏按键的设备,如游戏手柄。这类设备同时也属于KEYBOARD。
  • TOUCH, 可以上报EV_ABS类型事件的设备都属于此类,如触摸屏和触控板。
  • TOUCH_MT, 可以上报EV_ABS类型事件,并且其事件位掩码指示其支持多点事件的设备属于此类。例如多点触摸屏。这类设备同时也属于TOUCH类型。
  • CURSOR, 可以上报EV_ABS类型事件,并且可以上报BTN_MOUSE子类EV_KEY事件的设备属于此类,例如鼠标和轨迹球。
  • SWITCH, 可以上报EV_SW类型事件的设备。
  • JOYSTICK, 属于GAMEPAD类型,并且属于TOUCH类型的设备。
  • VIBRATOR, 支持力反馈的设备。
  • VIRTUAL, 虚拟设备。
  • EXTERNAL, 外部设备,即非内建设备。例如外接鼠标,键盘,游戏手柄等。
    确定设备类型之后,InputReader的createDeviceLocked()接口为InputDevice分配InputMapper。除了VIRTUAL和EXTERNAL没有对应的InputMapper,以及KEYBOARD,ALPHAKEY,DPAD与GAMEPAD 4者公用KeyboardInputMapper以外,InputReader为每种设备类型定义了对应的InputMapper.


    InputMapper分配

    一个设备节点可能托管很多种类型的物理输入设备。例如Android模拟器中只有event0一个设备节点,但是其负责按键,触摸屏事件的上班工作。此时,这个设备节点对应的InputDevice将会拥有KeyboardInputMapper与SingleTouchInputMapper两个InputMapper。不过得益于InputMapper的职责链的设计模式,一个InputDevicece处理多种输入事件一点也不吃力。

InputReader原始事件的读取与加工过程

Reader子系统分为读取(EventHub)和价格处理(ImputReader)两个部分。


InputReader原始事件加工

InputDispatcher通用事件派发流程

按键事件往往携带系统功能,例如HOME键,POWER键以及Volume键等,InputDispatcher需要对按键事件做更多附件工作,很多工作都是在Policy里面做的,先说明下通用事件派发流程总结。

  • 在事件进入派发队列之前的处理位于InputReader线程中。而其他操作则位于派发线程找那个。
  • 事件以EventEntry子类的形式存在于InputDispatcher中。
  • 事件派发是串行的,在队首的事件派发完成之前,不好进行其他事件的派发。
  • 在选择InputTarget的过程中,如果发现有一个目标窗口尚未准备好接受事件,则暂停当前事件的派发,并通过设置nextWakeupTime在下次派发循环时再次派发。


    Dispatcher通用事件派发流程

InputChannel对工作原理

InputChannel本质是一对SocketPair(非网络套接字)。SocketPair用来使用本机进程间的通信。一对SocketPair通过socketpair()接口创建。使用者可以因此而得到两个相互连接的文件描述符。这两个描述符可以通过套接字接口send()和recv()进行写入和读取,并且向其中一个文件描述符中写入的数据,可以从另一个描述符中读取。同pipe()所创建的管道不同,SocketPair的两个文件描述符是双向通信的,因此非常适合进程间的交付通信。
InputChannel就是SocketPair描述符及其操作的封装,而且是成对使用的。匹配的两个InputChannel分别保有一个SocketPair的描述符,并分别分配给InputDispatcher与窗口。因此InputDispatcher向保有的InputChannel中写入输入事件,可以由窗口从自己的InputChannel中读取。并且窗口可以将事件处理完毕的反馈写入到InputChannel中,InputDispatcher再将反馈进行读取。


InputChannel对工作原理

Connection工作原理

在InputDispatcher中,InputChannel被封装成一个Connection对象。Connection描述了从InputDispatcher到目标窗口中的一个链接,其中保存了向窗口发送的事件状态信息。在Connection中,重要的成员有:

  • minputPublisher, InputPublisher类的一个对象,它封装InputChannel并直接对其写入和读取。另外,它也负责InputMessage结构体的封装和解析。
  • outboundQueue, 用于保存等待通过此Connection进行发送的事件队列。
  • waitQueue, 用于保存已经通过此Connection将事件发送给窗口,正在等待窗口反馈的事件队列。


    Connection工作原理

窗口端的连接

当窗口端通过addWindow()接口获取InputChannel后,便会使用它创建一个InputEventReceiver对象,InputEventReceiver对象可以接收来之InputChannel的输入事件,并触发其onInputEvent()回调。


窗口端的连接

总结

以上只是根据《深入理解Android 卷III》学习记录,很多细节没有整理出来,有兴趣学习的建议翻阅书本。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容