Android廣播事件流程與廣播ANR原理深入刨析
序言
本想寫(xiě)廣播流程中ANR是如何觸發(fā)的,但是如果想講清楚ANR的流程,就不得不提整個(gè)廣播事件的流程,所以就把兩塊內(nèi)容合并在一起講了。
本文會(huì)講內(nèi)容如下:
1.動(dòng)態(tài)注冊(cè)廣播的整個(gè)分發(fā)流程,從廣播發(fā)出,一直到廣播注冊(cè)者接收。
2.廣播類(lèi)型ANR的判斷流程和原理。
PS:本文基于android13的源碼進(jìn)行講解。
一.基本流程和概念
動(dòng)態(tài)廣播的流程其實(shí)是很簡(jiǎn)單的,整套流程都在java層執(zhí)行,不涉及到native流程。
我們先來(lái)看一下最基本的流程圖:

首先是廣播注冊(cè)者,向AMS注冊(cè)廣播接收器,AMS交給BroadcastQueue來(lái)處理。也就是說(shuō)BroadcastQueue中裝載所有的廣播接收器。然后廣播發(fā)出者向AMS發(fā)出廣播,這時(shí)候AMS仍然會(huì)交給BroadcastQueue來(lái)處理,在其中找到符合條件的接受者,然后向接受者發(fā)出廣播的通知。
然后我們?cè)賮?lái)了解一些最基本類(lèi)的功能
ContextImp:Context的最終實(shí)現(xiàn)類(lèi),activity,application中的各種功能最終都是委托給其來(lái)處理的,廣播功能自然也不例外。
ActivityManagerService:負(fù)責(zé)所有應(yīng)用的Activity的流程,包括廣播六層呢。至于為什么廣播也由其來(lái)進(jìn)行處理,而不是搞一個(gè)BroadcastManagerService,估計(jì)是覺(jué)得廣播功能簡(jiǎn)單不需要單獨(dú)設(shè)計(jì)Service吧。
BroadCastQueue:裝載所有的動(dòng)態(tài)廣播接收器,靜態(tài)廣播也是由其來(lái)負(fù)責(zé)加載的,負(fù)責(zé)具體廣播的分發(fā)工作。一種有三種類(lèi)型,前臺(tái),后臺(tái),離線。
BroadcastReceiver:廣播注冊(cè)者
二.無(wú)序廣播流程
首先我們了解下什么是無(wú)需廣播和有序廣播。簡(jiǎn)單來(lái)說(shuō),有序廣播就是需要嚴(yán)格按照優(yōu)先級(jí),依次的執(zhí)行接收動(dòng)作,高優(yōu)先級(jí)的廣播接受者如果沒(méi)有處理完,低優(yōu)先級(jí)的廣播接受者是無(wú)法收到廣播的。無(wú)需廣播就是沒(méi)有順序,各個(gè)廣播接受者之間沒(méi)有相互依賴(lài)關(guān)系。
無(wú)需廣播,發(fā)送是sendBroadcast方法發(fā)送廣播通知。
有序廣播,需要使用sendOrderedBroadcast方法發(fā)送廣播通知。
無(wú)序廣播大體流程圖如下:

