一 概述
本文是 Systrace 系列文章的第六篇,主要是对 Systrace 中的 Input 进行简单介绍,介绍其 Input 的流程; Systrace 中 Input 信息的体现 ,以及如何结合 Input 信息,分析与 Input 相关的问题。
在 Android 基于 Choreographer 的渲染机制详解 这篇文章中,我们知道,Android App 的主线程运行的本质是靠 Message 驱动的,这个 Message 可以是循环动画、可以是定时任务、可以是其他线程唤醒,不过我们最常见的还是 Input Message ,这里的 Input 是以 InputReader 里的分类,不仅包含触摸事件(Down、Up、Move) , 可包含 Key 事件(Home Key 、 Back Key) 。这里我们着重讲的是触摸事件。
由于 Android 系统在 Input 链上加了一些 Trace 点,且这些 Trace 点也比较完善,部分厂家可能会自己加一些,不过我们这里以标准的 Trace 点来讲解,这样不至于你换了个手机抓的 Trace 就不一样了
Input 在 Android 中的地位是很高的,我们在玩手机的时候,大部分应用的滑动、跳转这些都依靠 Input 事件来驱动,后续我会专门写一篇文章,来介绍 Android 中基于 Input 的运行机制。这里是从 Systrace 的角度来看 Input 。看下面的流程之前,脑子里先有个关于 Input 的大概处理流程,这样看的时候,就可以代入:
- 触摸屏每隔几毫秒扫描一次,如果有触摸事件,那么把事件上报到对应的驱动
- InputReader 读取触摸事件交给 InputDispatcher 进行事件派发
- InputDispatcher 将触摸事件发给注册了 Input 事件的 App
- App 拿到事件之后,进行 Input 事件分发,如果此事件分发的过程中,App 的 UI 发生了变化,那么会请求 Vsync,则进行一帧的绘制
另外在看 Systrace 的时候,要牢记 Systrace 中时间是从左到右流逝的,也就是说如果你在 Systrace 上画一条竖直线,那么竖直线左边的事件永远比右边的事件先发生,这也是我们分析源码流程的一个基石。我希望大家在看基于 Systrace 的源码流程分析之后,脑子里有一个图形化的、立体的流程图,你跟的代码走到哪一步了在图形你在脑中可以快速定位出来。
二 Input in Systrace
下面这张图是一个概览图,以滑动桌面为例 (滑动桌面包括一个 Input_Down 事件 + 若干个 Input_Move 事件 + 一个 Input_Up 事件,这些事件和事件流都会在 Systrace 上有所体现,这也是我们分析 Systrace 的一个重要的切入点),主要牵扯到的模块是 SystemServer 和 App 模块,其中用蓝色标识的是事件的流动信息,红色的是辅助信息。
InputReader 和 InputDispatcher 是跑在 SystemServer 里面的两个 Native 线程,负责读取和分发 Input 事件,我们分析 Systrace 的 Input 事件流,首先是找到这里。下面针对上图中标号进行简单说明:
- InputReader 负责从 EventHub 里面把 Input 事件读取出来,然后交给 InputDispatcher 进行事件分发
- InputDispatcher 在拿到 InputReader 获取的事件之后,对事件进行包装和分发
- OutboundQueue 里面放的是即将要被派发给对应 AppConnection 的事件
- WaitQueue 里面记录的是已经派发给 AppConnection 但是 App 还在处理没有返回处理成功的事件
- PendingInputEventQueue 里面记录的是 App 需要处理的 Input 事件,这里可以看到已经到了应用进程
- deliverInputEvent 标识 App UI Thread 被 Input 事件唤醒
- InputResponse 标识 Input 事件区域,这里可以看到一个 Input_Down 事件 + 若干个 Input_Move 事件 + 一个 Input_Up 事件的处理阶段都被算到了这里
- App 响应 Input 事件 : 这里是滑动然后松手,也就是我们熟悉的桌面滑动的操作,桌面随着手指的滑动更新画面,松手后触发 Fling 继续滑动,从 Systrace 就可以看到整个事件的流程
下面以第一个 Input_Down 事件的处理流程来进行详细的工作流说明,其他的 Move 事件和 Up 事件的处理是一样的(部分不一样,不过影响不大)
2.1 InputDown 事件在 SystemServer 的工作流
放大 SystemServer 的部分,可以看到其工作流(蓝色),滑动桌面包括 Input_Down + 若干个 Input_Move + Input_Up ,我们这里看的是 Input_Down 这个事件:
2.2 InputDown 事件在 App 的工作流
应用在收到 Input 事件后,有时候会马上去处理 (没有 Vsync 的情况下),有时候会等 Vsync 信号来了之后去处理,这里 Input_Down 事件就是直接去唤醒主线程做处理,其 Systrace 比较简单,最上面有个 Input 事件队列,主线程则是简单的处理。
2.2.1 App 的 Pending 队列
2.2.2 主线程处理 Input 事件
主线程处理 Input 事件这个大家比较熟悉,从下面的调用栈可以看到,Input 事件传到了 ViewRootImpl,最终到了 DecorView ,然后就是大家熟悉的 Input 事件分发机制。
三 关键知识点和流程
从上面的 Systrace 来看,Input 事件的基本流向如下:
- InputReader 读取 Input 事件
- InputReader 将读取的 Input 事件放到 InboundQueue 中
- InputDispatcher 从 InboundQueue 中取出 Input 事件派发到各个 App(连接) 的 OutBoundQueue
- 同时将事件记录到各个 App(连接) 的 WaitQueue
- App 接收到 Input 事件,同时记录到 PendingInputEventQueue ,然后对事件进行分发处理
- App 处理完成后,回调 InputManagerService 将负责监听的 WaitQueue 中对应的 Input 移除
通过上面的流程,一次 Input 事件就被消耗掉了(当然这只是正常情况,还有很多异常情况、细节处理,这里就不细说了,自己看相关流程的时候可以深挖一下) , 那么本节就从上面的关键流中取几个重要的知识点讲解。
3.1 InputReader
InputReader 是一个 Native 线程,跑在 SystemServer 进程里面,其核心功能是从 EventHub 读取事件、进行加工、将加工好的事件发送到 InputDispatcher
InputReader Loop 流程如下
- getEvents:通过 EventHub (监听目录 /dev/input )读取事件放入 mEventBuffer ,而mEventBuffer 是一个大小为256的数组, 再将事件 input_event 转换为 RawEvent
- processEventsLocked: 对事件进行加工, 转换 RawEvent -> NotifyKeyArgs(NotifyArgs)
- QueuedListener->flush:将事件发送到 InputDispatcher 线程, 转换 NotifyKeyArgs -> KeyEntry(EventEntry)
核心代码 loopOnce 处理流程如下:
InputReader 核心 Loop 函数 loopOnce 逻辑如下:
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> inputDevices;
{
// acquire lock
......
//获取输入事件、设备增删事件,count 为事件数量
size_t count = mEventHub ->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{
......
if (count) {
//处理事件
processEventsLocked(mEventBuffer, count);
}
}
......
mQueuedListener->flush();//将事件传到 InputDispatcher,
// 这里getListener 得到的就是 InputDispatcher
}
3.2 InputDispatcher
上面的 InputReader 调用 mQueuedListener->flush 之后 ,将 Input 事件加入到InputDispatcher 的 mInboundQueue ,然后唤醒 InputDispatcher , 从 Systrace 的唤醒信息那里也可以看到 InputDispatch 线程是被 InputReader 唤醒的.
InputDispatcher 的核心逻辑如下:
- dispatchOnceInnerLocked(): 从 InputDispatcher 的 mInboundQueue 队列,取出事件 EventEntry。另外该方法开始执行的时间点 (currentTime) 便是后续事件 dispatchEntry 的分发时间 (deliveryTime)
- dispatchKeyLocked():满足一定条件时会添加命令 doInterceptKeyBeforeDispatchingLockedInterruptible
- enqueueDispatchEntryLocked():生成事件 DispatchEntry 并加入 connection 的 outbound 队列
- startDispatchCycleLocked():从 outboundQueue 中取出事件 DispatchEntry, 重新放入 connection 的 waitQueue 队列
- InputChannel.sendMessage 通过 socket 方式将消息发送给远程进程
- runCommandsLockedInterruptible():通过循环遍历的方式,依次处理 mCommandQueue 队列中的所有命令。而 mCommandQueue 队列中的命令是通过 postCommandLocked() 方式向该队列添加的
其核心处理逻辑在 dispatchOnceInnerLocked 这里
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// Ready to start a new event.
// If we don't already have a pending event, go grab one.
if (! mPendingEvent) {
if (mInboundQueue.isEmpty()) {
} else {
// Inbound queue has at least one entry.
mPendingEvent = mInboundQueue.dequeueAtHead();
traceInboundQueueLengthLocked();
}
// Poke user activity for this event.
if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
pokeUserActivityLocked(mPendingEvent);
}
// Get ready to dispatch the event.
resetANRTimeoutsLocked();
}
case EventEntry::TYPE_MOTION: {
done = dispatchMotionLocked(currentTime, typedEntry,
&dropReason, nextWakeupTime);
break;
}
if (done) {
if (dropReason != DROP_REASON_NOT_DROPPED) {
dropInboundEventLocked(mPendingEvent, dropReason);
}
mLastDropReason = dropReason;
releasePendingEventLocked();