原始碼剖析Android ANR產生機制
如下采用Android原始碼的android-11.0.0_r48分支進行,不同版本原始碼差異巨大,
ANR的捕獲起點為ProcessRecord.appNotResponding方法,本文由appNotResponding逆推ANR的產生機制,
原始碼:frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java,
通過原始碼搜索appNotResponding,發現系統提供了AnrHelper類,封裝了ProcessRecord.appNotResponding,所有ANR產生后,呼叫都會走到這里,通過搜索發現Activity、Broadcast、Service、ContentProvider都會呼叫AnrHelper.appNotResponding,也就是說Android四大組件都有可能產生ANR,
原始碼:frameworks/base/services/core/java/com/android/server/am/AnrHelper.java
Activity ANR
在開始Activity ANR之前,先問個問題:
創建一個Android Hello World工程,添加一個Button,在Button的onClick中回呼SystemClock.sleep(10 * 1000),
- 運行App,點擊一次按鈕,發現按鈕處于Pressed狀態,因為阻塞了UI執行緒,那么一直等待,會不會產生ANR呢?結果是不會,10秒后,會看到Button狀態恢復正常,并沒有ANR框彈出,也沒有產生ANR,
- 運行App,連續點擊兩次按鈕,一直等待會不會產生ANR呢?結果是會,
下面開始原始碼分析:
-
從AnrHelper.appNotResponding出發,搜索呼叫地方,發現在ActivityManagerService中有inputDispatchingTimedOut方法,最終呼叫到AnrHelper.appNotResponding,最終走到ANR流程,此處通過函式名基本得到是輸入超時導致的ANR,
原始碼:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java -
通過查找inputDispatchingTimedOut呼叫,最終發現ActivityRecord、AnrController兩個類中有呼叫
原始碼:frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
原始碼:frameworks/base/services/core/java/com/android/server/wm/AnrController.java
此處通過路徑發現,Activity過來的ANR的觸發點在WindowManager中,也就是Activity中的Window才會觸發ANR,
繼續看原始碼ActivityRecord中的inputDispatchingTimedOut,發現其只有轉調WindowManager.inputDispatchingTimedOut,并沒有觸發邏輯,
繼續看原始碼AnrController中的inputDispatchingTimedOut,發現其中提供了幾種型別的ANR函式供呼叫:notifyAppUnresponsive、notifyWindowUnresponsive、notifyGestureMonitorUnresponsive,如下逐個分析,
notifyAppUnresponsive呼叫堆疊:
frameworks/base/services/core/java/com/android/server/wm/InputManagerCallback.java (notifyNoFocusedWindowAnr)
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp (NativeInputManager::notifyNoFocusedWindowAnr)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::doNotifyNoFocusedWindowAnrLockedInterruptible)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::onAnrLocked(std::shared_ptr<InputApplicationHandle> application))
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::processNoFocusedWindowAnrLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::processAnrsLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::dispatchOnce)
notifyWindowUnresponsive呼叫堆疊:
frameworks/base/services/core/java/com/android/server/wm/InputManagerCallback.java (notifyWindowUnresponsive)
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp (NativeInputManager::notifyWindowUnresponsive)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::doNotifyWindowUnresponsiveLockedInterruptible)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::sendWindowUnresponsiveCommandLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::processConnectionUnresponsiveLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::onAnrLocked(const sp<Connection>& connection))
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::processAnrsLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::dispatchOnce)
notifyGestureMonitorUnresponsive呼叫堆疊:
frameworks/base/services/core/java/com/android/server/wm/InputManagerCallback.java (notifyGestureMonitorUnresponsive)
frameworks/base/services/core/java/com/android/server/input/InputManagerService.java (notifyMonitorUnresponsive)
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp (NativeInputManager::notifyMonitorUnresponsive)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::doNotifyMonitorUnresponsiveLockedInterruptible)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::sendMonitorUnresponsiveCommandLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::processConnectionUnresponsiveLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::onAnrLocked(const sp<Connection>& connection))
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::processAnrsLocked)
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp (InputDispatcher::dispatchOnce)
從呼叫堆疊可見,如上3種型別的ANR最終都會收斂至InputDispatcher::dispatchOnce函式,那么接下來先看此函式,
InputDispatcher::dispatchOnce為InputDispatcher::start函式中開啟的名為InputDispatcher執行緒的運行體,InputDispatcher執行緒啟動后,如果沒有停止,則一直回圈呼叫InputDispatcher::dispatchOnce,此函式最后的mLooper->pollOnce(timeoutMillis)將執行緒Block住,等待timeoutMillis時間,然后開啟下一輪InputDispatcher::dispatchOnce呼叫(mLooper采用Linux epoll機制進行block,等待timeoutMillis的呼叫為epoll_wait(…, timeoutMillis)),timeoutMillis的值為5000,也即每5秒,
在某些事件發生時(如:輸入事件),可以呼叫mLooper->wake(),停掉等待,立刻呼叫InputDispatcher::dispatchOnce,然后其中會呼叫processAnrsLocked判斷此次是否發生ANR,
接下來看InputDispatcher::processAnrsLocked,此函式判斷是否產生ANR,函式的注釋簡單來說就是“等待的事件時長超過Window timeout(5秒),就產生ANR”,接下來通過原始碼再行驗證一下,
- 其中首先判斷if (currentTime >= *mNoFocusedWindowTimeoutTime),為true就呼叫processNoFocusedWindowAnrLocked()產生ANR,
- 如果上述判斷沒通過,意味著具備了Focused Window,那么則繼續判斷操作是否超時,如果if (currentTime < nextAnrCheck)判斷沒通過,不幸超時,則走到InputDispatcher::onAnrLocked(const sp& connection),產生connection型別的ANR,即:Activity Window型別的ANR,下面會詳解,
再回到最開始的InputDispatcher::dispatchOnce函式,除了輪詢呼叫外,在某些事件發生時(呼叫mLooper->wake()停掉等待),也會被呼叫,那么也就意味著每個促使mLooper->wake()被呼叫的事件,都將可能產生ANR,
原始碼:frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
在原始碼中搜索mLooper->wake(),發現有22個匹配,原始碼中幾乎每個呼叫都有注釋,如下列舉幾個常見的事件:
- InputDispatcher::notifyConfigurationChanged – 配置改變,如:橫豎屏切換
- InputDispatcher::notifyKey – 發生輸入事件
- InputDispatcher::notifySensor – 傳感器資訊改變
- InputDispatcher::setInputWindows – 設定輸入視窗
- InputDispatcher::setFocusedApplication – 為App設定焦點
- InputDispatcher::transferTouchFocus – 傳遞觸摸焦點
- InputDispatcher::setFocusedWindow – 設定視窗焦點
- InputDispatcher::displayRemoved – 移除Display
回到上面3個ANR函式呼叫(notifyAppUnresponsive、notifyWindowUnresponsive、notifyGestureMonitorUnresponsive),這3個函式分別代表3種型別的ANR,
- notifyAppUnresponsive:No Window Focus Timeout,指Activity還沒有Focused Window(包括No Attach Window),并且上述22種事件到來,長達5秒鐘無回應,從而導致ANR (InputDispatcher::processNoFocusedWindowAnrLocked)
- notifyWindowUnresponsive:Window Timeout,指Activity中的Window在上述22種事件到來時,長達5秒鐘無回應,從而導致ANR (InputDispatcher::processConnectionUnresponsiveLocked)
- notifyGestureMonitorUnresponsive:Window Monitor Timeout,跟notifyWindowUnresponsive類似,不過此處是注冊了Window事件的Monitor,如:PointerEventDispatcher (InputDispatcher::processConnectionUnresponsiveLocked)
- 無論UI阻塞多長時間,只要沒有必要事件需要Dispatch(mLooper->wake()),則不會產生ANR
- 如果在InputDispatcher::dispatchOnce的時刻,恰好Input事件沒有超時5秒,則也不會產生ANR
注:經測驗,華為/小米廠商將ANR的5000ms改為了8000ms,可以通過adb shell dumpsys input | grep dispatchingTimeout來獲取系統值,
adb logcat -s ActivityManager可以查看ANR的初步資訊,如:
E ActivityManager: ANR in com.huya.mtp.myanr (com.huya.mtp.myanr/.MainActivity)
E ActivityManager: PID: 10789
E ActivityManager: Reason: Input dispatching timed out (f69705 com.huya.mtp.myanr/com.huya.mtp.myanr.MainActivity (server) is not responding. Waited 5005ms for MotionEvent(deviceId=9, source=0x00005002, displayId=0, action=DOWN, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, classification=NONE, edgeFlags=0x00000000, xPrecision=30.3, yPrecision=17.1, xCursorPosition=nan, yCursorPosition=nan, pointers=[0: (405.0, 269.9)]), policyFlags=0x62000000)
E ActivityManager: Parent: com.huya.mtp.myanr/.MainActivity
總結:Activity有3種ANR型別,分別為:No Window Focus Timeout,Window Timeout,Window Monitor Timeout,而這3種型別具備22個觸發事件點,函式呼叫為:mLooper->wake(),Android官方設定的超時時間為5000ms,
Broadcast ANR
在開始Broadcast ANR之前,同樣問個問題:
創建一個Android Hello World工程,動態注冊一個BroadcastReceiver,在onReceiver中呼叫SystemClock.sleep(100 * 1000),添加一個Button,點擊后發送廣播讓動態注冊的BroadcastReceiver接收,
- 運行App,點擊一次按鈕,發現有可能按鈕處于Pressed狀態,因為阻塞了UI執行緒,那么一直等待,會不會產生ANR呢?結果是不會
- 如果將動態注冊改為靜態注冊,再次運行,會不會產生ANR呢?結果是會
下面開始原始碼分析:
1. 繼續來從AnrHelper.appNotResponding出發,搜索呼叫地方,發現在BroadcastQueue中有broadcastTimeoutLocked方法,最終呼叫到AnrHelper.appNotResponding,最終走到ANR流程,此處通過函式名基本得到是廣播超時導致的ANR,
原始碼:frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
2. 查看broadcastTimeoutLocked原始碼發現,其從一個變數名為mDispatcher的物件中取活躍的廣播,然后對這個廣播進行發送,mDispatcher的型別為BroadcastDispatcher,根據類注釋知道,這個類是用來管理有序廣播的,有序廣播記錄在BroadcastDispatcher.mOrderedBroadcasts的ArrayList中,
原始碼:frameworks/base/services/core/java/com/android/server/am/BroadcastQueue/BroadcastDispatcher.java
3. 回到BroadcastQueue中,發現除了存盤有序廣播的BroadcastDispatcher mDispatcher物件外,還存在并行廣播型別ArrayList mParallelBroadcasts,這兩種型別的廣播在都會在processNextBroadcastLocked方法中被處理,processNextBroadcastLocked方法代碼700多行,直接反推比較繞,下面改變方向,采用順推方式,
原始碼:frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
4. 現在回到Android四大組件的Broadcast,發送廣播有如下3種方式:
- Context.sendBroadcast:發送普通廣播(并行廣播)
- Context.sendOrderedBroadcast:發送有序廣播(串行廣播)
- Context.sendStickyBroadcast:發送粘性廣播
由于粘性廣播已被廢棄,所以接下來只看普通廣播與有序廣播,廣播的并行與串行是針對接收方來說的,
5. 下面進入原始碼呼叫鏈
Context.sendBroadcast --> ContextWrapper.sendBroadcast --> ContextImpl.sendBroadcast --> ActivityManagerService.broadcastIntentWithFeature
Context.sendOrderedBroadcast --> ContextWrapper.sendOrderedBroadcast --> ContextImpl.sendOrderedBroadcast --> ActivityManagerService.broadcastIntentWithFeature
呼叫最終通過Binder跨行程走到了ActivityManagerService.broadcastIntentWithFeature,通過引數設定了是否有序,引數為broadcastIntentWithFeature方法中的倒數第3個引數(boolean serialized),broadcastIntentWithFeature方法有15個引數,
6. 繼續深入ActivityManagerService.broadcastIntentWithFeature查看呼叫鏈
ActivityManagerService.broadcastIntentWithFeature ----> ActivityManagerService.broadcastIntentLocked
經過多載呼叫來到了ActivityManagerService.broadcastIntentLocked方法,這又是一個700行左右的大方法,看來接下來只能死磕了,此處核心關注點在于廣播如何被保存起來,
6.1. 函式最開始判斷是否為Instant狀態、判斷廣播白名單、判斷系統是否完成啟動、判斷UserId等,這些跟并行與串行廣播沒啥關系,跳過
6.2. 判斷bOptions是否為null(注意:不是變數brOptions),bOptions是給System發送廣播用的,App發送的廣播bOptions寫死為null,所以跳過bOptions判斷的整段代碼,
6.3. 判斷是否為Protected Broadcast,根據注釋,這仍然是System行為,跳過
6.4. 接下來對發送者與系統廣播做一次安全檢測,不符合則拋出SecurityException,繼續跟并行與串行沒啥關系,跳過
6.5. 如果廣播的action不為null,則進入一個巨大的switch case,用于處理Intent.ACTION_PACKAGE_REMOVED等預定義廣播,跟我們自定義的沒關系,繼續跳過
6.6. 接下來處理粘性廣播(sticky),并為之添加一些屬性,由于粘性不在本文范圍內,并且也已經廢棄,跳過
6.7. 接下來獲取廣播的users,跳過
6.8. 接下來看到注釋“// Figure out who all will receive this broadcast”,來了來了,讀完500行代碼后,終于來到廣播接收的處理邏輯處了,
6.9. 首先判斷Intent是否不具備Intent.FLAG_RECEIVER_REGISTERED_ONLY屬性,如果不具備則呼叫collectReceiverComponents收集receivers,根據Android檔案Intent.FLAG_RECEIVER_REGISTERED_ONLY表示動態注冊的廣播型別,不會Launch BroadcastReceiver components,
6.9.1. 接下去開啟支線任務,進到collectReceiverComponents查看收集到了一些什么receivers,方法回傳型別為List,ResolveInfo即為IntentFilter中的Intent,
6.9.2. collectReceiverComponents的主要是呼叫了AppGlobals.getPackageManager().queryIntentReceivers(intent, resolvedType, pmFlags, user).getList()來收集List
6.9.2.1. 接下來繼續開啟支線任務,進入到queryIntentReceivers看看怎么樣將List收集到的,
PackageManagerService.queryIntentReceivers --> PackageManagerService.queryIntentReceiversInternal --> ComponentResolver.queryReceivers --> ComponentResolver.queryIntent
ComponentResolver類用于決議Android四大組件,最終的呼叫ComponentResolver.queryIntent將所有的靜態與動態注冊廣播的Intent的query出來,并呼叫PackageManagerService.applyPostResolutionFilter決議后保存到List中,最侄訓傳給collectReceiverComponents(這個支線扯遠了,已經到PackageManagerService中了),
6.10. 回到ActivityManagerService,此時receivers已經賦值,其中的內容為所有通過Resolve的Intent的List,也就是所有靜態與動態注冊的廣播接收者List,
6.11. 接下來呼叫mReceiverResolver.queryIntent為registeredReceivers賦值,好家伙,又來一個廣播接收者List,此處支線下去看的話,發現回傳的僅為動態注冊的接收者List(支線任務就不展開講了,感興趣可以自己閱讀原始碼),
6.12. 接下來采用白名單對registeredReceivers進行處理,跟并行與串行無關,跳過,
6.13. 此處總結一下,上面分析了那么多,最終得到了兩個廣播接收者List:
- receivers:所有注冊的廣播接收者,包括靜態與動態
- registeredReceivers:僅為動態注冊的廣播接收者
6.14. 接下來判斷:如果不是串行廣播,并且具備接收者(registeredReceivers.size() > 0),則呼叫queue.enqueueParallelBroadcastLocked發送廣播,從方法名可以看出來,此處是并行的,enqueueParallelBroadcastLocked將廣播添加到mParallelBroadcasts,揮應了上面逆推中的第3點,而mParallelBroadcasts中的廣播怎么被處理,后面再講,
6.15. 接下來判斷是否存在receivers,如果存在,則進行安全檢測,避免監聽Intent.ACTION_PACKAGE_ADDED做App立即啟動的后門,此處與我們主題無關,跳過,
6.16. 接下來按照廣播的優先級priority進行排序,串行的廣播按照優先級發送,
6.17. 接下來做一些List操作后,最終呼叫queue.enqueueOrderedBroadcastLocked發送廣播,同樣從方法名可以看出,此處是串行的,enqueueOrderedBroadcastLocked將廣播添加到mDispatcher中,揮應了上面逆推中的第2點,至于廣播怎么被處理,同樣后面再講,
6.18. 看完這個700行的大方法后,最后總結一下:
- mParallelBroadcasts:并行廣播發送佇列,其中要求廣播接收者必須是動態注冊的,并且采用非Order的sendBroadcast發送,
- mDispatcher:串行廣播發送佇列管理類,如果廣播接收者是靜態注冊的或者采用Order的sendOrderedBroadcast發送,
也就是說: - 動態注冊廣播接收者的廣播,采用sendBroadcast發送則是并行發送與接受的,
- 靜態注冊廣播接收者的廣播,無論怎樣發送都是串行的接受的,
- 采用sendOrderedBroadcast發送的廣播,無論接收者是靜態還是動態注冊的,都將串行接收,
7. 上面完成串行并行廣播的入列之后,接下去看一下佇列中的廣播是怎樣觸發消費的(也就是說了解了生產者邏輯后,看看如何消費的,并優先看看如何觸發消費的),
7.1. 在queue.enqueueParallelBroadcastLocked與queue.enqueueOrderedBroadcastLocked方法后,都能看到queue.scheduleBroadcastsLocked呼叫,從函式名看出這個應該是調度廣播去消費了,
7.2. 進入scheduleBroadcastsLocked方法,其中采用Handler發送了BROADCAST_INTENT_MSG訊息,
7.3. 在接受BROADCAST_INTENT_MSG Handler訊息的之處,看到呼叫processNextBroadcast,也就是處理下一條廣播,
7.4. 進入processNextBroadcast后看到其同步呼叫processNextBroadcastLocked,而processNextBroadcastLocked便是廣播消費的主體了,
8. 下面進入processNextBroadcastLocked查看廣播消費,這也揮應了如上逆推中的第3點,也就是mParallelBroadcasts與mDispatcher兩個佇列均在processNextBroadcastLocked中被處理,這就又回到這個700多行的大方法了,接下來又只能死磕了,由于具備上面第6點的順推知識,此處順推這個大方法也就輕松多了,此處核心關注點在這個廣播如何被發送出去,
原始碼:frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
8.1. 方法開始定義一個變數BroadcastRecord r,這便是廣播的記錄,代表著當前廣播,也就是整個方法都圍繞著這個r進行,
8.2. 接下里更新CPU等內容,跳過,
8.3. 接下來看到一個while回圈(while (mParallelBroadcasts.size() > 0) {),mParallelBroadcasts是并行佇列,所以接下來這個回圈便是處理并行廣播的,這個回圈的核心呼叫為deliverToRegisteredReceiverLocked,將廣播deliver至RegisteredReceiver型別的的Receiver中,根據第6點知識,RegisteredReceiver便是動態注冊的廣播串列,
8.3.1. 接下來又該開啟支線任務了,deliverToRegisteredReceiverLocked又是一個200多行的大方法,繼續死磕
8.3.2. 方法開始就定義一個boolean skip = false,表示是否要跳過這個廣播的deliver,
8.3.3. 接下來大約150行都是各種if判斷,if判斷為真,則skip = true,if中判斷各種權限之類的,如果最終skip = true,則跳過這次deliver,此處忽略這種被skip的情況,核心關注如何被發送,
8.3.4. 接下來繼續判斷權限,然后繼續跳過deliver,跳過,
8.3.5. 如果是ordered的廣播,還要處理oomAdj,跟如何發送也無關,繼續跳過,
8.3.6. 接下來是一個try catch,看到if判斷,一般來說filter.receiverList.app.inFullBackup為false(inFullBackup:Process is currently hosting a backup agent for backup or restore),即會走到else處,最終呼叫核心函式performReceiveLocked,開始接受廣播,
8.3.6.1. 進入performReceiveLocked方法,根據不同條件,可能會轉調scheduleRegisteredReceiver或performReceive,
8.3.6.2. scheduleRegisteredReceiver的定義在IApplicationThread.aidl中,此aidl有oneway標記,即異步執行,
原始碼:frameworks/base/core/java/android/app/IApplicationThread.aidl
8.3.6.3. performReceive的定義在IIntentReceiver.aidl中,同樣,此aidl也有oneway標記,同樣是異步執行,
原始碼:frameworks/base/core/java/android/content/IIntentReceiver.aidl
8.3.7. 從performReceiveLocked函式中出來后,回到processNextBroadcastLocked函式的while回圈中,由于異步執行,所以while回圈會很快發送完所有訊息,讓registeredReceivers分別去處理,所以這也就是廣播的并行發送邏輯,
8.4. 接下去該到串行佇列mDispatcher的處理了(由于一個廣播可以被某應用動態注冊為并行廣播,也可以被其他應用靜態注冊為串行廣播,所以接下去還需要處理串行佇列),接著往下看processNextBroadcastLocked方法,代碼中也可看到注釋:// Now take care of the next serialized one…,
8.5. 首先處理行程等待的特殊邏輯,此處不關心,跳過,
8.6. 接下去看到一個有著200行代碼的巨大的do while回圈,首先呼叫r = mDispatcher.getNextBroadcastLocked(now)去取串行佇列中的BroadcastRecord r,在7.1中講過,整個大方法都圍繞著這個r進行,如果r為null,那么就該結束了,否則繼續往下,
8.7. 接著判斷些出錯邏輯,如果當前時間超出了2倍的廣播超時時間 * 接收者數,那么就彈出ANR,由于這里是特殊出錯邏輯,非主流程,所以可無視,跳過此處接著看,
8.8. 接著的兩個if判斷都是在處理特殊出錯邏輯,繼續跳過,
8.9. 接下去是一個大的延時廣播判斷if (!r.deferred),deferred僅在addDeferredBroadcast被賦值為true,此處進入后if后,再呼叫if (mDispatcher.isDeferringLocked(receiverUid))判斷是否需要延期處理,通常被判斷為不需要,最后跳出這個大的if判斷,
8.10. 繼續往下,進行一系列系統廣播除錯所需的Track與Log等操作后,計算timeout時間后,呼叫setBroadcastTimeoutLocked
8.10.1. 接下來支線任務到setBroadcastTimeoutLocked中,看到在超時的時間點,將采用Handler發送BROADCAST_TIMEOUT_MSG訊息
8.10.2. 在接受BROADCAST_TIMEOUT_MSG Handler訊息的之處,看到broadcastTimeoutLocked
8.10.3. 進入broadcastTimeoutLocked,這就揮應第1點了,這個方法進行一系列判斷后,最終呼叫AnrHelper.appNotResponding,走到ANR流程,繞了一大圈,終于找到廣播觸發ANR的導火索了,
8.10.4. setBroadcastTimeoutLocked在未來的時間點埋了個定時炸彈,如果時間到,還沒拆除,就ANR,
8.11. 先回來,上面埋了定時炸彈后,串行廣播還沒有被發送,所以接著往下看,if判斷是否需要直接呼叫呼叫receiver(if (nextReceiver instanceof BroadcastFilter)),此處不需要,跳過,
8.12. 接下去又是一個250行的skip判斷邏輯,走完后skip = false,無需skip此廣播,由于靜態注冊廣播的App如果未啟動,系統會將App啟動,于是這一系列skip判斷邏輯包括判斷App是否處于已安裝的正常狀態,權限上是否允許啟動,最后在必要時啟動App,
8.13. 最終呼叫processCurBroadcastLocked進行廣播的發送,然后便return結束這個長達700行的大方法,
8.14. processCurBroadcastLocked函式最終通過Binder呼叫IApplicationThread.scheduleReceiver,
原始碼:frameworks/base/core/java/android/app/IApplicationThread.aidl
等一下,此處的IApplicationThread仍然是oneway的,也就是說仍然是異步呼叫的,說好的同步呢?說好的串行呢?ANR定時炸彈何時拆除呢?
9. 為了解決上面的問題,這次從BroadcastReceiver再出發,在廣播接收處理結束,會呼叫BroadcastReceiver的sendFinished方法,不管是并行還是串行,都會呼叫ActivityManager.finishReceiver,如果是串行,則會傳遞一些列引數,用于后續事項,如果是并行或沒有接收器了,則基本傳0或null了,目的只為告訴ActivityManagerService,這個廣播最后一個接收器處理完了,
原始碼:frameworks/base/core/java/android/content/BroadcastReceiver.java
10. 在ActivityManager.finishReceiver中,呼叫getMatchingOrderedReceiver判斷此Receiver是否為串行的:
- 如果是,則呼叫BroadcastQueue.finishReceiverLocked,然后再呼叫BroadcastQueue.processNextBroadcastLocked,
- 如果為否,即為并行,那么就什么都不處理,回憶一下如上并行處理邏輯,在processNextBroadcastLocked中,通過一個while回圈將所有廣播發送至動態注冊的接收者中了,一次性就全部處理完了,也沒有埋什么定時炸彈,所以對于并行廣播,對于ActivityManagerService來說,沒有什么后事需要處理了,
11. 接下來再次回到BroadcastQueue中,查看BroadcastQueue.finishReceiverLocked,方法名為結束Receiver,根據上面的分析,并行接收器名稱為registeredReceivers,所以此方法是專門為串行接收器準備的,方法中主要是處理一些資料結構等善后作業,此處就不詳細展開了,
12. 繼續在BroadcastQueue中查看processNextBroadcastLocked,這個方法名有些眼熟,這又回到這個700多行代碼的大方法了,根據上面串行的分析,對于串行廣播佇列,每一次processNextBroadcastLocked呼叫僅deliver一個廣播,然后等待BroadcastReceiver的sendFinished,然后再行呼叫processNextBroadcastLocked來deliver下一個廣播,如此來實作了串行,
而拆掉定時炸彈的作業也是在processNextBroadcastLocked中進行的,其中有cancelBroadcastTimeoutLocked呼叫,函式實作為移除Handler訊息BROADCAST_TIMEOUT_MSG,
采用adb shell dumpsys activity broadcasts可以查看系統為廣播設定的超時值(Broadcast parameters)
- key=bcast_fg_constants:前臺廣播(默認超時10秒)
- key=bcast_bg_constants:后臺廣播(默認超時60秒)
- key=bcast_offload_constants:offload廣播(默認超時60秒),已知需要耗費長時間的廣播,如:BOOT_COMPLETED
adb logcat -s ActivityManager可以查看ANR的初步資訊,如:
E ActivityManager: ANR in com.huya.mtp.mybroadcast
E ActivityManager: PID: 10653
E ActivityManager: Reason: Broadcast of Intent { act=com.huya.mtp.mybroadcast.BROADCAST_STATIC flg=0x10000010 pkg=com.huya.mtp.mybroadcast cmp=com.huya.mtp.mybroadcast/.MyBroadcastStaticReceiver (has extras) }
總結:Broadcast只有一種ANR,就是超時,對于并行廣播,是不存在超時這一說的,也就是并行廣播不會引發ANR,而對于串行廣播,每次發送給接收器之前,都會通過Handler在未來設定一個定時炸彈,如果此接收器在超時之內沒有完成處理,則引爆炸彈彈出ANR,按照編碼的說法:
- 呼叫Context.sendBroadcast,發送給動態注冊的Receiver,則怎么都不會引發ANR
- 呼叫Context.sendBroadcast,發送給靜態注冊的Receiver,如果Receiver在超時時間內未完成,則ANR
- 呼叫Context.sendOrderedBroadcast,無論發送給動態或靜態注冊的Receiver,如果Receiver在超時時間內未完成,則ANR
Service ANR
在開始Service ANR前,再次同樣問個問題:
創建一個Android Hello World工程,寫一個服務,在Service.onCreate中呼叫SystemClock.sleep(300 * 1000),添加一個Button,點擊后呼叫Context.startForegroundService啟動這個服務,
運行App,點擊一次按鈕,一直等待,會不會產生ANR呢?結果是會,但通常看不到彈出ANR框,因為這個時候會崩潰,實際上此時即發生了ANR,又發生了崩潰,
上面硬磕完Broadcast的部分代碼后,此處看Service的代碼就輕松多了,仍然采用先逆推,后順推的方式,
1. 繼續從AnrHelper.appNotResponding出發,搜索呼叫地方,發現在ActiveServices中有serviceTimeout, serviceForegroundTimeout,最終呼叫到AnrHelper.appNotResponding,最終走到ANR流程,此處通過函式名基本得到是服務超時導致的ANR,
原始碼:frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
2. 搜索serviceTimeout的呼叫,發現在ActivityManagerService中有唯一呼叫,這就又回到ActivityManagerService的代碼中了,繼續發現當收到Handler訊息SERVICE_TIMEOUT_MSG后,便開始ANR流程,
原始碼:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
3. 繼續搜索SERVICE_TIMEOUT_MSG,發現又回到ActiveServices中了,在其中有兩處Handler.removeMessages呼叫(根據Broadcast原始碼猜測,則應該是Service拆定時炸彈邏輯了),有一處Handler.sendMessageDelayed(埋定時炸彈了),
4. 先看Handler.sendMessageDelayed的封裝方法scheduleServiceTimeoutLocked,翻譯成中文大概是“鎖定的調度服務超時”,上面Broadcast就多次出現schedule(調度),此處應該也就是到了Service被消費(被創建)的邏輯了,
5. 繼續逆推看scheduleServiceTimeoutLocked的呼叫出,發現被bumpServiceExecutingLocked方法呼叫了,繼續往上逆推,發現bumpServiceExecutingLocked又被realStartServiceLocked呼叫了,這就切實到了Service被創建的時刻了,這都幾乎推到最開始了,下面轉為順推,應該會看得更清晰,
6. 這兒我們順推Context.startService,進入呼叫鏈:
Context.startService --> ContextWrapper.startService --> ContextImpl.startService --> ContextImpl.startServiceCommon --> ActivityManagerService.startService
看到跟Broadcast的流程一摸一樣,下面再驗證一下Context.startForegroundService,進入呼叫鏈:
Context.startForegroundService --> ContextWrapper.startForegroundService --> ContextImpl.startForegroundService --> ContextImpl.startServiceCommon --> ActivityManagerService.startService
發現兩者的流程也一致,foreground與否的差別在于startServiceCommon的第二個引數boolean requireForeground,
7. 繼續深入ActivityManagerService.startService查看呼叫鏈
ActivityManagerService.startService ----> ActiveServices.startServiceLocked
經過多載來到了ActiveServices.startServiceLocked,這個方法大概250行代碼,接下來進去看看,
7.1. 判斷并設定final boolean callerFg的值,表示呼叫發起者是否為前臺,這個值將貫穿整個Service的啟動程序,ActivityManagerService.getRecordForAppLocked用于查找呼叫發起者的資訊,
7.2. 接著呼叫retrieveServiceLocked查找服務啟動接收者的資訊,并最終取得ServiceRecord r,跟廣播變數名一致,這個r變數是用于記錄服務資訊的,
7.3. 接下來一系列的判斷,最終為ServiceRecord r的成員變數賦值,
7.4. 然后又是一系列的判斷,最終來到內部的startServiceInnerLocked方法,
7.4.1. 接下去開啟支線任務,進入到startServiceInnerLocked
7.4.2 繼續進入,進行一些邏輯處理后,轉調bringUpServiceLocked
7.4.3. 繼續進入,進行一些邏輯處理后,轉調realStartServiceLocked
7.4.4. 繼續進入,進行一些邏輯處理后,呼叫bumpServiceExecutingLocked(此處也能揮應至如上第5點了),在7.1點中提到boolean callerFg,此值一直傳遞到bumpServiceExecutingLocked中,最終賦值給r變數的executeFg,在后續設定Service超時時使用,
7.4.5. 繼續進入,進行一些判斷后,發現呼叫了scheduleServiceTimeoutLocked,這兒就埋上ANR定時炸彈了,如果是前臺服務,超時設定為20秒,后臺則為200秒(此處揮應如上第4點),
7.4.6. 從scheduleServiceTimeoutLocked中退出
7.4.7. 從bumpServiceExecutingLocked中退出
7.4.8. 回到realStartServiceLocked繼續往下看,最終通過Binder呼叫到ActivityThread.scheduleCreateService,回呼已經來到Service的目標行程中了,scheduleCreateService發送Handler訊息CREATE_SERVICE,
原始碼:frameworks/base/core/java/android/app/ActivityThread.java
7.4.9. 在接受CREATE_SERVICE處呼叫handleCreateService,
7.4.10. handleCreateService進行一系列檢查后,呼叫service.onCreate,這就到了目標Service的onCreate方法了,也就是Service的@Override onCreate,
7.4.11. 上述onCreate執行完后,接著往下通過Binder呼叫到ActivityManagerService.serviceDoneExecuting,
7.4.12. 繼續一路轉調至ActiveServices.serviceDoneExecutingLocked
7.4.13. 然后再轉調至ActiveServices.serviceDoneExecutingLocked,在其中經過一系列判斷后,最終呼叫Handler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app)拆除ANR定時炸彈,
7.4.14. 這里補充說明一下,查看原始碼的同時,有些朋友可能會看到Handler訊息ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,10秒的超時時間SERVICE_START_FOREGROUND_TIMEOUT,從命名上看,似乎說的是前臺服務,而前臺服務超時的20秒在7.4.5點已經講過了,此處的這個10秒的意思是什么呢?實際上,如果呼叫Context.startForegroundService啟動前臺服務,則系統要求在這個10秒鐘之內,Service必須呼叫Context.startForeground,否則會產生ANR,也會拋出RuntimeException崩潰,崩潰會將行程退掉,所以表面看上去是發生崩潰了,實際上ANR也發生了,只不過表面上看不到(崩潰是因為發送了Handler訊息SCHEDULE_CRASH,代碼在ActivityThread.java中)
logcat中的崩潰堆疊:
2021-11-08 15:20:13.931 11661-11661/com.huchao.myserviceanr E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.huchao.myserviceanr, PID: 11661
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{56a0ba u0 com.huchao.myserviceanr/.MyService}
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2005)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
logcat中的ANR資訊:
E ActivityManager: ANR in com.huchao.myserviceanr
E ActivityManager: PID: 11661
E ActivityManager: Reason: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{56a0ba u0 com.huchao.myserviceanr/.MyService}
adb logcat -s ActivityManager可以查看ANR的初步資訊,如:
ActivityManager: ANR in com.huchao.myserviceanr
ActivityManager: PID: 11033
ActivityManager: Reason: executing service com.huchao.myserviceanr/.ForegroundService
總結:Service在創建超時會產生ANR,有3個超時值:
- ActiveServices.SERVICE_START_FOREGROUND_TIMEOUT:10秒,開啟前臺服務超時時長,是指Context.startForegroundService呼叫后,Service中必須呼叫Context.startForeground,否則會同時發生ANR與崩潰,
- ActiveServices.SERVICE_TIMEOUT:20秒,前臺服務超時時長,是指Context.startForegroundService呼叫后,Service的onCreate執行的超時,如果超時則會發生ANR,
- ActiveServices.SERVICE_BACKGROUND_TIMEOUT:200秒,后臺服務超時時長,是指Context.startService呼叫后,Service的onCreate執行的超時,如果超時則會發生ANR,
除了如上Service在創建時超時外,在bind系結、unbind解綁、destroy銷毀、start執行、反start執行時(某些特殊的服務Pending邏輯),也受如上條件限制,
按照編碼的說法:
Service在onCreate、onDestroy、onStartCommand、onBind、onUnbind中執行都受超時限制,如果超時則ANR,
ContentProvider ANR
1. 繼續來從AnrHelper.appNotResponding出發,搜索呼叫地方,發現在ContentProviderHelper中有appNotRespondingViaProvider方法,最終呼叫到AnrHelper.appNotResponding,最終走到ANR流程,此處通過函式名基本得到是ContentProvider導致的ANR,
原始碼:frameworks/base/services/core/java/com/android/server/am/ContentProviderHelper.java
2. 接著搜索appNotRespondingViaProvider,發現好幾個地方有呼叫地方,接下來一步步看,
2.1. ContentProviderHelper中的appNotRespondingViaProvider呼叫,在ContentProviderHelper類中就有名為getProviderMimeType的方法呼叫appNotRespondingViaProvider,繼續搜索getProviderMimeType,發現在ActivityManagerShellCommand中有呼叫getProviderMimeType,不過這個類是給adb shell am start-activity使用的,已超出本文范圍,所以忽略,
原始碼:frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
2.2. ActivityManagerService, ActivityThread, ContextImpl中均有appNotRespondingViaProvider呼叫,不過這3個類僅為轉調appNotRespondingViaProvider,并沒有觸發邏輯,所以先忽略,
原始碼:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
frameworks/base/core/java/android/app/ActivityThread.java
frameworks/base/core/java/android/app/ContextImpl.java
2.3. ContentProviderClient中有appNotRespondingViaProvider呼叫,并且也有觸發邏輯,接下去深入進去看看,
2.3.1. 在ContentProviderClient中有NotRespondingRunnable,運行到這個Runnable的run就會呼叫appNotRespondingViaProvider,呼叫堆疊流程為:
NotRespondingRunnable.run --> ApplicationContentResolver.appNotRespondingViaProvider --> ActivityThread.appNotRespondingViaProvider --> ActivityManagerService.appNotRespondingViaProvider --> ContentProviderHelper.appNotRespondingViaProvider --> AnrHelper.appNotResponding
呼叫流程就回到了最開始產生ANR處,
2.3.2. ContentProviderClient與ContentResolver類似,都是提供給Client使用的,都可以用來獲取的資料,差別在于兩者的使用場景不同,
- 針對相同ContentProvider的多次呼叫,建議使用ContentProviderClient,但用完需要釋放,
- 針對不同ContentProvider的多次呼叫,建議使用ContentResolver,
2.3.3. 接下來看NotRespondingRunnable的觸發邏輯就能找到ANR的產生處了,在名為setDetectNotResponding的方法中找到了new NotRespondingRunnable(),并且ContentProviderClient中還提供了beforeRemote, afterRemote來安裝與拆除ANR定時炸彈,看樣子是這兒了,繼續看setDetectNotResponding方法,這是一個@SystemApi,并且不開放給第三方App使用(通過反射與Hook機制作業系統行為不在本文討論范圍內),
2.4. 通過如上分析,所有常規三方App可達的ContentProvider ANR可行路徑均已被堵死,也就是App自己的ContentProvider是不會產生ANR的,
總結:在Android 11版本中,ContentProvider有好幾處ANR觸發邏輯,但均已被堵死,也就是App的ContentProvider是不會觸發產生ANR的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/355360.html
標籤:其他