注冊(cè)廣播接收者流程
1.APP側(cè),無(wú)論activity還是application,還是service,最終都是交給Context的實(shí)現(xiàn)類(lèi)ContentImpl進(jìn)行最終的處理的。所以注冊(cè)廣播的時(shí)候,最終調(diào)用的是ContentImpl的registerReceiver方法。
2.registerReceiver方法中通過(guò)binder,通知到AMS的registerReceiverWithFeature方法。
3.registerReceiverWithFeature方法中,首先做一些安全校驗(yàn)。
4.除了action類(lèi)型,還會(huì)有scheme的類(lèi)型,這里就不具體介紹了。
5.最終都會(huì)注冊(cè)到IntentResolver類(lèi)型對(duì)象mReceiverResolver上。
廣播通知流程
1.仍然交由ContextImpl中的broadcastIntentWithFeature方法來(lái)處理。
2.broadcastIntentWithFeature通知到AMS的broadcastIntentLocked方法中。
3.首先從mReceiverResolver中查詢(xún),看是否存在接受者,如果存在,加入到registeredReceivers集合中。
registeredReceivers = mReceiverResolver.queryIntent(intent,
resolvedType, false /*defaultOnly*/, userId);4.如果mReceiverResolver不為空,并且是無(wú)序廣播,則根據(jù)intent找到所對(duì)應(yīng)的BroadcastQueue(根據(jù)前臺(tái),后臺(tái),離線的類(lèi)型)。
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對(duì)象用來(lái)記錄這次的action所對(duì)應(yīng)的廣播事件,然后加入到BroadcastQueue的mParallelBroadcasts集合中。然后通過(guò)scheduleBroadcastsLocked方法切換到主線程執(zhí)行。這里要強(qiáng)調(diào)的是,無(wú)論是有序廣播還是無(wú)序廣播,都是通過(guò)這個(gè)方法來(lái)分發(fā)的。最終主線程會(huì)調(diào)用到processNextBroadcast方法。代碼在上面5,6行。
6.processNextBroadcast方法中邏輯很多,我們這里先只講無(wú)序廣播的流程。上一步中,我們把BroadcastRecord加入到了mParallelBroadcasts中,所以這里發(fā)送廣播的時(shí)候,直接遍歷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);
}三.有序廣播流程
有序廣播流程和上面無(wú)序廣播是類(lèi)似的,最終請(qǐng)求AMS的方法其實(shí)也是broadcastIntentWithFeature方法,兩者只是參數(shù)有區(qū)別而已。
我們?nèi)匀话凑兆?cè)廣播接受者和發(fā)送廣播兩個(gè)流程來(lái)講,首先是注冊(cè)廣播。
注冊(cè)廣播接收者流程
注冊(cè)的流程和無(wú)序廣播是一樣的。
廣播通知流程
1.前面的流程和無(wú)序廣播是一樣的,唯一的區(qū)別是進(jìn)入到AMS的broadcastIntentLocked方法后,執(zhí)行邏略微有區(qū)別。有序廣播,調(diào)用的是BroadcastQueue中的
enqueueOrderedBroadcastLocked方法,把BroadcastRecord對(duì)象最終注冊(cè)到BroadcastDispatcher中的mOrderedBroadcasts集合中,然后在調(diào)用scheduleBroadcastsLocked方法切換到主線程,執(zhí)行processNextBroadcastLocked的主流程。
下面介紹的都是processNextBroadcastLocked方法,也是廣播事件分發(fā)的的核心流程代碼:
2.processNextBroadcastLocked中,首先仍然去無(wú)序隊(duì)列mParallelBroadcasts中查,這時(shí)候肯定是沒(méi)有值的,所以跳過(guò)這個(gè)步驟。
3.然后通過(guò)mDispatcher.getNextBroadcastLocked(now);方法去BroadcastDispatcher中查找,方法實(shí)現(xiàn)如下,因?yàn)閯偛艖?yīng)添加到了mOrderedBroadcasts集合中,所以這時(shí)候可以找到并返回BroadcastRecord對(duì)象。
4.BroadcastRecord.nextReceiver用來(lái)記錄一系列有序廣播執(zhí)行到了第幾個(gè),0代表開(kāi)始,如果為0,則記錄一些必要信息。另外,會(huì)更新掉record的時(shí)間receiverTime,這一點(diǎn)很重要,下面ANR流程中會(huì)有涉及到。
代碼如下:
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ā)送一個(gè)延時(shí)廣播,延時(shí)時(shí)間具體看是前臺(tái)廣播還是后臺(tái)廣播定義的。如果已經(jīng)發(fā)送過(guò)并且未結(jié)束,則跳過(guò)。setBroadcastTimeoutLocked中的具體邏輯,第四章ANR流程中會(huì)講到。
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類(lèi)型,則把廣播事件發(fā)送給接受者。
if (nextReceiver instanceof BroadcastFilter) {
BroadcastFilter filter = (BroadcastFilter)nextReceiver;
deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
}至此,processNextBroadcastLocked的方法暫時(shí)介紹完成,即然用暫時(shí),那就說(shuō)明后面還會(huì)涉及到。
7.接收者是LoadedApk中ReceiverDispatcher的內(nèi)部類(lèi)InnerReceiver,它是binder的服務(wù)端接收者,其performReceive方法收到通知后,會(huì)交給ReceiverDispatcher中的performReceive方法處理。
if (rd != null) {
rd.performReceive(intent, resultCode, data, extras,
ordered, sticky, sendingUser);
}所以,最終來(lái)處理廣播信息的方法是ReceiverDispatcher中的performReceive方法。其中部分代碼如下,getRunnable就是最終通知到我們注冊(cè)的廣播接受者的流程。如果getRunnable中的任務(wù)注冊(cè)不成功的話,會(huì)直接發(fā)送信號(hào)通知回AMS。什么情況下會(huì)注冊(cè)不成功呢?主線程Looper異常執(zhí)行完并退出時(shí)就會(huì)發(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);
}
}
}我們?cè)趤?lái)看一下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),這里就會(huì)最終通知到我們的BroadcastReceiver中的onReceive方法。
然后我們?cè)诳匆幌耭inish方法:這個(gè)其實(shí)核心就是發(fā)送廣播完成的信號(hào)給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方法中,通過(guò)binder通知回AMS。
am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
mAbortBroadcast, mFlags);8.AMS的finishReceiver中,主要功能是先通過(guò)binder,找到所對(duì)應(yīng)的BroadcastRecord,然后結(jié)束掉當(dāng)前的這個(gè)事件流程,如果后面還有事件,則繼續(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流程
那么什么時(shí)候會(huì)導(dǎo)致廣播ANR呢?這一套題,我估計(jì)能考倒90%的安卓開(kāi)發(fā)者,單純的無(wú)序廣播,廣播接受者中sleep幾分鐘,是不會(huì)產(chǎn)生廣播類(lèi)型ANR的,ANR只存在于有序廣播并且廣播接受者未及時(shí)響應(yīng)。
其實(shí)了解完整個(gè)廣播流程,我們ANR流程就好講的多。我們只需關(guān)注整個(gè)流程中的幾個(gè)點(diǎn)就好了。
觸發(fā)廣播監(jiān)聽(tīng)者流程
1.首先第三章的第五小節(jié)中,我們講到,通過(guò)setBroadcastTimeoutLocked方法設(shè)置一個(gè)延時(shí)的消息,消息的執(zhí)行時(shí)間=當(dāng)前時(shí)間+超時(shí)時(shí)間,此時(shí)伴隨著廣播通知到客戶端的流程。
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;
}
}也就是說(shuō),一旦達(dá)到超時(shí)時(shí)間,那么就會(huì)發(fā)送BROADCAST_TIMEOUT_MSG類(lèi)型的事件,就一定會(huì)執(zhí)行broadcastTimeoutLocked方法。
2.broadcastTimeoutLocked方法中,會(huì)再一次進(jìn)行判斷,如果沒(méi)有到執(zhí)行時(shí)間,會(huì)重新觸發(fā)一次setBroadcastTimeoutLocked的流程。
上面3.8中講到,如果是有序廣播,廣播接收者收到消息后,會(huì)通過(guò)binder回調(diào)AMS通知事件處理完成,并重新進(jìn)入processNextBroadcastLocked流程進(jìn)行下一輪的分發(fā),這時(shí)候,會(huì)更新掉上面用到的receiverTime時(shí)間。
3.processNextBroadcastLocked分發(fā)廣播的時(shí)候,如果判斷進(jìn)入到了結(jié)束的環(huán)節(jié),會(huì)主動(dòng)取消注冊(cè)BROADCAST_TIMEOUT_MSG類(lèi)型的事件。
if (r.receivers == null || r.nextReceiver >= numReceivers
|| r.resultAbort || forceReceive) {
...
cancelBroadcastTimeoutLocked();
}一旦取消了超時(shí)類(lèi)型消息的注冊(cè),自然也不會(huì)走到ANR邏輯。
這里有一點(diǎn)繞,所以我舉個(gè)例子詳細(xì)解釋下,因?yàn)橐婚_(kāi)始我其實(shí)也被繞進(jìn)去了。
假設(shè)我們的超時(shí)時(shí)間為10S,有序廣播中有3個(gè)接收者,接收者1耗時(shí)5S,接收者2耗時(shí)6S,接收者3耗時(shí)4S,這時(shí)候,在接收者2處理中的時(shí)候,就會(huì)進(jìn)入到broadcastTimeoutLocked的流程。但是此時(shí),由于接收者1執(zhí)行完其流程,所以更新了receiverTime時(shí)間,此時(shí)的超時(shí)時(shí)間變成5+10S,而當(dāng)前為第10S,所以并不會(huì)超時(shí)。第14S的時(shí)候接收者3也完成流程,通知回AMS,取消了超時(shí)類(lèi)型消息的注冊(cè),所以就不會(huì)再次走到broadcastTimeoutLocked的流程了。
所以,走到broadcastTimeoutLocked流程并不一定意味著就會(huì)發(fā)生ANR。我一開(kāi)始就是被這個(gè)繞進(jìn)去了。
五.總結(jié)
所以整個(gè)廣播事件分發(fā)以及ANR觸發(fā)流程,我們可以用下圖來(lái)做一個(gè)總結(jié):圖片較大,建議另外開(kāi)一個(gè)tab頁(yè)查看:

