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!

推薦閱讀: