[ANR] Input ANR是怎么產(chǎn)生的

最近在做ANR優(yōu)化,發(fā)現(xiàn)線上非常多的ANR(一半以上)原因都是
Input dispatching timed out。對于ActivityService生命周期的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)行在InputReaderThreadInputDispatcherThread

  • 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ā)。
  • ViewActivity:接收按鍵并進(jìn)行處理。

基礎(chǔ)服務(wù):

  • InputManagerService:負(fù)責(zé)InputReaderInputDispatcher的創(chuàng)建。
  • WindowManagerService:管理InputManagerWindowAMS之間的通信。

通信機(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è)問題,如果我在ActivitydispatchTouchEvent中,手動讓線程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 eventcommand主要是mPolicy處理的事務(wù),和我們點(diǎn)擊事件的ANR沒有關(guān)系,所以這里不詳細(xì)分析。input event的處理邏輯主要是,找到對應(yīng)的window對象,通過socketevent發(fā)送給應(yīng)用進(jìn)程。

當(dāng)處理完所有commandinput event之后,會調(diào)用LooperpollOnce方法。從之前的epoll機(jī)制分析文章中,我們知道,在這個(gè)方法里,線程會進(jìn)入epoll_wait等待。

喚醒epoll_wait的方法有:

  • 監(jiān)聽的fd有相關(guān)數(shù)據(jù)變化
  • timeout:到達(dá)timeoutMillis的時(shí)間
  • wake:主動調(diào)Looperwake()方法。

這里會監(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í)表示WindowReady。則新的事件無法走到正常分發(fā)的邏輯。

  1. 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通知。
  1. 正常分發(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。

image.png

整體流程如上圖所示。

  • 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。

拓展閱讀

深入理解Android ANR觸發(fā)原理以及信息采集過程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 簡介 分析 -- android 10.01.啟動過程 因?yàn)镮nputDispatcher跑在一個(gè)循環(huán)執(zhí)行的線程中...
    鋤禾豆閱讀 2,946評論 0 1
  • http://www.itdecent.cn/p/2bff4ecd86c9本篇博客主要是過一下Android I...
    wbo4958閱讀 8,151評論 4 20
  • 原創(chuàng)內(nèi)容,轉(zhuǎn)載請注明出處,多謝配合。 上一篇分析了InputReader獲取事件過程,最終InputReader將...
    Stan_Z閱讀 13,908評論 2 21
  • 原創(chuàng)內(nèi)容,轉(zhuǎn)載請注明出處,多謝配合。 先針對前面的Input調(diào)用流程進(jìn)行一個(gè)簡單總結(jié): EventHub: 收集底...
    Stan_Z閱讀 7,106評論 0 20
  • 什么是ANR ANR(Application Not Responding)就是應(yīng)用在規(guī)定的時(shí)間內(nèi)沒有響應(yīng)用戶輸入...
    lbtrace閱讀 3,540評論 3 9

友情鏈接更多精彩內(nèi)容