IMS简介

Android输入系统简介

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

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

  • Linux内核,接收输入设备的中断,并将原始事件的数据写入设备节点中,设备节点,作为内核与IMS的桥梁,将原始事件的数据暴露给用户空间,以便IMS可以从中读取事件
  • InputManagerService,一个android系统服务,分为Java层和Native层两部分,java层负责与WMS通信,Native层则是InputReader和InputDispatcher两个输入系统关键组件的运行容器
  • EventHub,直接访问所有的设备节点,通过getEvents()函数将所有输入系统相关的待处理的底层事件返回给使用者,包括原始输入事件,设备节点的增删等
  • InputReader,是IMS中的关键组件之一,它运行一个独立的线程InputReaderThread中,负责管理输入设备的列表和配置,以及进行输入事件的加工处理,它通过其线程循环不断地通过getEvents()函数从EventHub中将事件取出并进行处理,对于设备节点的增删事件,它会更新输入设备列表与配置,对于原始输入事件,InputReader对其进行翻译,组装,封装为包含更多信息,更多可读性的输入事件,然后交给InputDispatcher进行派发
  • InputReaderPolicy,为InputReader的事件加工处理提供一些策略配置
  • InputDispatcher,是IMS中的另一个关键组件,运行于一个独立的线程InputDispatcherThread中, 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内部做了很多的抽象工作,EventHub、InputReader以及InputDispatcher等实际上都是继承自相应的名为XXXInterface接口,并且仅通过接口进行相互之间的引用。下图左侧部分为Reader子系统作为第一台水泵,右侧为Dispatcher子系统,作为第二台水泵。

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)和加工处理(InputReader)两个部分。

InputDispatcher通用事件派发流程

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

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

InputChannel的工作原理

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

Connection工作原理

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

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

窗口端的连接

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

猜你喜欢

转载自blog.csdn.net/xiaowang_lj/article/details/128392292
今日推荐