最近在做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);
}
// 新增:假如还有event在处理中,需求提前唤醒查看是否产生anr
const nsecs_t nextAnrCheck = processAnrsLocked();
nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
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
pps:新版别添加逻辑:假如还有event在处理中,需求提前唤醒查看是否产生anr.
- 经过AnrTracker记载一切待处理的events的超时时刻
- 获取最小的时刻,作为下次唤醒的timeout时刻
分发事情
在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呢?
由于在老版别的Android体系上,ANR的查看逻辑,是在下个事情的分发流程中进行的。假如在这个9s中,没有后续事情,或者后续事情的等候时刻不超过5s,则不会触发ANR。
全体流程如上图所示。
-
InputDispatcher
在发送事情之前,会查看Window是否Ready,这个判别条件便是waitQueue是否为空。 - 假定第一个音讯被卡住了,则
waitQueue
不为空。 - 第二个音讯来的时候,Window不Ready,会进入
handleTargetsNotReadyLocked
办法,将mInputTargetWaitCause
设置为INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
,并设置mInputTargetWaitTimeoutTime
为当时时刻+5s。 - 之后再循环进来,假如还是卡顿状况,持续走进
handleTargetsNotReadyLocked
,当时时刻假如大于mInputTargetWaitTimeoutTime
,则会触发ANR。
不过,上面的流程和源码是用的比较老的Android版别,新版别能够一起发多个事情,而且修正且严厉了ANR的校验逻辑。
新版别的逻辑有以下几点不同:
- 添加
AnrTracker
记载事情的超时时刻。 - 每次回调
dispatchOnce
都会查看Tracker
中记载的事情是否有超时,假如超时则触发ANR。 - 能够一起发多个
Event
,但只要有一个超时,则判定为ANR。 - 当时
Event
也会触发ANR,由于也会走到查看Tracker
中是否有超时Event
的逻辑。
下篇文章,我再来详细共享新版别的不同之处。
拓宽阅览
深入理解Android ANR触发原理以及信息采集进程