最近在做ANR優(yōu)化,發(fā)現(xiàn)線上非常多的ANR(一半以上)原因都是
Input dispatching timed out。對于Activity或Service生命周期的ANR產(chǎn)生原理,我想大家應(yīng)該都比較了解了,就是在AMS里埋炸彈、拆炸彈那一套機(jī)制,那Input Dispatching time outANR是怎么產(chǎn)生的呢?這篇文章帶大家一起學(xué)習(xí)一下。
Android輸入系統(tǒng)
Input Dispatching time outANR是有Android點(diǎn)擊事件超時(shí)所產(chǎn)生的,所以要了解它產(chǎn)生的原理,就要從Android的輸入系統(tǒng)開始講起。
Android輸入系統(tǒng),主要包含以下幾個(gè)模塊:
發(fā)送端:運(yùn)行在system_server進(jìn)程,主要運(yùn)行在
InputReaderThread和InputDispatcherThread。
-
InputReader:這個(gè)模塊主要負(fù)責(zé)從硬件獲取輸入,轉(zhuǎn)換成事件Event,傳給InputDispatcher。 -
InputDispatcher:將InputReader傳遞過來的事件分發(fā)給相應(yīng)的窗口,并且監(jiān)控ANR。
接收端:運(yùn)行在應(yīng)用程序進(jìn)程,運(yùn)行在UI線程。
-
InputEventReceiver:在App端接收按鍵,并進(jìn)行分發(fā)。 -
View和Activity:接收按鍵并進(jìn)行處理。
基礎(chǔ)服務(wù):
-
InputManagerService:負(fù)責(zé)InputReader和InputDispatcher的創(chuàng)建。 -
WindowManagerService:管理InputManager與Window及AMS之間的通信。
通信機(jī)制:
-
socket:發(fā)送端和接收端跨進(jìn)程,采用的是socket的通信機(jī)制。
Android輸入系統(tǒng)的原理比較復(fù)雜,這篇文章,我們著重分析ANR發(fā)生的原理,所以我們只看InputDispatcher即可,因?yàn)殛P(guān)于ANR的判定是在這里發(fā)生的。
后續(xù)學(xué)姐會再出專題,詳細(xì)分析整個(gè)Android輸入系統(tǒng)的原理,感興趣的可以點(diǎn)個(gè)關(guān)注??。
ANR原理分析
我們先來思考一個(gè)問題,如果我在Activity的dispatchTouchEvent中,手動讓線程sleep一段時(shí)間。這種情況一定會報(bào)ANR么?
var count = 0;
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
// 讓第0個(gè)event,卡住9s
if(count == 0){
try {
Thread.sleep(9000)
} catch (e: Throwable) {
e.printStackTrace()
}
}
count++
return super.dispatchTouchEvent(ev)
}
我相信很多同學(xué)會回答一定,因?yàn)橹骶€程 sleep 的時(shí)間遠(yuǎn)遠(yuǎn)超過了 Input 事件報(bào)ANR的超時(shí)時(shí)間,所以會報(bào)ANR。
但真實(shí)的情況是,在主線程sleep 大于 5s 不一定會報(bào)ANR。下面我們就從InputDispatcher源碼的角度來看看,Input ANR到底是怎么產(chǎn)生的吧。
InputDispatcher啟動
InputDispatcher運(yùn)行在InputDispatcherThread線程中,這個(gè)線程和應(yīng)用UI線程一樣,也是靠Looper機(jī)制運(yùn)行起來的。
首先看下InputDispatcher對象的初始化過程:
InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
//創(chuàng)建Looper對象
mLooper = new Looper(false);
//獲取分發(fā)超時(shí)參數(shù)
policy->getDispatcherConfiguration(&mConfig);
}
主要就是創(chuàng)建了一個(gè)屬于自己線程的Looper對象。當(dāng)這個(gè)線程的Looper被啟動之后,會不斷重復(fù)調(diào)threadLoop方法,直到該方法返回false,退出循環(huán),從而結(jié)束線程。
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
threadLoop里面,只做了一件事情,就是調(diào)用InputDispatcher的dispatchOnce方法:
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
// 如果有commands需要處理,優(yōu)先處理commands
if (!haveCommandsLocked()) {
//當(dāng)commands處理完后,處理events
dispatchOnceInnerLocked(&nextWakeupTime);
}
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis); //進(jìn)入epoll_wait
}
InputDispatcher處理的事件主要分為兩種:一種是command,一種是input event。command主要是mPolicy處理的事務(wù),和我們點(diǎn)擊事件的ANR沒有關(guān)系,所以這里不詳細(xì)分析。input event的處理邏輯主要是,找到對應(yīng)的window對象,通過socket將event發(fā)送給應(yīng)用進(jìn)程。
當(dāng)處理完所有command和input event之后,會調(diào)用Looper的pollOnce方法。從之前的epoll機(jī)制分析文章中,我們知道,在這個(gè)方法里,線程會進(jìn)入epoll_wait等待。
喚醒epoll_wait的方法有:
- 監(jiān)聽的
fd有相關(guān)數(shù)據(jù)變化 - timeout:到達(dá)
timeoutMillis的時(shí)間 - wake:主動調(diào)
Looper的wake()方法。
這里會監(jiān)聽和應(yīng)用程序通信的socket fd,接收應(yīng)用程序處理完事件的消息。
ps:關(guān)于epoll機(jī)制的原理,可參考:從epoll機(jī)制看MessageQueue
分發(fā)事件
在InputDispatcher線程中,主要包含三個(gè)事件隊(duì)列:
-
mInBoundQueue:InputReader線程負(fù)責(zé)通過EventHub讀取輸入事件,一旦監(jiān)聽到輸入事件就放入這個(gè)隊(duì)列。 -
outBoundQueue:記錄即將分發(fā)給目標(biāo)應(yīng)用窗口的輸入事件。 -
waitQueue:記錄已分發(fā)給目標(biāo)應(yīng)用,且應(yīng)用尚未處理完成的輸入事件。
還有一個(gè)單獨(dú)的變量:
-
mPendingEvent:記錄當(dāng)前正在發(fā)送中的event,發(fā)送成功后會清空
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// 如果沒有正在發(fā)送中的event,才去取新的event
if (!mPendingEvent) {
// mInboundQueue為待處理的事件隊(duì)列
if (mInboundQueue.isEmpty()) {
if (!mPendingEvent) {
return; //沒有事件需要處理,則直接返回
}
} else {
//從mInboundQueue取出頭部的事件
mPendingEvent = mInboundQueue.dequeueAtHead();
}
// 新的分發(fā)開始了,重置ANR超時(shí)時(shí)間
resetANRTimeoutsLocked();
}
switch (mPendingEvent->type) {
// 嘗試分發(fā)按鍵事件
done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
break;
}
}
//分發(fā)操作完成,則進(jìn)入該分支
if (done) {
// 釋放pendingEvent事件,將這個(gè)標(biāo)志位設(shè)置為空
releasePendingEventLocked();
*nextWakeupTime = LONG_LONG_MIN; //強(qiáng)制立刻執(zhí)行輪詢
}
}
這個(gè)方法主要的邏輯是:
- 如果有正在發(fā)送的event(
pendingEvent),則什么都不做,如果沒有,則取mInboundQueue頭部的事件,用于發(fā)送。 - 調(diào)用
dispatchKeyLocked方法發(fā)送事件。 - 當(dāng)發(fā)送成功后,釋放
pendingEvent標(biāo)志位。
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
Vector<InputTarget> inputTargets;
// 尋找焦點(diǎn)
int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
return false; //直接返回
}
//只有injectionResult是成功,才有機(jī)會執(zhí)行分發(fā)事件
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
執(zhí)行到這一步的事件,不一定可以走到發(fā)送的邏輯。因?yàn)檫€需要尋找可執(zhí)行的焦點(diǎn),只有當(dāng)找到了可執(zhí)行焦點(diǎn)后,事件才會被真正分發(fā)。
int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
//檢測窗口是否為更多的輸入操作而準(zhǔn)備就緒
reason = checkWindowReadyForMoreInputLocked(currentTime,
mFocusedWindowHandle, entry, "focused");
if (!reason.isEmpty()) {
// 如果窗口沒有就緒,判斷是否發(fā)生了ANR
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.string());
}
}
這個(gè)方法會先檢測窗口是否就緒,如果未就緒,會判斷是否超過5s,即判斷是否發(fā)生ANR。
String8 InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
const char* targetType) {
// 處理一些窗口暫停、窗口連接已死亡、窗口連接已滿的問題
if (eventEntry->type == EventEntry::TYPE_KEY) {
// 按鍵事件,輸出隊(duì)列或事件等待隊(duì)列不為空
if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
return String8::format();
}
} else {
// 非按鍵事件,事件等待隊(duì)列不為空且頭事件分發(fā)超時(shí)500ms
if (!connection->waitQueue.isEmpty()
&& currentTime >= connection->waitQueue.head->deliveryTime
+ STREAM_AHEAD_EVENT_TIMEOUT) {
return String8::format();
}
}
return String8::empty();
}
到這里就很清楚了,如果outboundQueue不為空,或waitQueue不為空,此時(shí)表示Window不Ready。則新的事件無法走到正常分發(fā)的邏輯。
- Window不ready的邏輯
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime){
// 如果失敗的原因是因?yàn)樯弦粋€(gè)任務(wù)未處理完,則不需要給超時(shí)時(shí)間重新賦值
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
// 設(shè)置InputTargetWaitCause
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
//這里的currentTime是指執(zhí)行dispatchOnceInnerLocked方法體的起點(diǎn)
mInputTargetWaitStartTime = currentTime;
// timeout為5s
mInputTargetWaitTimeoutTime = currentTime + timeout;
mInputTargetWaitTimeoutExpired = false;
mInputTargetWaitApplicationHandle.clear();
}
//當(dāng)超時(shí)5s,則進(jìn)入ANR流程
if (currentTime >= mInputTargetWaitTimeoutTime) {
onANRLocked(currentTime, applicationHandle, windowHandle,
entry->eventTime, mInputTargetWaitStartTime, reason);
*nextWakeupTime = LONG_LONG_MIN; //強(qiáng)制立刻執(zhí)行輪詢來執(zhí)行ANR策略
return INPUT_EVENT_INJECTION_PENDING;
}
}
這段代碼,判斷ANR的邏輯如下:
- 在首次進(jìn)入
handleTargetsNotReadyLocked()方法的時(shí)候,mInputTargetWaitCause的值不為INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,因此會去獲取一個(gè)超時(shí)時(shí)間,并記錄等待的開始的時(shí)間、等待超時(shí)時(shí)間,等待的原因?yàn)?code>INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY。 - 當(dāng)下一個(gè)輸入事件調(diào)用
handleTargetsNotReadyLocked()方法時(shí),如果mInputTargetWaitCause的值還沒有被改變,仍然為INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,則直接進(jìn)入(currentTime >= mInputTargetWaitTimeoutTime)的判斷。如果超時(shí)等待時(shí)間大于5s,則滿足該條件,進(jìn)入onANRLocked()方法,發(fā)送ANR通知。
- 正常分發(fā)邏輯
如果當(dāng)前沒有正在分發(fā)的Event,會走到真正的分發(fā)邏輯:
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
// 將事件加入到 outboundQueue 隊(duì)尾
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
if (wasEmpty && !connection->outboundQueue.isEmpty()) {
// 開始dispatch事件
startDispatchCycleLocked(currentTime, connection);
}
}
首先將事件加到outboundQueue的隊(duì)尾,然后開始分發(fā)事件。
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection) {
//當(dāng)Connection狀態(tài)正常,且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);
//發(fā)布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;
}
}
// 發(fā)布事件成功后,從outboundQueue中取出事件,重新放入waitQueue隊(duì)列
connection->outboundQueue.dequeue(dispatchEntry);
connection->waitQueue.enqueueAtTail(dispatchEntry);
}
}
調(diào)用inputPublisher.publishKeyEvent將事件真正發(fā)送了出去,然后將事件從outboundQueue中取出,加入到waitQueue中。到這里,事件真正發(fā)送了出去了。
如果應(yīng)用端及時(shí)處理完事件返回,會將事件從waitQueue中刪除。
總結(jié)
回答文章開始時(shí)的問題,為什么在主線程中sleep 9s不一定會造成ANR呢?
因?yàn)锳NR的檢查邏輯,是在下個(gè)事件的分發(fā)流程中進(jìn)行的。如果在這個(gè)9s中,沒有后續(xù)事件,或者后續(xù)事件的等待時(shí)間不超過5s,則不會觸發(fā)ANR。

整體流程如上圖所示。
-
InputDispatcher在發(fā)送事件之前,會檢查Window是否Ready,這個(gè)判斷條件就是waitQueue是否為空。 - 假設(shè)第一個(gè)消息被卡住了,則
waitQueue不為空。 - 第二個(gè)消息來的時(shí)候,Window不Ready,會進(jìn)入
handleTargetsNotReadyLocked方法,將mInputTargetWaitCause設(shè)置為INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,并設(shè)置mInputTargetWaitTimeoutTime為當(dāng)前時(shí)間+5s。 - 之后再循環(huán)進(jìn)來,如果還是卡頓狀態(tài),繼續(xù)走進(jìn)
handleTargetsNotReadyLocked,當(dāng)前時(shí)間如果大于mInputTargetWaitTimeoutTime,才會觸發(fā)ANR。
系統(tǒng)這樣做的好處是,如果這個(gè)事件后續(xù)沒有事件要處理,那其實(shí)不需要報(bào)ANR。只有當(dāng)后續(xù)真的有事件需要處理,且事件被卡住的時(shí)候,才會觸發(fā)ANR。