Android廣播事件流程與廣播ANR原理深入刨析
序言
本想寫廣播流程中ANR是如何觸發的,但是如果想講清楚ANR的流程,就不得不提整個廣播事件的流程,所以就把兩塊內容合並在一起講瞭。
本文會講內容如下:
1.動態註冊廣播的整個分發流程,從廣播發出,一直到廣播註冊者接收。
2.廣播類型ANR的判斷流程和原理。
PS:本文基於android13的源碼進行講解。
一.基本流程和概念
動態廣播的流程其實是很簡單的,整套流程都在java層執行,不涉及到native流程。
我們先來看一下最基本的流程圖:
首先是廣播註冊者,向AMS註冊廣播接收器,AMS交給BroadcastQueue來處理。也就是說BroadcastQueue中裝載所有的廣播接收器。然後廣播發出者向AMS發出廣播,這時候AMS仍然會交給BroadcastQueue來處理,在其中找到符合條件的接受者,然後向接受者發出廣播的通知。
然後我們再來瞭解一些最基本類的功能
ContextImp:Context的最終實現類,activity,application中的各種功能最終都是委托給其來處理的,廣播功能自然也不例外。
ActivityManagerService:負責所有應用的Activity的流程,包括廣播六層呢。至於為什麼廣播也由其來進行處理,而不是搞一個BroadcastManagerService,估計是覺得廣播功能簡單不需要單獨設計Service吧。
BroadCastQueue:裝載所有的動態廣播接收器,靜態廣播也是由其來負責加載的,負責具體廣播的分發工作。一種有三種類型,前臺,後臺,離線。
BroadcastReceiver:廣播註冊者
二.無序廣播流程
首先我們瞭解下什麼是無需廣播和有序廣播。簡單來說,有序廣播就是需要嚴格按照優先級,依次的執行接收動作,高優先級的廣播接受者如果沒有處理完,低優先級的廣播接受者是無法收到廣播的。無需廣播就是沒有順序,各個廣播接受者之間沒有相互依賴關系。
無需廣播,發送是sendBroadcast方法發送廣播通知。
有序廣播,需要使用sendOrderedBroadcast方法發送廣播通知。
無序廣播大體流程圖如下:
註冊廣播接收者流程
1.APP側,無論activity還是application,還是service,最終都是交給Context的實現類ContentImpl進行最終的處理的。所以註冊廣播的時候,最終調用的是ContentImpl的registerReceiver方法。
2.registerReceiver方法中通過binder,通知到AMS的registerReceiverWithFeature方法。
3.registerReceiverWithFeature方法中,首先做一些安全校驗。
4.除瞭action類型,還會有scheme的類型,這裡就不具體介紹瞭。
5.最終都會註冊到IntentResolver類型對象mReceiverResolver上。
廣播通知流程
1.仍然交由ContextImpl中的broadcastIntentWithFeature方法來處理。
2.broadcastIntentWithFeature通知到AMS的broadcastIntentLocked方法中。
3.首先從mReceiverResolver中查詢,看是否存在接受者,如果存在,加入到registeredReceivers集合中。
registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false /*defaultOnly*/, userId);
4.如果mReceiverResolver不為空,並且是無序廣播,則根據intent找到所對應的BroadcastQueue(根據前臺,後臺,離線的類型)。
if (!ordered && NR > 0) { final BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, ...); if (!replaced) { queue.enqueueParallelBroadcastLocked(r); queue.scheduleBroadcastsLocked(); } egisteredReceivers = null; NR = 0; }
5.生成BroadcastRecord對象用來記錄這次的action所對應的廣播事件,然後加入到BroadcastQueue的mParallelBroadcasts集合中。然後通過scheduleBroadcastsLocked方法切換到主線程執行。這裡要強調的是,無論是有序廣播還是無序廣播,都是通過這個方法來分發的。最終主線程會調用到processNextBroadcast方法。代碼在上面5,6行。
6.processNextBroadcast方法中邏輯很多,我們這裡先隻講無序廣播的流程。上一步中,我們把BroadcastRecord加入到瞭mParallelBroadcasts中,所以這裡發送廣播的時候,直接遍歷mParallelBroadcasts集合,然後通知接受者接收。具體的流程可以看流程圖,這裡就不細講瞭。
具體代碼如下:
while (mParallelBroadcasts.size() > 0) { r = mParallelBroadcasts.remove(0); r.dispatchTime = SystemClock.uptimeMillis(); r.dispatchClockTime = System.currentTimeMillis(); ... final int N = r.receivers.size(); for (int i=0; i<N; i++) { Object target = r.receivers.get(i); deliverToRegisteredReceiverLocked(r, (BroadcastFilter) target, false, i);//分發給接受者 } addBroadcastToHistoryLocked(r); }
三.有序廣播流程
有序廣播流程和上面無序廣播是類似的,最終請求AMS的方法其實也是broadcastIntentWithFeature方法,兩者隻是參數有區別而已。
我們仍然按照註冊廣播接受者和發送廣播兩個流程來講,首先是註冊廣播。
註冊廣播接收者流程
註冊的流程和無序廣播是一樣的。
廣播通知流程
1.前面的流程和無序廣播是一樣的,唯一的區別是進入到AMS的broadcastIntentLocked方法後,執行邏略微有區別。有序廣播,調用的是BroadcastQueue中的
enqueueOrderedBroadcastLocked方法,把BroadcastRecord對象最終註冊到BroadcastDispatcher中的mOrderedBroadcasts集合中,然後在調用scheduleBroadcastsLocked方法切換到主線程,執行processNextBroadcastLocked的主流程。
下面介紹的都是processNextBroadcastLocked方法,也是廣播事件分發的的核心流程代碼:
2.processNextBroadcastLocked中,首先仍然去無序隊列mParallelBroadcasts中查,這時候肯定是沒有值的,所以跳過這個步驟。
3.然後通過mDispatcher.getNextBroadcastLocked(now);方法去BroadcastDispatcher中查找,方法實現如下,因為剛才應添加到瞭mOrderedBroadcasts集合中,所以這時候可以找到並返回BroadcastRecord對象。
4.BroadcastRecord.nextReceiver用來記錄一系列有序廣播執行到瞭第幾個,0代表開始,如果為0,則記錄一些必要信息。另外,會更新掉record的時間receiverTime,這一點很重要,下面ANR流程中會有涉及到。
代碼如下:
int recIdx = r.nextReceiver++; r.receiverTime = SystemClock.uptimeMillis(); if (recIdx == 0) { r.dispatchTime = r.receiverTime; r.dispatchClockTime = System.currentTimeMillis(); if (mLogLatencyMetrics) { FrameworkStatsLog.write( FrameworkStatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED, r.dispatchClockTime - r.enqueueClockTime); } if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING), System.identityHashCode(r)); Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED), System.identityHashCode(r)); } if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast [" + mQueueName + "] " + r); }
5.發送一個延時廣播,延時時間具體看是前臺廣播還是後臺廣播定義的。如果已經發送過並且未結束,則跳過。setBroadcastTimeoutLocked中的具體邏輯,第四章ANR流程中會講到。
if (! mPendingBroadcastTimeoutMessage) { long timeoutTime = r.receiverTime + mConstants.TIMEOUT; if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Submitting BROADCAST_TIMEOUT_MSG [" + mQueueName + "] for " + r + " at " + timeoutTime); setBroadcastTimeoutLocked(timeoutTime); }
6.如果接受者是BroadcastFilter類型,則把廣播事件發送給接受者。
if (nextReceiver instanceof BroadcastFilter) { BroadcastFilter filter = (BroadcastFilter)nextReceiver; deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx); }
至此,processNextBroadcastLocked的方法暫時介紹完成,即然用暫時,那就說明後面還會涉及到。
7.接收者是LoadedApk中ReceiverDispatcher的內部類InnerReceiver,它是binder的服務端接收者,其performReceive方法收到通知後,會交給ReceiverDispatcher中的performReceive方法處理。
if (rd != null) { rd.performReceive(intent, resultCode, data, extras, ordered, sticky, sendingUser); }
所以,最終來處理廣播信息的方法是ReceiverDispatcher中的performReceive方法。其中部分代碼如下,getRunnable就是最終通知到我們註冊的廣播接受者的流程。如果getRunnable中的任務註冊不成功的話,會直接發送信號通知回AMS。什麼情況下會註冊不成功呢?主線程Looper異常執行完並退出時就會發生。
if (intent == null || !mActivityThread.post(args.getRunnable())) { if (mRegistered && ordered) { IActivityManager mgr = ActivityManager.getService(); if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing sync broadcast to " + mReceiver); args.sendFinished(mgr); } } }
我們在來看一下getRunnable中返回的任務,我們仍然隻貼核心代碼:
if (receiver == null || intent == null || mForgotten) { if (mRegistered && ordered) { if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing null broadcast to " + mReceiver); sendFinished(mgr); } return; } ... try { ... receiver.onReceive(mContext, intent); } catch (Exception e) { ... } if (receiver.getPendingResult() != null) { finish(); }
我們可以看到,先執行onReceive的回調,這裡就會最終通知到我們的BroadcastReceiver中的onReceive方法。
然後我們在看一下finish方法:這個其實核心就是發送廣播完成的信號給AMS:
public final void finish() { if (mType == TYPE_COMPONENT) { final IActivityManager mgr = ActivityManager.getService(); if (QueuedWork.hasPendingWork()) { .. QueuedWork.queue(new Runnable() { @Override public void run() { if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing broadcast after work to component " + mToken); sendFinished(mgr); } }, false); } else { if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing broadcast to component " + mToken); sendFinished(mgr); } } else if (mOrderedHint && mType != TYPE_UNREGISTERED) { if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing broadcast to " + mToken); final IActivityManager mgr = ActivityManager.getService(); sendFinished(mgr); } }
最終,在BroadcastReceiver.PendingResult的sendFinished方法中,通過binder通知回AMS。
am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras, mAbortBroadcast, mFlags);
8.AMS的finishReceiver中,主要功能是先通過binder,找到所對應的BroadcastRecord,然後結束掉當前的這個事件流程,如果後面還有事件,則繼續調用processNextBroadcastLocked方法,進行下一輪的廣播事件分發。
r = queue.getMatchingOrderedReceiver(who); if (r != null) { doNext = r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort, true); } if (doNext) { r.queue.processNextBroadcastLocked(/*fromMsg=*/ false, /*skipOomAdj=*/ true); }
四.廣播ANR流程
那麼什麼時候會導致廣播ANR呢?這一套題,我估計能考倒90%的安卓開發者,單純的無序廣播,廣播接受者中sleep幾分鐘,是不會產生廣播類型ANR的,ANR隻存在於有序廣播並且廣播接受者未及時響應。
其實瞭解完整個廣播流程,我們ANR流程就好講的多。我們隻需關註整個流程中的幾個點就好瞭。
觸發廣播監聽者流程
1.首先第三章的第五小節中,我們講到,通過setBroadcastTimeoutLocked方法設置一個延時的消息,消息的執行時間=當前時間+超時時間,此時伴隨著廣播通知到客戶端的流程。
long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
final void setBroadcastTimeoutLocked(long timeoutTime) { if (! mPendingBroadcastTimeoutMessage) { Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this); mHandler.sendMessageAtTime(msg, timeoutTime); mPendingBroadcastTimeoutMessage = true; } }
也就是說,一旦達到超時時間,那麼就會發送BROADCAST_TIMEOUT_MSG類型的事件,就一定會執行broadcastTimeoutLocked方法。
2.broadcastTimeoutLocked方法中,會再一次進行判斷,如果沒有到執行時間,會重新觸發一次setBroadcastTimeoutLocked的流程。
上面3.8中講到,如果是有序廣播,廣播接收者收到消息後,會通過binder回調AMS通知事件處理完成,並重新進入processNextBroadcastLocked流程進行下一輪的分發,這時候,會更新掉上面用到的receiverTime時間。
3.processNextBroadcastLocked分發廣播的時候,如果判斷進入到瞭結束的環節,會主動取消註冊BROADCAST_TIMEOUT_MSG類型的事件。
if (r.receivers == null || r.nextReceiver >= numReceivers || r.resultAbort || forceReceive) { ... cancelBroadcastTimeoutLocked(); }
一旦取消瞭超時類型消息的註冊,自然也不會走到ANR邏輯。
這裡有一點繞,所以我舉個例子詳細解釋下,因為一開始我其實也被繞進去瞭。
假設我們的超時時間為10S,有序廣播中有3個接收者,接收者1耗時5S,接收者2耗時6S,接收者3耗時4S,這時候,在接收者2處理中的時候,就會進入到broadcastTimeoutLocked的流程。但是此時,由於接收者1執行完其流程,所以更新瞭receiverTime時間,此時的超時時間變成5+10S,而當前為第10S,所以並不會超時。第14S的時候接收者3也完成流程,通知回AMS,取消瞭超時類型消息的註冊,所以就不會再次走到broadcastTimeoutLocked的流程瞭。
所以,走到broadcastTimeoutLocked流程並不一定意味著就會發生ANR。我一開始就是被這個繞進去瞭。
五.總結
所以整個廣播事件分發以及ANR觸發流程,我們可以用下圖來做一個總結:圖片較大,建議另外開一個tab頁查看:
六.擴展問題
1.有序和無序廣播是怎麼區分的?
答:sendOrderedBroadcast和sendBroadcast兩個方法,其實最終broadcastIntentWithFeature方法中,就是一個參數不一樣,倒數第三個boolean serialized。sendOrderedBroadcast為true,sendBroadcast為false。
2.發送一個廣播,A進程中接收,廣播接收者A中的onReceive方法中sleep100秒,是否一定會觸發ANR?如果是或者不是?原因是什麼?如果我們把廣播改為有序廣播呢?
答:
隻有有序廣播才會ANR,如果第一種情況如果是無序廣播,自然不會ANR。
第二個答案是一般情況下是會的,因為廣播接收者A中阻塞,導致AMS無法按時收到廣播完成的信號,從而會引起ANR。除非進程A的主線程因為某種異常原因退出,不過這種情況下,onReceive方法自然也不會走到。
到此這篇關於Android廣播事件流程與廣播ANR原理深入刨析的文章就介紹到這瞭,更多相關Android廣播事件流程內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Android Broadcast原理分析之registerReceiver詳解
- Android那兩個你碰不到但是很重要的類之ActivityThread
- Android性能優化之ANR問題定位分析
- Android13 加強Intent filters 的安全性
- 淺談Android ANR的信息收集過程