[ANR] Input ANR是怎么产生的
最近在做ANR优化,发现线上非常多的ANR(一半以上)原因都是
Input dispatching timed out
。对于Activity
或Service
生命周期的ANR产生原理,我想大家应该都比较了解了,就是在AMS
里埋炸弹、拆炸弹那一套机制,那Input Dispatching time out
ANR是怎么产生的呢?这篇文章带大家一起学习一下。
Android输入系统
Input Dispatching time out
ANR是有Android点击事件超时所产生的,所以要了解它产生的原理,就要从Android的输入系统开始讲起。
Android输入系统,主要包含以下几个模块:
发送端:运行在system_server进程,主要运行在
InputReaderThread
和InputDispatcherThread
。
-
InputReader
:这个模块主要负责从硬件获取输入,转换成事件Event
,传给InputDispatcher
。 -
InputDispatcher
:将InputReader
传递过来的事件分发给相应的窗口,并且监控ANR。
接收端:运行在应用程序进程,运行在UI线程。
-
InputEventReceiver
:在App端接收按键,并进行分发。 -
View
和Activity
:接收按键并进行处理。
基础服务:
-
InputManagerService
:负责InputReader
和InputDispatcher
的创建。 -
WindowManagerService
:管理InputManager
与Window
及AMS
之间的通信。
通信机制:
-
socket
:发送端和接收端跨进程,采用的是socket的通信机制。
Android输入系统的原理比较复杂,这篇文章,我们着重分析ANR发生的原理,所以我们只看InputDispatcher
即可,因为关于ANR的判定是在这里发生的。
后续学姐会再出专题,详细分析整个Android输入系统的原理,感兴趣的可以点个关注❤️。
ANR原理分析
我们先来思考一个问题,如果我在Activity
的dispatchTouchEvent
中,手动让线程sleep一段时间。这种情况一定会报ANR么?
var count = 0;
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
// 让第0个event,卡住9s
if(count == 0){
try {
Thread.sleep(9000)
} catch (e: Throwable) {
e.printStackTrace()
}
}
count++
return super.dispatchTouchEvent(ev)
}
我相信很多同学会回答一定,因为主线程 sleep 的时间远远超过了 Input 事件报ANR的超时时间,所以会报ANR。
但真实的情况是,在主线程sleep 大于 5s 不一定会报ANR。下面我们就从InputDispatcher
源码的角度来看看,Input ANR到底是怎么产生的吧。
InputDispatcher启动
InputDispatcher
运行在InputDispatcherThread
线程中,这个线程和应用UI线程一样,也是靠Looper
机制运行起来的。
首先看下InputDispatcher
对象的初始化过程:
InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
//创建Looper对象
mLooper = new Looper(false);
//获取分发超时参数
policy->getDispatcherConfiguration(&mConfig);
}
主要就是创建了一个属于自己线程的Looper
对象。当这个线程的Looper
被启动之后,会不断重复调threadLoop
方法,直到该方法返回false,退出循环,从而结束线程。
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
threadLoop
里面,只做了一件事情,就是调用InputDispatcher的dispatchOnce
方法:
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
// 如果有commands需要处理,优先处理commands
if (!haveCommandsLocked()) {
//当commands处理完后,处理events
dispatchOnceInnerLocked(&nextWakeupTime);
}
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis); //进入epoll_wait
}
InputDispatcher
处理的事件主要分为两种:一种是command
,一种是input event
。command
主要是mPolicy
处理的事务,和我们点击事件的ANR没有关系,所以这里不详细分析。input event
的处理逻辑主要是,找到对应的window
对象,通过socket
将event
发送给应用进程。
当处理完所有command
和input event
之后,会调用Looper
的pollOnce
方法。从之前的epoll
机制分析文章中,我们知道,在这个方法里,线程会进入epoll_wait等待。
唤醒epoll_wait
的方法有:
- 监听的
fd
有相关数据变化 - timeout:到达
timeoutMillis
的时间 - wake:主动调
Looper
的wake()
方法。
这里会监听和应用程序通信的socket fd
,接收应用程序处理完事件的消息。
ps:关于epoll机制的原理,可参考:从epoll机制看MessageQueue
分发事件
在InputDispatcher
线程中,主要包含三个事件队列:
-
mInBoundQueue
:InputReader线程负责通过EventHub读取输入事件,一旦监听到输入事件就放入这个队列。 -
outBoundQueue
:记录即将分发给目标应用窗口的输入事件。 -
waitQueue
:记录已分发给目标应用,且应用尚未处理完成的输入事件。
还有一个单独的变量:
-
mPendingEvent
:记录当前正在发送中的event,发送成功后会清空
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// 如果没有正在发送中的event,才去取新的event
if (!mPendingEvent) {
// mInboundQueue为待处理的事件队列
if (mInboundQueue.isEmpty()) {
if (!mPendingEvent) {
return; //没有事件需要处理,则直接返回
}
} else {
//从mInboundQueue取出头部的事件
mPendingEvent = mInboundQueue.dequeueAtHead();
}
// 新的分发开始了,重置ANR超时时间
resetANRTimeoutsLocked();
}
switch (mPendingEvent->type) {
// 尝试分发按键事件
done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
break;
}
}
//分发操作完成,则进入该分支
if (done) {
// 释放pendingEvent事件,将这个标志位设置为空
releasePendingEventLocked();
*nextWakeupTime = LONG_LONG_MIN; //强制立刻执行轮询
}
}
这个方法主要的逻辑是:
- 如果有正在发送的event(
pendingEvent
),则什么都不做,如果没有,则取mInboundQueue
头部的事件,用于发送。 - 调用
dispatchKeyLocked
方法发送事件。 - 当发送成功后,释放
pendingEvent
标志位。
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
Vector<InputTarget> inputTargets;
// 寻找焦点
int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
return false; //直接返回
}
//只有injectionResult是成功,才有机会执行分发事件
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
执行到这一步的事件,不一定可以走到发送的逻辑。因为还需要寻找可执行的焦点,只有当找到了可执行焦点后,事件才会被真正分发。
int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
//检测窗口是否为更多的输入操作而准备就绪
reason = checkWindowReadyForMoreInputLocked(currentTime,
mFocusedWindowHandle, entry, "focused");
if (!reason.isEmpty()) {
// 如果窗口没有就绪,判断是否发生了ANR
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.string());
}
}
这个方法会先检测窗口是否就绪,如果未就绪,会判断是否超过5s,即判断是否发生ANR。
String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
const char* targetType) {
// 处理一些窗口暂停、窗口连接已死亡、窗口连接已满的问题
if (eventEntry->type == EventEntry::TYPE_KEY) {
// 按键事件,输出队列或事件等待队列不为空
if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
return String8::format();
}
} else {
// 非按键事件,事件等待队列不为空且头事件分发超时500ms
if (!connection->waitQueue.isEmpty()
&& currentTime >= connection->waitQueue.head->deliveryTime
+ STREAM_AHEAD_EVENT_TIMEOUT) {
return String8::format();
}
}
return String8::empty();
}
到这里就很清楚了,如果outboundQueue
不为空,或waitQueue
不为空,此时表示Window
不Ready
。则新的事件无法走到正常分发的逻辑。
- Window不ready的逻辑
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime){
// 如果失败的原因是因为上一个任务未处理完,则不需要给超时时间重新赋值
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
// 设置InputTargetWaitCause
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
//这里的currentTime是指执行dispatchOnceInnerLocked方法体的起点
mInputTargetWaitStartTime = currentTime;
// timeout为5s
mInputTargetWaitTimeoutTime = currentTime + timeout;
mInputTargetWaitTimeoutExpired = false;
mInputTargetWaitApplicationHandle.clear();
}
//当超时5s,则进入ANR流程
if (currentTime >= mInputTargetWaitTimeoutTime) {
onANRLocked(currentTime, applicationHandle, windowHandle,
entry->eventTime, mInputTargetWaitStartTime, reason);
*nextWakeupTime = LONG_LONG_MIN; //强制立刻执行轮询来执行ANR策略
return INPUT_EVENT_INJECTION_PENDING;
}
}
这段代码,判断ANR的逻辑如下:
- 在首次进入
handleTargetsNotReadyLocked()
方法的时候,mInputTargetWaitCause
的值不为INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
,因此会去获取一个超时时间,并记录等待的开始的时间、等待超时时间,等待的原因为INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
。 - 当下一个输入事件调用
handleTargetsNotReadyLocked()
方法时,如果mInputTargetWaitCause
的值还没有被改变,仍然为INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
,则直接进入(currentTime >= mInputTargetWaitTimeoutTime)
的判断。如果超时等待时间大于5s,则满足该条件,进入onANRLocked()
方法,发送ANR通知。
- 正常分发逻辑
如果当前没有正在分发的Event
,会走到真正的分发逻辑:
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
// 将事件加入到 outboundQueue 队尾
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
if (wasEmpty && !connection->outboundQueue.isEmpty()) {
// 开始dispatch事件
startDispatchCycleLocked(currentTime, connection);
}
}
首先将事件加到outboundQueue
的队尾,然后开始分发事件。
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection) {
//当Connection状态正常,且outboundQueue不为空
while (connection->status == Connection::STATUS_NORMAL
&& !connection->outboundQueue.isEmpty()) {
EventEntry* eventEntry = dispatchEntry->eventEntry;
switch (eventEntry->type) {
case EventEntry::TYPE_KEY: {
KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
//发布Key事件
status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
keyEntry->deviceId, keyEntry->source,
dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
keyEntry->keyCode, keyEntry->scanCode,
keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
keyEntry->eventTime);
break;
}
}
// 发布事件成功后,从outboundQueue中取出事件,重新放入waitQueue队列
connection->outboundQueue.dequeue(dispatchEntry);
connection->waitQueue.enqueueAtTail(dispatchEntry);
}
}
调用inputPublisher.publishKeyEvent
将事件真正发送了出去,然后将事件从outboundQueue
中取出,加入到waitQueue
中。到这里,事件真正发送了出去了。
如果应用端及时处理完事件返回,会将事件从waitQueue
中删除。
总结
回答文章开始时的问题,为什么在主线程中sleep 9s不一定会造成ANR呢?
因为ANR的检查逻辑,是在下个事件的分发流程中进行的。如果在这个9s中,没有后续事件,或者后续事件的等待时间不超过5s,则不会触发ANR。
image.png整体流程如上图所示。
-
InputDispatcher
在发送事件之前,会检查Window是否Ready,这个判断条件就是waitQueue是否为空。 - 假设第一个消息被卡住了,则
waitQueue
不为空。 - 第二个消息来的时候,Window不Ready,会进入
handleTargetsNotReadyLocked
方法,将mInputTargetWaitCause
设置为INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
,并设置mInputTargetWaitTimeoutTime
为当前时间+5s。 - 之后再循环进来,如果还是卡顿状态,继续走进
handleTargetsNotReadyLocked
,当前时间如果大于mInputTargetWaitTimeoutTime
,才会触发ANR。
系统这样做的好处是,如果这个事件后续没有事件要处理,那其实不需要报ANR。只有当后续真的有事件需要处理,且事件被卡住的时候,才会触发ANR。