Android廣播事件流程與廣播ANR原理深入刨析
序言
本想寫廣播流程中ANR是如何觸發(fā)的,但是如果想講清楚ANR的流程,就不得不提整個廣播事件的流程,所以就把兩塊內(nèi)容合并在一起講了。
本文會講內(nèi)容如下:
1.動態(tài)注冊廣播的整個分發(fā)流程,從廣播發(fā)出,一直到廣播注冊者接收。
2.廣播類型ANR的判斷流程和原理。
PS:本文基于android13的源碼進(jìn)行講解。
一.基本流程和概念
動態(tài)廣播的流程其實是很簡單的,整套流程都在java層執(zhí)行,不涉及到native流程。
我們先來看一下最基本的流程圖:
首先是廣播注冊者,向AMS注冊廣播接收器,AMS交給BroadcastQueue來處理。也就是說BroadcastQueue中裝載所有的廣播接收器。然后廣播發(fā)出者向AMS發(fā)出廣播,這時候AMS仍然會交給BroadcastQueue來處理,在其中找到符合條件的接受者,然后向接受者發(fā)出廣播的通知。
然后我們再來了解一些最基本類的功能
ContextImp:Context的最終實現(xiàn)類,activity,application中的各種功能最終都是委托給其來處理的,廣播功能自然也不例外。
ActivityManagerService:負(fù)責(zé)所有應(yīng)用的Activity的流程,包括廣播六層呢。至于為什么廣播也由其來進(jìn)行處理,而不是搞一個BroadcastManagerService,估計是覺得廣播功能簡單不需要單獨(dú)設(shè)計Service吧。
BroadCastQueue:裝載所有的動態(tài)廣播接收器,靜態(tài)廣播也是由其來負(fù)責(zé)加載的,負(fù)責(zé)具體廣播的分發(fā)工作。一種有三種類型,前臺,后臺,離線。
BroadcastReceiver:廣播注冊者
二.無序廣播流程
首先我們了解下什么是無需廣播和有序廣播。簡單來說,有序廣播就是需要嚴(yán)格按照優(yōu)先級,依次的執(zhí)行接收動作,高優(yōu)先級的廣播接受者如果沒有處理完,低優(yōu)先級的廣播接受者是無法收到廣播的。無需廣播就是沒有順序,各個廣播接受者之間沒有相互依賴關(guān)系。
無需廣播,發(fā)送是sendBroadcast方法發(fā)送廣播通知。
有序廣播,需要使用sendOrderedBroadcast方法發(fā)送廣播通知。
無序廣播大體流程圖如下:
注冊廣播接收者流程
1.APP側(cè),無論activity還是application,還是service,最終都是交給Context的實現(xiàn)類ContentImpl進(jìn)行最終的處理的。所以注冊廣播的時候,最終調(diào)用的是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不為空,并且是無序廣播,則根據(jù)intent找到所對應(yīng)的BroadcastQueue(根據(jù)前臺,后臺,離線的類型)。
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所對應(yīng)的廣播事件,然后加入到BroadcastQueue的mParallelBroadcasts集合中。然后通過scheduleBroadcastsLocked方法切換到主線程執(zhí)行。這里要強(qiáng)調(diào)的是,無論是有序廣播還是無序廣播,都是通過這個方法來分發(fā)的。最終主線程會調(diào)用到processNextBroadcast方法。代碼在上面5,6行。
6.processNextBroadcast方法中邏輯很多,我們這里先只講無序廣播的流程。上一步中,我們把BroadcastRecord加入到了mParallelBroadcasts中,所以這里發(fā)送廣播的時候,直接遍歷mParallelBroadcasts集合,然后通知接受者接收。具體的流程可以看流程圖,這里就不細(xì)講了。
具體代碼如下:
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);//分發(fā)給接受者 } addBroadcastToHistoryLocked(r); }
三.有序廣播流程
有序廣播流程和上面無序廣播是類似的,最終請求AMS的方法其實也是broadcastIntentWithFeature方法,兩者只是參數(shù)有區(qū)別而已。
我們?nèi)匀话凑兆詮V播接受者和發(fā)送廣播兩個流程來講,首先是注冊廣播。
注冊廣播接收者流程
注冊的流程和無序廣播是一樣的。
廣播通知流程
1.前面的流程和無序廣播是一樣的,唯一的區(qū)別是進(jìn)入到AMS的broadcastIntentLocked方法后,執(zhí)行邏略微有區(qū)別。有序廣播,調(diào)用的是BroadcastQueue中的
enqueueOrderedBroadcastLocked方法,把BroadcastRecord對象最終注冊到BroadcastDispatcher中的mOrderedBroadcasts集合中,然后在調(diào)用scheduleBroadcastsLocked方法切換到主線程,執(zhí)行processNextBroadcastLocked的主流程。
下面介紹的都是processNextBroadcastLocked方法,也是廣播事件分發(fā)的的核心流程代碼:
2.processNextBroadcastLocked中,首先仍然去無序隊列mParallelBroadcasts中查,這時候肯定是沒有值的,所以跳過這個步驟。
3.然后通過mDispatcher.getNextBroadcastLocked(now);方法去BroadcastDispatcher中查找,方法實現(xiàn)如下,因為剛才應(yīng)添加到了mOrderedBroadcasts集合中,所以這時候可以找到并返回BroadcastRecord對象。
4.BroadcastRecord.nextReceiver用來記錄一系列有序廣播執(zhí)行到了第幾個,0代表開始,如果為0,則記錄一些必要信息。另外,會更新掉record的時間receiverTime,這一點(diǎn)很重要,下面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.發(fā)送一個延時廣播,延時時間具體看是前臺廣播還是后臺廣播定義的。如果已經(jīng)發(fā)送過并且未結(jié)束,則跳過。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類型,則把廣播事件發(fā)送給接受者。
if (nextReceiver instanceof BroadcastFilter) { BroadcastFilter filter = (BroadcastFilter)nextReceiver; deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx); }
至此,processNextBroadcastLocked的方法暫時介紹完成,即然用暫時,那就說明后面還會涉及到。
7.接收者是LoadedApk中ReceiverDispatcher的內(nèi)部類InnerReceiver,它是binder的服務(wù)端接收者,其performReceive方法收到通知后,會交給ReceiverDispatcher中的performReceive方法處理。
if (rd != null) { rd.performReceive(intent, resultCode, data, extras, ordered, sticky, sendingUser); }
所以,最終來處理廣播信息的方法是ReceiverDispatcher中的performReceive方法。其中部分代碼如下,getRunnable就是最終通知到我們注冊的廣播接受者的流程。如果getRunnable中的任務(wù)注冊不成功的話,會直接發(fā)送信號通知回AMS。什么情況下會注冊不成功呢?主線程Looper異常執(zhí)行完并退出時就會發(fā)生。
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中返回的任務(wù),我們?nèi)匀恢毁N核心代碼:
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(); }
我們可以看到,先執(zhí)行onReceive的回調(diào),這里就會最終通知到我們的BroadcastReceiver中的onReceive方法。
然后我們在看一下finish方法:這個其實核心就是發(fā)送廣播完成的信號給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,找到所對應(yīng)的BroadcastRecord,然后結(jié)束掉當(dāng)前的這個事件流程,如果后面還有事件,則繼續(xù)調(diào)用processNextBroadcastLocked方法,進(jìn)行下一輪的廣播事件分發(fā)。
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流程
那么什么時候會導(dǎo)致廣播ANR呢?這一套題,我估計能考倒90%的安卓開發(fā)者,單純的無序廣播,廣播接受者中sleep幾分鐘,是不會產(chǎn)生廣播類型ANR的,ANR只存在于有序廣播并且廣播接受者未及時響應(yīng)。
其實了解完整個廣播流程,我們ANR流程就好講的多。我們只需關(guān)注整個流程中的幾個點(diǎn)就好了。
觸發(fā)廣播監(jiān)聽者流程
1.首先第三章的第五小節(jié)中,我們講到,通過setBroadcastTimeoutLocked方法設(shè)置一個延時的消息,消息的執(zhí)行時間=當(dāng)前時間+超時時間,此時伴隨著廣播通知到客戶端的流程。
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; } }
也就是說,一旦達(dá)到超時時間,那么就會發(fā)送BROADCAST_TIMEOUT_MSG類型的事件,就一定會執(zhí)行broadcastTimeoutLocked方法。
2.broadcastTimeoutLocked方法中,會再一次進(jìn)行判斷,如果沒有到執(zhí)行時間,會重新觸發(fā)一次setBroadcastTimeoutLocked的流程。
上面3.8中講到,如果是有序廣播,廣播接收者收到消息后,會通過binder回調(diào)AMS通知事件處理完成,并重新進(jìn)入processNextBroadcastLocked流程進(jìn)行下一輪的分發(fā),這時候,會更新掉上面用到的receiverTime時間。
3.processNextBroadcastLocked分發(fā)廣播的時候,如果判斷進(jìn)入到了結(jié)束的環(huán)節(jié),會主動取消注冊BROADCAST_TIMEOUT_MSG類型的事件。
if (r.receivers == null || r.nextReceiver >= numReceivers || r.resultAbort || forceReceive) { ... cancelBroadcastTimeoutLocked(); }
一旦取消了超時類型消息的注冊,自然也不會走到ANR邏輯。
這里有一點(diǎn)繞,所以我舉個例子詳細(xì)解釋下,因為一開始我其實也被繞進(jìn)去了。
假設(shè)我們的超時時間為10S,有序廣播中有3個接收者,接收者1耗時5S,接收者2耗時6S,接收者3耗時4S,這時候,在接收者2處理中的時候,就會進(jìn)入到broadcastTimeoutLocked的流程。但是此時,由于接收者1執(zhí)行完其流程,所以更新了receiverTime時間,此時的超時時間變成5+10S,而當(dāng)前為第10S,所以并不會超時。第14S的時候接收者3也完成流程,通知回AMS,取消了超時類型消息的注冊,所以就不會再次走到broadcastTimeoutLocked的流程了。
所以,走到broadcastTimeoutLocked流程并不一定意味著就會發(fā)生ANR。我一開始就是被這個繞進(jìn)去了。
五.總結(jié)
所以整個廣播事件分發(fā)以及ANR觸發(fā)流程,我們可以用下圖來做一個總結(jié):圖片較大,建議另外開一個tab頁查看:
六.擴(kuò)展問題
1.有序和無序廣播是怎么區(qū)分的?
答:sendOrderedBroadcast和sendBroadcast兩個方法,其實最終broadcastIntentWithFeature方法中,就是一個參數(shù)不一樣,倒數(shù)第三個boolean serialized。sendOrderedBroadcast為true,sendBroadcast為false。
2.發(fā)送一個廣播,A進(jìn)程中接收,廣播接收者A中的onReceive方法中sleep100秒,是否一定會觸發(fā)ANR?如果是或者不是?原因是什么?如果我們把廣播改為有序廣播呢?
答:
只有有序廣播才會ANR,如果第一種情況如果是無序廣播,自然不會ANR。
第二個答案是一般情況下是會的,因為廣播接收者A中阻塞,導(dǎo)致AMS無法按時收到廣播完成的信號,從而會引起ANR。除非進(jìn)程A的主線程因為某種異常原因退出,不過這種情況下,onReceive方法自然也不會走到。
到此這篇關(guān)于Android廣播事件流程與廣播ANR原理深入刨析的文章就介紹到這了,更多相關(guān)Android廣播事件流程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實現(xiàn)基于滑動的SQLite數(shù)據(jù)分頁加載技術(shù)(附demo源碼下載)
這篇文章主要介紹了Android實現(xiàn)基于滑動的SQLite數(shù)據(jù)分頁加載技術(shù),涉及Android針對SQLite數(shù)據(jù)的讀取及查詢結(jié)果的分頁顯示功能相關(guān)實現(xiàn)技巧,末尾還附帶demo源碼供讀者下載參考,需要的朋友可以參考下2016-07-07Android 新手引導(dǎo)蒙層效果實現(xiàn)代碼示例
本篇文章主要介紹了Android 新手引導(dǎo)蒙層效果實現(xiàn)代碼示例,具有一定的參考價值,有興趣的可以了解一下。2017-01-01Android WebView使用方法詳解 附j(luò)s交互調(diào)用方法
這篇文章主要為大家詳細(xì)介紹了Android WebView使用方法詳解,文中附j(luò)s交互調(diào)用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-05-05Android 高版本API方法在低版本系統(tǒng)上的兼容性處理
本文主要介紹Android 高版本API方法在低版本系統(tǒng)上的兼容性處理的問題,這里提供了解決辦法,并附簡單示例,來詳細(xì)說明解決問題步驟,有需要的小伙伴可以參考下2016-09-09Android之自定義實現(xiàn)BaseAdapter(通用適配器一)
這篇文章主要為大家詳細(xì)介紹了Android之自定義實現(xiàn)BaseAdapter通用適配器第一篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-08-08Android?Flutter實現(xiàn)"斑馬紋"背景的示例代碼
本文將通過實現(xiàn)一個canvas繪制斑馬紋類。使用Stack布局,將斑馬紋放在下方作為背景板,需要展示的內(nèi)容在上方。從而實現(xiàn)?“斑馬紋”背景,感興趣的可以了解一下2022-06-06Android使用addView動態(tài)添加組件的方法
這篇文章主要為大家詳細(xì)介紹了Android使用addView動態(tài)添加組件的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-09-09Android原生TabLayout使用的超全解析(看這篇就夠了)
現(xiàn)在很多app都有頂部可左右切換的導(dǎo)航欄,并且還帶動畫效果,要實現(xiàn)這種導(dǎo)航欄,可以使用Android原生的Tablayout也可以借助第三方框架實現(xiàn),這篇文章主要給大家介紹了關(guān)于Android原生TabLayout使用的相關(guān)資料,需要的朋友可以參考下2022-09-09Android library native調(diào)試代碼遇到的問題解決
這篇文章主要介紹了Android library native 代碼不能調(diào)試解決方法匯總,android native開發(fā)會碰到native代碼無法調(diào)試問題,而app主工程中的native代碼是可以調(diào)試的2023-04-04