Android 10.0 Input System 源码分析

前言

这篇文章主要解决以下问题:

  1. 什么是Linux标准输入协议?
  2. Android Input System架构是怎样的?
  3. 如何对InputEvent协议扩展?
  4. Input System触发 ANR产生的原理是什么?如何避免该类型ANR?
  5. 如何调试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图

Android Input System UML批注.png

2.1.1 InputReader主要做三件事
  1. mEventHub获取驱动的RawEvent进行处理
  2. mDevice将RawEvent 加工成成熟的NotifyArgs,并添加到mQueueInputListener
  3. 调用mQueueInputListener.flush(),触发队列里的NotifyArgs.notify(),Dispatcher把NotifyArgs加工成EventEntry,并添加到mInboundQueue。
2.1.2 InputDispatcher主要做三件事,
  1. 从mInboundQueue获取EventEntry
  2. 通过mWindowHandlesByDisplay找到焦点窗口
  3. mConnectionsByFd把EventEntry转行成InputMessage并分发到InputChannel
2.1.3 ViewRootImpl主要做三件事
  1. 在scheduleTraversals()中mChoreographer.postCallback()一个mConsumedBatchedInputRunnable
  2. 等待下一帧到来时,调用mInputEventReceiver.consumeBatchedInputEvents()开始取出InputChannel内的InputMessage转化成InputEvent。
  3. mFirstInputStage根据不同的策略把InputEvent分发到不同字View处理。
    ViewRootImpl对InputEvent分发过程如下图。
    ViewRootImpl对InputEvent分发过程.png

2. 2 InputEvent 处理流程图。

根据UML我们可以得出InputEvent数据加工流程图,如下图。
image.png

  1. 首先EventHub从驱动获取RawEvent,接着InputReader根据事件的type把RawEvent加工成Android事件NotifyArgs,比如NotifyMotionArgs或NotifyKeyArgs,然后InputDispatcher把NotifyArgs转化成EventEntry,然后根据当前焦点窗口,把EventEntry转化成InputMessage,存放到InputChannel。
  2. 当处于焦点窗口的应用下一帧渲染触发的时候,会从InputChannel取出InputMessage,再把InputMessage转化成InputEvent,比如MotionEvent或KeyEvent,最终发到ViewRootImpl分发到给对应的子View处理。

三、详细设计

3.1 关键类的职责

  • InputManager:事件处理的核心,负责事件使用
  1. InputReaderThread(称为“InputReader”)读取和预处理原始输入事件,应用策略,并将消息发送到DispatcherThread管理的队列中。
  2. 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。如下图
image.png
image.png

这样当我们想在驱动层对Pressure增加协议时就需要这个知识点了,我们应用上层要先对Pressure进行还原成原始数据,最后才能获取我们协议的内容。

有了这些知识之后,如果提一个需求,底层要用倾斜角作为协议字段,上层解析协议,技术可行吗?
首先我们找到Liunx Input定义的文件
https://source.android.com/devices/input/touch-devices#orientation-and-tilt-fields
image.png

接着我们查看官方文档 Liunx Input ABS_TILT_X和ABS_TILT_Y对应着raw.tiltX和raw.tiltY
最后从Android源码中找到TouchInputMapper::cookPointerData()中raw.tiltX和raw.tiltY的部分
image.png
把raw.tiltX和raw.tiltY三角函数操作得出tilt,tilt是无法逆还原成raw.tiltX和raw.tiltY,所以我们计划倾斜角作为协议字段技术是不可行的。
image.png

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()) 
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值