Android開發(fā)No Focused Window ANR產(chǎn)生原理解析
引言
之前我們講過因為事件沒有得到及時處理,引起的ANR問題。但這只是Input Dispatching Timeout中的一種情況,還有一種情況,在我們應(yīng)用中出現(xiàn)的也很常見,就是No Focused Window ANR,這個又是在哪些情況下產(chǎn)生的呢?
由之前的文章,我們知道,點擊事件都是由InputDispatcher來分發(fā)的,我們直接來看InputDispatcher的源碼。
No Focused Window ANR如何產(chǎn)生
如果是Key事件,或Motion事件,都需要找到焦點窗口取處理,都會調(diào)用到findFocusedWindowTargetsLocked()。
// frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry& entry, std::vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) { std::string reason; int32_t displayId = getTargetDisplayId(entry); // mFocusedWindowHandlesByDisplay在setInputWindowsLocked()里賦值 sp<InputWindowHandle> focusedWindowHandle = getValueByKey(mFocusedWindowHandlesByDisplay, displayId); // mFocusedApplicationHandlesByDisplay在setFocusedApplication()里賦值 sp<InputApplicationHandle> focusedApplicationHandle = getValueByKey(mFocusedApplicationHandlesByDisplay, displayId); // focusedWindowHandle和focusedApplicationHandle都為空時表示當前無窗口,該事件會被丟棄,不會執(zhí)行dispatchEventLocked // 一般出現(xiàn)兩個都為空的場景,是在窗口切換的過程,此時不處理事件注入 if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) { return INPUT_EVENT_INJECTION_FAILED; } // focusedWindowHandle為空但focusedApplicationHandle不為空時開始ANR檢查 if (focusedWindowHandle == nullptr && focusedApplicationHandle != nullptr) { // 默認mNoFocusedWindowTimeoutTime沒有值,第一次檢查ANR會走下面這個流程 if (!mNoFocusedWindowTimeoutTime.has_value()) { // DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5s * HwTimeoutMultiplier(); // 默認input dispatch timeout時間時5s const nsecs_t timeout = focusedApplicationHandle->getDispatchingTimeout( DEFAULT_INPUT_DISPATCHING_TIMEOUT.count()); // 給mNoFocusedWindowTimeoutTime賦值,觸發(fā)ANR時會檢查這個值是否為空,不為空才觸發(fā)ANR mNoFocusedWindowTimeoutTime = currentTime + timeout; // 把當前的focusedApplicationHandle賦值給mAwaitedFocusedApplication,觸發(fā)ANR時會檢查這個值是否為空,不為空才觸發(fā)ANR mAwaitedFocusedApplication = focusedApplicationHandle; mAwaitedApplicationDisplayId = displayId; *nextWakeupTime = *mNoFocusedWindowTimeoutTime; // 返回INPUT_EVENT_INJECTION_PENDING表示dispatchKeyLocked()或者dispatchMotionLocked()為false return INPUT_EVENT_INJECTION_PENDING; } else if (currentTime > *mNoFocusedWindowTimeoutTime) { // Already raised ANR. Drop the event return INPUT_EVENT_INJECTION_FAILED; } else { // Still waiting for the focused window return INPUT_EVENT_INJECTION_PENDING; } } // 如果走到這個流程,說明沒有ANR,清空mNoFocusedWindowTimeoutTime和mAwaitedFocusedApplication resetNoFocusedWindowTimeoutLocked(); return INPUT_EVENT_INJECTION_SUCCEEDED; }
主要邏輯:
- 如果focusedWindowHandle和focusedApplicationHandle都為null,一般發(fā)生在窗口切換的時候,返回INPUT_EVENT_INJECTION_FAILED,直接drop事件,不做處理
- 如果focusedWindowHandle為null,focusedApplicationHandle不為null,返回INPUT_EVENT_INJECTION_PENDING,在nextWakeupTime之后喚醒,檢查是否發(fā)生ANR
- mNoFocusedWindowTimeoutTime:記錄no focused window timeout的時間
- mAwaitedFocusedApplication:記錄focusedApplicationHandle
- nextWakeupTime: 下次喚醒pollInner的時間
檢查ANR邏輯
// frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp nsecs_t InputDispatcher::processAnrsLocked() { const nsecs_t currentTime = now(); nsecs_t nextAnrCheck = LONG_LONG_MAX; // 在findFocusedWindowTargetsLocked()中,如果focusedWindowHandle為空,focusedApplicationHandle不為空,以下條件就會滿足 if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) { // mNoFocusedWindowTimeoutTime為檢查時間+5s,如果currentTime大于等于mNoFocusedWindowTimeoutTime,表示超時 if (currentTime >= *mNoFocusedWindowTimeoutTime) { // 觸發(fā)ANR流程,此處觸發(fā)的ANR類型是xxx does not have a focused window processNoFocusedWindowAnrLocked(); // 清空mAwaitedFocusedApplication,下次就不會再走ANR流程 mAwaitedFocusedApplication.clear(); mNoFocusedWindowTimeoutTime = std::nullopt; return LONG_LONG_MIN; } else { // Keep waiting const nsecs_t millisRemaining = ns2ms(*mNoFocusedWindowTimeoutTime - currentTime); ALOGW("Still no focused window. Will drop the event in %" PRId64 "ms", millisRemaining); // 還沒有超時,更新檢查時間 nextAnrCheck = *mNoFocusedWindowTimeoutTime; } } .... // 如果走到這個流程,ANR類型是xxx is not responding. Waited xxx ms for xxx // 這個地方,focusedWindowHandle和focusedApplicationHandle都是不為空的場景 onAnrLocked(*connection); return LONG_LONG_MIN; }
主要流程:
- 如果mNoFocusedWindowTimeoutTime有值,且mAwaitedFocusedApplication不為空
- 超時:調(diào)用processNoFocusedWindowAnrLocked觸發(fā)ANR
- 未超時:更新檢查時間
- 繼續(xù)檢查input事件是否超時,如果超時,則調(diào)用onAnrLocked觸發(fā)ANR
processNoFocusedAnrLocked的流程
// frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp void InputDispatcher::processNoFocusedWindowAnrLocked() { // 在觸發(fā)ANR前,再獲取一次當前的focusedApplication sp<InputApplicationHandle> focusedApplication = getValueByKey(mFocusedApplicationHandlesByDisplay, mAwaitedApplicationDisplayId); // 檢查觸發(fā)ANR時的條件是focusedApplication不為空 // 如果此時focusedApplication為空,或者focusedApplication不等于前一個mAwaitedFocusedApplication表示已經(jīng)切換application focus,取消觸發(fā)ANR if (focusedApplication == nullptr || focusedApplication->getApplicationToken() != mAwaitedFocusedApplication->getApplicationToken()) { return; // The focused application has changed. } // 在觸發(fā)ANR前,再獲取一次當前的focusedWindowHandle const sp<InputWindowHandle>& focusedWindowHandle = getFocusedWindowHandleLocked(mAwaitedApplicationDisplayId); // 檢查觸發(fā)ANR時focusedWindowHandle為空,如果此時focusedWindowHandle不為空,取消觸發(fā)ANR if (focusedWindowHandle != nullptr) { return; // We now have a focused window. No need for ANR. } // 通過前面的判斷,還是無法攔截,說明該ANR無可避免,最終觸發(fā)ANR // 早期代碼沒有前面一系列的判斷,是直接觸發(fā)的ANR,會在性能較差的場景下出現(xiàn)誤判 onAnrLocked(mAwaitedFocusedApplication); }
主要流程:
- 在這個方法里面,再次檢查focusedApplication
- 如果當前focusedApplication為空,或者和之前記錄的mAwaitedFocusedApplication不一致,則說明窗口已經(jīng)切換,不需要報ANR
- 再次檢查focusedWindow是否未空
- 如果不為空,則不需要報ANR
- 檢查都通過之后,才會調(diào)用onAnrLocked,報no Focused Window ANR
focusedApplication設(shè)置流程
// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp void InputDispatcher::setFocusedApplication( int32_t displayId, const sp<InputApplicationHandle>& inputApplicationHandle) { { // acquire lock std::scoped_lock _l(mLock); // 獲取當前的focusedApplicationHandle sp<InputApplicationHandle> oldFocusedApplicationHandle = getValueByKey(mFocusedApplicationHandlesByDisplay, displayId); // 如果當前的focusedApplicationHandle跟觸發(fā)ANR是的focusedApplicationHandle是一樣且 // 新的focusedApplicationHandle跟舊的不一樣,說明focusedApplicationHandle有更新 // 需要重置ANR計時 if (oldFocusedApplicationHandle == mAwaitedFocusedApplication && inputApplicationHandle != oldFocusedApplicationHandle) { // 重置ANR計時 resetNoFocusedWindowTimeoutLocked(); } if (inputApplicationHandle != nullptr && inputApplicationHandle->updateInfo()) { if (oldFocusedApplicationHandle != inputApplicationHandle) { // 賦值新的inputApplicationHandle到mFocusedApplicationHandlesByDisplay,在findFocusedWindowTargetsLocked()時用到 mFocusedApplicationHandlesByDisplay[displayId] = inputApplicationHandle; } } else if (oldFocusedApplicationHandle != nullptr) { // 如果inputApplicationHandle為空,oldFocusedApplicationHandle不為空,需要清除oldFocusedApplicationHandle oldFocusedApplicationHandle.clear(); // 走到這個流程會出現(xiàn)findFocusedWindowTargetsLocked()中focusedApplicationHandle為空 mFocusedApplicationHandlesByDisplay.erase(displayId); } } // release lock // Wake up poll loop since it may need to make new input dispatching choices. mLooper->wake(); }
主要流程:
- 如果inputApplicationHandle與oldFocusedApplication,則要重置ANR計時
- 如果inputApplicationHandle不為空,則更新map中的值
- 如果inputApplicationHandle為空,則清除oldFocusedApplication
這個方法,是從AMS調(diào)過來的,主要流程如下圖:
focusedWindow設(shè)置流程
// frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp // 當VSYNC信號來了之后,會調(diào)用到SurfaceFlinger的onMessageInvalidate()方法 // SurfaceFlinger::onMessageInvalidate() // ==> SurfaceFlinger: updateInputFlinger() // ==> SurfaceFlinger: updateInputWindowInfo() // ==> InputManager::setInputWindows() // ==> InputDispatcher::setInputWindows() // ==> InputDispatcher::setInputWindowsLocked() void InputDispatcher::setInputWindowsLocked( const std::vector<sp<InputWindowHandle>>& inputWindowHandles, int32_t displayId) { // ...... const std::vector<sp<InputWindowHandle>> oldWindowHandles = getWindowHandlesLocked(displayId); // 更新mWindowHandlesByDisplay這個map,然后通過getWindowHandlesLocked()找newFocusedWindowHandle updateWindowHandlesForDisplayLocked(inputWindowHandles, displayId); sp<InputWindowHandle> newFocusedWindowHandle = nullptr; bool foundHoveredWindow = false; // 在mWindowHandlesByDisplay這個map里面找newFocusedWindowHandle for (const sp<InputWindowHandle>& windowHandle : getWindowHandlesLocked(displayId)) { // newFocusedWindowHandle要不為空,windowHandle具備focusable和visible屬性 if (!newFocusedWindowHandle && windowHandle->getInfo()->hasFocus && windowHandle->getInfo()->visible) { // 給newFocusedWindowHandle賦值,最后這個值存到mFocusedWindowHandlesByDisplay這個map newFocusedWindowHandle = windowHandle; } if (windowHandle == mLastHoverWindowHandle) { foundHoveredWindow = true; } } if (!foundHoveredWindow) { mLastHoverWindowHandle = nullptr; } // 在mFocusedWindowHandlesByDisplay這個map里找當前的焦點窗口 sp<InputWindowHandle> oldFocusedWindowHandle = getValueByKey(mFocusedWindowHandlesByDisplay, displayId); // 判斷oldFocusedWindowHandle是否等于newFocusedWindowHandle,如果相等則不走focus change流程 if (!haveSameToken(oldFocusedWindowHandle, newFocusedWindowHandle)) { // 如果當前的焦點窗口不為空,需要從mFocusedWindowHandlesByDisplay移除掉 if (oldFocusedWindowHandle != nullptr) { sp<InputChannel> focusedInputChannel = getInputChannelLocked(oldFocusedWindowHandle->getToken()); if (focusedInputChannel != nullptr) { CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, "focus left window"); synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options); // 新建一個FocusEntry加入到mInboundQueue去dispatch enqueueFocusEventLocked(*oldFocusedWindowHandle, false /*hasFocus*/); } // oldFocusedWindowHandle不為空時需要移除舊的 mFocusedWindowHandlesByDisplay.erase(displayId); } // 走到這個流程,如果oldFocusedWindowHandle不為空,newFocusedWindowHandle為空,那么在findFocusedWindowTargetsLocked()中的focusedWindowHandle為空 // 如果newFocusedWindowHandle不為空,更新mFocusedWindowHandlesByDisplay if (newFocusedWindowHandle != nullptr) { // 更新mFocusedWindowHandlesByDisplay,在findFocusedWindowTargetsLocked()時用到 mFocusedWindowHandlesByDisplay[displayId] = newFocusedWindowHandle; // 新建一個FocusEntry加入到mInboundQueue去dispatch enqueueFocusEventLocked(*newFocusedWindowHandle, true /*hasFocus*/); } if (mFocusedDisplayId == displayId) { // 添加focusChanged到mCommandQueue,在dispatchOnce時會執(zhí)行 onFocusChangedLocked(oldFocusedWindowHandle, newFocusedWindowHandle); } } // ...... }
這個方法,是從WMS調(diào)過來的,主要流程如下圖:
ANR可能得原因
設(shè)置focusedApplication和focusedWindow中間時間差太長,在這個時間差內(nèi)發(fā)生了ANR
設(shè)置focusedApplication發(fā)生在,resumeTopActivity,也就是am_set_resumed_activity的時候。
設(shè)置focusedWindow發(fā)生在,onResume之后,調(diào)用WMS的addView添加完窗口之后。
window被設(shè)置成了no_focusable,無法響應(yīng)焦點。
如果誤將一個window設(shè)置成no_focusable,則窗口無法成為focusedWindow,也可能導(dǎo)致ANR的發(fā)生。
以上就是Android開發(fā)No Focused Window ANR產(chǎn)生原理解析的詳細內(nèi)容,更多關(guān)于Android No Focused Window ANR的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android協(xié)程作用域與序列發(fā)生器限制介紹梳理
協(xié)程的作用是什么?協(xié)程是一種輕量級的線程,解決異步編程的復(fù)雜性,異步的代碼使用協(xié)程可以用順序進行表達,文中通過示例代碼介紹詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-08-08Android?Jetpack?組件LiveData源碼解析
這篇文章主要為大家介紹了Android?Jetpack?組件LiveData源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03Android4.4開發(fā)之電池低電量告警提示原理與實現(xiàn)方法分析
這篇文章主要介紹了Android4.4開發(fā)之電池低電量告警提示原理與實現(xiàn)方法,結(jié)合實例形式分析了Android4.4電池電量警告的原理及相關(guān)操作技巧,需要的朋友可以參考下2017-09-09Android中的SQL查詢語句LIKE綁定參數(shù)問題解決辦法(sqlite數(shù)據(jù)庫)
這篇文章主要介紹了Android中的SQL查詢語句LIKE綁定參數(shù)問題解決辦法,本文使用的是sqlite數(shù)據(jù)庫,需要的朋友可以參考下2014-06-06android使用ItemDecoration給RecyclerView 添加水印
本篇文章主要介紹了android使用ItemDecoration給RecyclerView 添加水印,介紹了自定義Drawable來完成水印圖片和使用ItemDecoration來布局水印,有興趣的可以了解一下。2017-02-02Android應(yīng)用獲取設(shè)備序列號的方法
本篇文章主要介紹了Android應(yīng)用獲取設(shè)備序列號的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06Android應(yīng)用開發(fā)中CardView的初步使用指南
這篇文章主要介紹了Android應(yīng)用開發(fā)中CardView的初步使用指南,CardView主要處理一些卡片型的視圖布局,需要的朋友可以參考下2016-02-02