前言
这篇文章主要解决以下问题:
- 什么是Linux标准输入协议?
- Android Input System架构是怎样的?
- 如何对InputEvent协议扩展?
- Input System触发 ANR产生的原理是什么?如何避免该类型ANR?
- 如何调试Android Input System?
本文结构
1、功能介绍
2、总体设计
3、详细设计
4、开发调试
5、总结
6、资料
一、功能介绍
Linux 输入协议在 linux/input.h 内核头文件中定义了一组标准事件类型和代码,输入设备驱动程序负责通过 Linux 输入协议将设备特定信号转换为标准输入事件格式。
接下来,Android EventHub 组件通过打开与每个输入设备关联的 evdev 驱动程序从内核读取输入事件。然后,Android InputReader 组件根据设备类别解码输入事件,并生成 Android 输入事件流。在此过程中,Linux 输入协议事件代码将根据输入设备配置、键盘布局文件和各种映射表,转化为 Android 事件代码。
最后,InputReader 将输入事件发送到 InputDispatcher,后者将这些事件转发到相应的窗口。(注:这段话选自官方输入系统文档)
二、总体设计
2.1 Input System UML图
2.1.1 InputReader主要做三件事
- mEventHub获取驱动的RawEvent进行处理
- mDevice将RawEvent 加工成成熟的NotifyArgs,并添加到mQueueInputListener
- 调用mQueueInputListener.flush(),触发队列里的NotifyArgs.notify(),Dispatcher把NotifyArgs加工成EventEntry,并添加到mInboundQueue。
2.1.2 InputDispatcher主要做三件事,
- 从mInboundQueue获取EventEntry
- 通过mWindowHandlesByDisplay找到焦点窗口
- mConnectionsByFd把EventEntry转行成InputMessage并分发到InputChannel
2.1.3 ViewRootImpl主要做三件事
- 在scheduleTraversals()中mChoreographer.postCallback()一个mConsumedBatchedInputRunnable
- 等待下一帧到来时,调用mInputEventReceiver.consumeBatchedInputEvents()开始取出InputChannel内的InputMessage转化成InputEvent。
- mFirstInputStage根据不同的策略把InputEvent分发到不同字View处理。
ViewRootImpl对InputEvent分发过程如下图。
2. 2 InputEvent 处理流程图。
根据UML我们可以得出InputEvent数据加工流程图,如下图。
- 首先EventHub从驱动获取RawEvent,接着InputReader根据事件的type把RawEvent加工成Android事件NotifyArgs,比如NotifyMotionArgs或NotifyKeyArgs,然后InputDispatcher把NotifyArgs转化成EventEntry,然后根据当前焦点窗口,把EventEntry转化成InputMessage,存放到InputChannel。
- 当处于焦点窗口的应用下一帧渲染触发的时候,会从InputChannel取出InputMessage,再把InputMessage转化成InputEvent,比如MotionEvent或KeyEvent,最终发到ViewRootImpl分发到给对应的子View处理。
三、详细设计
3.1 关键类的职责
- InputManager:事件处理的核心,负责事件使用
- InputReaderThread(称为“InputReader”)读取和预处理原始输入事件,应用策略,并将消息发送到DispatcherThread管理的队列中。
- InputDispatcherThread(称为“InputDispatcher”)线程等待队列上的新事件,并异步地将它们分派给应用程序。
根据设计,InputReaderThread类和InputDispatcherThread类不共享任何内部状态。 而且,所有通信都是从InputReaderThread到InputDispatcherThread的一种方式,绝不会相反。 但是,这两个类都可以与InputDispatchPolicy交互。
InputManager类从不对Java本身进行任何调用。 相反,InputDispatchPolicy负责与系统执行所有外部交互,包括调用DVM服务。
-
EventHub: 事件的中心车站。
EventHub汇总系统上所有输入设备(包括模拟器)接收的输入事件。 此外,EventHub通过生成伪造的输入事件以指示何时添加或删除设备。
EventHub还提供输入事件流(通过getEvent方法)。
它还支持查询输入设备的当前状态,例如识别当前按下的键。 最后,EventHub还有跟踪各个输入设备的功能,例如它们的类别和它们支持的键控代码集。 -
InputReader: InputReader从EventHub读取原始事件RawEvent数据,并将其处理为InputEvent,并将其发送到InputListener。 InputReader的某些功能(例如低功耗状态下的早期事件过滤)由单独的策略对象控制。
InputReader拥有InputMappers的集合。 它所做的大部分工作都在InputReaderThread上进行,InputReader也可以接收在任意线程上运行的其他系统组件的查询。 为了使内容易于管理,InputReader使用单个Mutex来保护其状态。 互斥对象可以在调用EventHub或InputReaderPolicy时保留,但从不保留在调用InputListener时保留。 -
InputDevice: 表示单个输入设备的状态。
InputMapper :输入映射器将源数据(raw data)转成熟数据(cooked data),单个输入设备可以具有多个关联的输入映射器,以便解释事件的不同类别。 -
InputReaderThread:InputReaderThread实现了类Threads.cpp的threadLoop(),Threads内部实现了一个线程循环,会不断调用threadLoop(),threadLoop()内调用InputReader loopOnce(),loopOnce()方法先从EventHub的getEvent获取RawEvent,然后把RawEvent传递给EventDevice的process(),process()方法内调用EventDevice内所有的InputMapper,InputMapper调用process()把RawEvent加工成不同类型的Event
-
InputDispatcherThread:InputDispatcherThread循环处理入队和调度事件。
-
InputDispatcherPolicyInterface :输入调度程序策略接口。
输入阅读器策略由InputReader用来与Window Manager和其他系统组件进行交互。
通过JNI到DVM的回调部分支持了实际的实现。 单元测试中也模拟了此接口。 -
InputChannel InputChannel由一个本地unix域套接字组成,该套接字用于跨进程发送和接收输入消息。 每个通道都有一个用于调试目的的描述性名称。每个端点都有自己的InputChannel对象,用于指定其文件描述符。释放所有对输入通道的引用后,将关闭该通道。
-
InputPublisher: InputDispatcher通过InputPublisher将InputEvent发布到InputChannel。
-
InputConsumer 消费来自InputChannel的Input Event。
3.2 InputMapper如何加工Raw数据
我们以InputMapper的子类TouchInputMapper为例分析下Raw数据是如何加工成Touch数据的
首先TouchMapper调用process(),接着调用sync(),然后调用processRawTouches(), 在调用cookAndDispatch(),最后cookPointerData(),这个方法才是处理对raw数据处理成触摸数据的。这样处理完数据后,回到cookAndDispatch()方法,它接下去调用dispatchPointerUsage()分发cookEvent
dispatchMotion()发送事件.
InputDispatcher的dispatchOnce() 接着dispatchOnceInnerLocked(),然后dispatchMotionLocked(),接着dispatchEventLocked() prepareDispatchCycleLocked() ->enqueueDispatchEntriesLocked()->enqueueDispatchEntryLocked() -> traceOutboundQueueLength()
比如我们在MotionEvent收到的压感Pressure,是乘以mPressureScale才得到我们MotionEvent中获取的压感Pressure。如下图
这样当我们想在驱动层对Pressure增加协议时就需要这个知识点了,我们应用上层要先对Pressure进行还原成原始数据,最后才能获取我们协议的内容。
有了这些知识之后,如果提一个需求,底层要用倾斜角作为协议字段,上层解析协议,技术可行吗?
首先我们找到Liunx Input定义的文件
https://source.android.com/devices/input/touch-devices#orientation-and-tilt-fields
接着我们查看官方文档 Liunx Input ABS_TILT_X和ABS_TILT_Y对应着raw.tiltX和raw.tiltY
最后从Android源码中找到TouchInputMapper::cookPointerData()中raw.tiltX和raw.tiltY的部分
把raw.tiltX和raw.tiltY三角函数操作得出tilt,tilt是无法逆还原成raw.tiltX和raw.tiltY,所以我们计划倾斜角作为协议字段技术是不可行的。
3.1 InputDispatcher是如何调度数据的
分析思路:下面我们围绕MotionEvent来分析调度流程。
当InputReader处理完事件后,会调用mQueuedInputListener.flush(),flush()会把集合所有的NotifyArgs都notify() ,notify()最终调用InputDispatcher的notify(),notify()内调用mLooper.wake() 唤醒线程loop,最后触发dispatcherOnce()。
dispatcherOnce()内部实际上是调用dispatchOnceInnerLocked()来调度事件, dispatchOnceInnerLocked()主要作用是从mInboundQueue取出mPendingEvent,然后根据事件类型调度事件。dispatchOnceInnerLocked()代码如下:
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// 准备处理新的Event
if (!mPendingEvent) {
if (mInboundQueue.isEmpty())