六.擴(kuò)展問(wèn)題
1.有序和無(wú)序廣播是怎么區(qū)分的?
答:sendOrderedBroadcast和sendBroadcast兩個(gè)方法,其實(shí)最終broadcastIntentWithFeature方法中,就是一個(gè)參數(shù)不一樣,倒數(shù)第三個(gè)boolean serialized。sendOrderedBroadcast為true,sendBroadcast為false。
2.發(fā)送一個(gè)廣播,A進(jìn)程中接收,廣播接收者A中的onReceive方法中sleep100秒,是否一定會(huì)觸發(fā)ANR?如果是或者不是?原因是什么?如果我們把廣播改為有序廣播呢?
答:
只有有序廣播才會(huì)ANR,如果第一種情況如果是無(wú)序廣播,自然不會(huì)ANR。
第二個(gè)答案是一般情況下是會(huì)的,因?yàn)閺V播接收者A中阻塞,導(dǎo)致AMS無(wú)法按時(shí)收到廣播完成的信號(hào),從而會(huì)引起ANR。除非進(jìn)程A的主線程因?yàn)槟撤N異常原因退出,不過(guò)這種情況下,onReceive方法自然也不會(huì)走到。
到此這篇關(guān)于Android廣播事件流程與廣播ANR原理深入刨析的文章就介紹到這了,更多相關(guān)Android廣播事件流程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實(shí)現(xiàn)基于滑動(dòng)的SQLite數(shù)據(jù)分頁(yè)加載技術(shù)(附demo源碼下載)
這篇文章主要介紹了Android實(shí)現(xiàn)基于滑動(dòng)的SQLite數(shù)據(jù)分頁(yè)加載技術(shù),涉及Android針對(duì)SQLite數(shù)據(jù)的讀取及查詢(xún)結(jié)果的分頁(yè)顯示功能相關(guān)實(shí)現(xiàn)技巧,末尾還附帶demo源碼供讀者下載參考,需要的朋友可以參考下2016-07-07
Android 新手引導(dǎo)蒙層效果實(shí)現(xiàn)代碼示例
本篇文章主要介紹了Android 新手引導(dǎo)蒙層效果實(shí)現(xiàn)代碼示例,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01
Android WebView使用方法詳解 附j(luò)s交互調(diào)用方法
這篇文章主要為大家詳細(xì)介紹了Android WebView使用方法詳解,文中附j(luò)s交互調(diào)用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05
Android 高版本API方法在低版本系統(tǒng)上的兼容性處理
本文主要介紹Android 高版本API方法在低版本系統(tǒng)上的兼容性處理的問(wèn)題,這里提供了解決辦法,并附簡(jiǎn)單示例,來(lái)詳細(xì)說(shuō)明解決問(wèn)題步驟,有需要的小伙伴可以參考下2016-09-09
Android之自定義實(shí)現(xiàn)BaseAdapter(通用適配器一)
這篇文章主要為大家詳細(xì)介紹了Android之自定義實(shí)現(xiàn)BaseAdapter通用適配器第一篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08
Android?Flutter實(shí)現(xiàn)"斑馬紋"背景的示例代碼
本文將通過(guò)實(shí)現(xiàn)一個(gè)canvas繪制斑馬紋類(lèi)。使用Stack布局,將斑馬紋放在下方作為背景板,需要展示的內(nèi)容在上方。從而實(shí)現(xiàn)?“斑馬紋”背景,感興趣的可以了解一下2022-06-06
Android使用addView動(dòng)態(tài)添加組件的方法
這篇文章主要為大家詳細(xì)介紹了Android使用addView動(dòng)態(tài)添加組件的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
Android原生TabLayout使用的超全解析(看這篇就夠了)
現(xiàn)在很多app都有頂部可左右切換的導(dǎo)航欄,并且還帶動(dòng)畫(huà)效果,要實(shí)現(xiàn)這種導(dǎo)航欄,可以使用Android原生的Tablayout也可以借助第三方框架實(shí)現(xiàn),這篇文章主要給大家介紹了關(guān)于Android原生TabLayout使用的相關(guān)資料,需要的朋友可以參考下2022-09-09
Android library native調(diào)試代碼遇到的問(wèn)題解決
這篇文章主要介紹了Android library native 代碼不能調(diào)試解決方法匯總,android native開(kāi)發(fā)會(huì)碰到native代碼無(wú)法調(diào)試問(wèn)題,而app主工程中的native代碼是可以調(diào)試的2023-04-04

