Android Broadcast

本文基于 AOSP android-9.0.0_r2

Android中的廣播機制是通過底層binder來傳遞Intent的跨進程通信方式。它是1-N的機制,即一個廣播端可以與N個Receiver對應。

發送端為信息源, 它將一些信息封裝到Intent中,然后廣播出去。
接收端為信息消費端,它向系統注冊廣播Receiver(BroadcastReceiver與IntentFilter),當有對應(IntentFilter)廣播發生時,Receiver就會收到廣播并開始處理相應的信息。

Android的廣播機制涉及如下幾個方面

一. Receiver

1.1 靜態注冊

通過在AndroidManifest.xml里聲明注冊的廣播稱為靜態Receiver.

       <receiver
           android:name=".MyReceiver"
           android:enabled="true"
           android:exported="true">
           <intent-filter>
               <action android:name="me.bobby.test2.MyReceiver"/>
           </intent-filter>
       </receiver>

如上所示,向系統注冊一個MyReceiver的廣播Receiver,對應的IntentFilter中的action為me.bobby.test2.MyReceiver.
那么靜態注冊的Receiver是怎樣注冊到系統里的呢?
當安裝App或系統啟動時,PMS會對Apk中的AndroidManifest.xml進行解析。具體的代碼在PackageParser.java中的parseBaseApplication函數中

if (tagName.equals("receiver")) {
    Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, true, false);
    hasReceiverOrder |= (a.order != 0);
    owner.receivers.add(a);
}
靜態Receiver

1.2 動態注冊

ContextImpl.registerReceiver(BroadcastReceiver receiver, IntentFilter filter)

通過Activity里直接調用registerReceiver去注冊一個動態的廣播Receiver。


動態注冊廣播Receiver

1.3 小結

從靜態廣播Receiver與動態廣播Receiver可以看出,
靜態廣播Receiver,并沒有刻意去注冊,只是由PMS在解析Apk時,將申明在AndroidManifest.xml里的廣播解析出來,并保存在PMS里僅此而已。
動態廣播Receiver,會在Activity的Context中直接調用接口往AMS中注冊,可以看出來BroadcastReceiver是直接注冊到LoadedApk中的mReceivers中的.

mReceivers的定義
private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
mReceivers是一個ArrayMap, key為Context, 也就是一個Activity/Service/Provider這樣的實例, 它的value又是另一個ArrayMap, 這里的意思是一個Context其實是允許注冊多個BroadcastReceiver.

二. 發送端

2.1 發送廣播,找到match的Receiver,并加入到廣播隊列中

發送廣播流程, 主要是在AMS的broadcastIntentLocked中

  • 1. 排除stopped的package
        intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
  • 2. 檢查是否是protected的廣播
        final boolean isProtectedBroadcast;
        isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
        if (!isCallerSystem) {
            if (isProtectedBroadcast) {
                throw new SecurityException(msg);
            } 

從PMS中查找該action是否是protected的廣播,如果是非系統App發出的protected廣播,直接拋出異常。
那什么是protected的廣播呢?
protected 廣播是由系統App在AndroidManifest.xml中定義的,比如 framework-res.apk里面定義了大多數protected broadcast.
而一般的apk可以申明protected broadcast么?答案是非系統apk可以定義protected廣播,但是不會有任何作用,盡管PMS能正常解析, 但是在applyPolicy中發現它非系統apk, 會重置為null.

  • 3. sticky的廣播
    見后面第三大節。

  • 4. 是否針對動態注冊的Receiver

        List receivers = null;
        List<BroadcastFilter> registeredReceivers = null;
        // Need to resolve the intent to interested receivers...
        if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)== 0) {
            receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
        }

如果Intent設置了FLAG_RECEIVER_REGISTERED_ONLY標志,表明該intent只會去找動態注冊的Receiver。 否則會查找靜態注冊和動態注冊的兩種Receiver。
其中 receivers list中存放的是靜態注冊的Receiver, collectReceiverComponents通過PMS的queryIntentReceivers去查詢靜態Receiver,說到底就是從1.1小節所構成的類圖中去查找。這個就不細說了。

而registeredReceivers里存放的是動態注冊的Receiver,也就是接下來所說的。

  • 5. 查找動態注冊的Receiver
        if (intent.getComponent() == null) {
            if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
                // Query one target user at a time, excluding shell-restricted users
                for (int i = 0; i < users.length; i++) {
                    ...
                    List<BroadcastFilter> registeredReceiversForUser =
                            mReceiverResolver.queryIntent(intent, resolvedType, false /*defaultOnly*/, users[i]);
                    if (registeredReceivers == null) {
                        registeredReceivers = registeredReceiversForUser;
                    } else if (registeredReceiversForUser != null) {
                        registeredReceivers.addAll(registeredReceiversForUser);
                    }
                }
            } else {
                registeredReceivers = mReceiverResolver.queryIntent(intent,
                        resolvedType, false /*defaultOnly*/, userId);
            }
        }

intent.getComponent()如果不為空時,則表明intent意圖很明顯,就是找某個特定的component, 所以如果它不為空時,則會依次遍歷去查找。其實說到底,就是從1.2小節所構成的類圖中查詢。查詢出來的廣播receiver放在registeredReceivers中。

  • 6. enqueue無序的動態注冊的廣播
        final boolean replacePending =
                (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
       int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
        if (!ordered && NR > 0) {
            ...
            final BroadcastQueue queue = broadcastQueueForIntent(intent);
            BroadcastRecord r = new BroadcastRecord(...);
            final boolean replaced = replacePending && (queue.replaceParallelBroadcastLocked(r) != null);
            if (!replaced) {
                queue.enqueueParallelBroadcastLocked(r);
                queue.scheduleBroadcastsLocked();
            }
            registeredReceivers = null;
            NR = 0;
        }

上面這代碼針對的是動態注冊的Receiver。
如果設置FLAG_RECEIVER_REPLACE_PENDING,且之前已經有相同的廣播,則只需要替換即可,不需要重新入廣播隊列。 否則就將該廣播加入到mParallelBroadcasts中去, 這個是無序廣播。

  • 7合并動態和靜態Receiver
        // Merge into one list.
        int ir = 0;
        if (receivers != null) {
            // this decision.
            String skipPackages[] = null;
             ...
            int NT = receivers != null ? receivers.size() : 0;
            int it = 0;
            ResolveInfo curt = null;
            BroadcastFilter curr = null;
            while (it < NT && ir < NR) {
                if (curt == null) {
                    curt = (ResolveInfo)receivers.get(it);
                }
                if (curr == null) {
                    curr = registeredReceivers.get(ir);
                }
                if (curr.getPriority() >= curt.priority) {
                    // Insert this broadcast record into the final list.
                    receivers.add(it, curr);
                    ir++;
                    curr = null;
                    it++;
                    NT++;
                } else {
                    // Skip to the next ResolveInfo in the final list.
                    it++;
                    curt = null;
                }
            }
        }
        while (ir < NR) {
            if (receivers == null) {
                receivers = new ArrayList();
            }
            receivers.add(registeredReceivers.get(ir));
            ir++;
        }

上面的代碼根據Receiver的優先級,合并成一個 receivers.

  • 8 將最后的廣播依次加入到有序廣播隊列中
       if ((receivers != null && receivers.size() > 0)
                || resultTo != null) {
            BroadcastQueue queue = broadcastQueueForIntent(intent);
            BroadcastRecord r = new BroadcastRecord( ... );
            final BroadcastRecord oldRecord =
                    replacePending ? queue.replaceOrderedBroadcastLocked(r) : null;
            if (oldRecord != null) {
                // 替換了,fire CANCELED
                if (oldRecord.resultTo != null) {
                    final BroadcastQueue oldQueue = broadcastQueueForIntent(oldRecord.intent);
                    try {
                        oldQueue.performReceiveLocked(oldRecord.callerApp, oldRecord.resultTo,
                                oldRecord.intent, Activity.RESULT_CANCELED, null, null,  false, false, oldRecord.userId);
                    } catch (RemoteException e) {
                    }
                }
            } else {
                //加入到有序廣播隊列中
                queue.enqueueOrderedBroadcastLocked(r);
                queue.scheduleBroadcastsLocked();
            }
        } else {
            ...
        }
  • 9 小結

broadcastQueue

如圖所示,
如果是無序廣播。
那么動態注冊的廣播receiver全部會加入到 mParallelBroadcasts中,而靜態注冊的廣播receiver會按優先級保存到mOrderedBroadcasts中。
而如果是有序廣播
那么靜態注冊的receiver和動態注冊的receiver會根據優先級都放入到mOrderedBroadcasts中。

2.2 處理隊列

如上面broadcastQueue所示.
廣播隊列分為兩種,

  • mFgBroadcastQueue隊列
    從全名來看,這是一個foreground的隊列,它的timeout時間是10s, 不允許delay broadcast
  • mBgBroadcastQueue隊列
    這是一個background的隊列,timeout時間是60s,允許delay broadcast

而每個廣播隊列中又包含有序廣播列表和并發廣播列表。

廣播發送在system的binder線程中,但是真正處理廣播的是在ServiceThread線程中。

廣播有兩種,那如何選擇正確的廣播隊列的呢?

    BroadcastQueue broadcastQueueForIntent(Intent intent) {
        final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
        return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
    }

如果Intent里設置了FLAG_RECEIVER_FOREGROUND,則使用mFgBroadcastQueue, 否則使用mBgBroadcastQueue, 當設置FLAG_RECEIVER_FOREGROUND,則接收端的允許有foreground優先級。

然后根據是無序廣播

                queue.enqueueParallelBroadcastLocked(r);
                queue.scheduleBroadcastsLocked();

有序廣播

                queue.enqueueOrderedBroadcastLocked(r);
                queue.scheduleBroadcastsLocked();

進行調度。

binder線程一般會調用 scheduleBroadcastsLocked 向servicethread線程發送BROADCAST_INTENT_MSG, 然后開始進程處理廣播的流程。

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BROADCAST_INTENT_MSG: {
                    processNextBroadcast(true);
                } break;
                case BROADCAST_TIMEOUT_MSG: {
                    synchronized (mService) {
                        broadcastTimeoutLocked(true);
                    }
                } break;
            }
        }

下面來看下processNextBroadcast函數

  • 1. mParallelBroadcasts里保存的是無序的廣播,此時會將無序的廣播依次deliver給廣播receiver
        // First, deliver any non-serialized broadcasts right away.
        while (mParallelBroadcasts.size() > 0) {
            r = mParallelBroadcasts.remove(0);
            r.dispatchTime = SystemClock.uptimeMillis(); //記錄當前dispatch時間
            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);
        }
  • 2. 針對 mPendingBroadcast
    檢查是否有當前還有Broadcast沒有處理完,如果正在處理廣播的Receiver還沒有處理完,則等著它處理完。
        if (mPendingBroadcast != null) {
            boolean isDead;
            if (mPendingBroadcast.curApp.pid > 0) {
                synchronized (mService.mPidsSelfLocked) {
                    ProcessRecord proc = mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.pid);
                    isDead = proc == null || proc.crashing;
                }
            } else {
                final ProcessRecord proc = mService.mProcessNames.get(
                        mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
                isDead = proc == null || !proc.pendingStart;
            }
            if (!isDead) {
                // It's still alive, so keep waiting
                return;
            } else {
                mPendingBroadcast.state = BroadcastRecord.IDLE;
                mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
                mPendingBroadcast = null;
            }
        }
  • 3. 遍歷有序廣播, 處理第一個廣播
 do {
            if (mOrderedBroadcasts.size() == 0) {
                mService.scheduleAppGcsLocked();
                if (looped) {
                    mService.updateOomAdjLocked();
                }
                //已經沒有廣播了,直接return
                return;
            }
            r = mOrderedBroadcasts.get(0);
            boolean forceReceive = false;

            //當前廣播上掛著多少個廣播receiver, 下面檢查broadcast是否掛住了。
            int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
            if (mService.mProcessesReady && r.dispatchTime > 0) {
                long now = SystemClock.uptimeMillis();
                if ((numReceivers > 0) &&
                        (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
                    //如果broadcast消耗時間大于了2*mTimeoutPeriod*numReceivers,則強制finish.
                    //可以看出超時時間和當前掛在廣播上的receiver個數有關。
                    broadcastTimeoutLocked(false); // forcibly finish this broadcast
                    forceReceive = true;
                    r.state = BroadcastRecord.IDLE;
                }
            }

           //如果當前廣播狀態不是IDLE的話,說明當前正有廣播還在處理,直接返回。
            if (r.state != BroadcastRecord.IDLE) {
                return;
            }

            //當前廣播已經沒有等待接收的receiver了,通過廣播發送端,
            // 當前廣播被Receiver中止了,后面的Receiver不會再接收到該廣播了。
            if (r.receivers == null || r.nextReceiver >= numReceivers
                    || r.resultAbort || forceReceive) {
                // No more receivers for this broadcast!  Send the final
                // result if requested...
                if (r.resultTo != null) {
                    try {
                        performReceiveLocked(r.callerApp, r.resultTo,
                            new Intent(r.intent), r.resultCode,
                            r.resultData, r.resultExtras, false, false, r.userId);
                        r.resultTo = null;
                    } catch (RemoteException e) {
                    }
                }
                ...
            }
        } while (r == null);

上面代碼有一處非常重要, 即r.resultAbort, 如果當前正在處理廣播的Receiver調用了abortBroadcast了,則該有序廣播就不會再繼續發了,直接中斷,進入下一個廣播處理。

  • 4. 處理廣播
    經過第3步后,找到了即將要處理的廣播
        // Get the next receiver...
        int recIdx = r.nextReceiver++;  //當前要處理的廣播receiver 索引

        r.receiverTime = SystemClock.uptimeMillis();
        if (recIdx == 0) { //如果處理的是該廣播的第一個receiver, 則記錄它的dispatch時間。
            r.dispatchTime = r.receiverTime;
            r.dispatchClockTime = System.currentTimeMillis();
        }

r.nextReceiver是指下一次要處理的廣播receiver, 如上面broadcastQueue所示,每一個廣播都會掛著一個list的receiver, 這時,nextReceiver就用來指明下一次處理哪一個。

        final BroadcastOptions brOptions = r.options;
        final Object nextReceiver = r.receivers.get(recIdx);

        if (nextReceiver instanceof BroadcastFilter) {
            BroadcastFilter filter = (BroadcastFilter)nextReceiver;
            deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
            ...
            return;
        }

由broadcastQueue圖所示,有序廣播隊列會掛著靜態注冊和動態注冊的廣播receiver. 而動態廣播receiver, 對應的是BroadcastFilter, 所以這里如果是動態receiver, 則直接deliver給動態receiver. 而沒有直接deliver給靜態receiver, 說明后面會對靜態的receiver有更多的限制

  • 5. 靜態廣播receiver
        // Hard case: need to instantiate the receiver, possibly
        // starting its application process to host it.
        // 由注釋可知,下面針對的是靜態廣播receiver, 有可能會啟動靜態廣播的進程
    ResolveInfo info = (ResolveInfo)nextReceiver;
    ComponentName component = new ComponentName( info.activityInfo.applicationInfo.packageName, info.activityInfo.name);

獲得靜態廣播receiver組件信息
然后根據receiver的信息來決定是否將broadcast deliver給該receiver

  • 6. receiver權限檢查

代碼很長,就不帖了。比如Receiver中的Permission是否granted, 是否exported等等,具體直接看代碼吧。

而下面這個檢查比較關鍵,因為它涉及到在android高版本中雖然注冊了靜態廣播receiver, 但是有廣播發生時,Receiver確收不到廣播的情況

        if (!skip) {
            final int allowed = mService.getAppStartModeLocked(
                    info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
                    info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
            if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
                    skip = true;
                } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
                        || (r.intent.getComponent() == null
                            && r.intent.getPackage() == null
                            && ((r.intent.getFlags()
                                    & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
                            && !isSignaturePerm(r.requiredPermissions))) {
                    mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
                            component.getPackageName());
                    skip = true;
                }
            }
        }

通過getAppStartModeLocked去獲得APP START模式,
getAppStartModeLocked會調用如下代碼,其中 alwaysRestrict = true

                final int startMode = (alwaysRestrict)
                        ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
                        : appServicesRestrictedInBackgroundLocked(uid, packageName,

在appRestrictedInBackgroundLocked函數中

    int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
        // Apps that target O+ are always subject to background check
        if (packageTargetSdk >= Build.VERSION_CODES.O) {
            return ActivityManager.APP_START_MODE_DELAYED_RIGID;
        }
    ...

如果當前Android版本是Oreo及以后,那返回是非APP_START_MODE_DELAYED_RIGID,
所以會進入else if, 從else if的條件可以看出。

設置FLAG_RECEIVER_EXCLUDE_BACKGROUND, 表明要排除靜態receiver.
如果intent沒有指定特定的component, package, 以及FLAG_RECEIVER_INCLUDE_BACKGROUND, 以及signature權限相關,也會排除靜態receiver.

這也就是在Android O后面,靜態廣播不會再收到一些系統廣播的原因了。

        if (skip) {
            r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
            r.receiver = null;
            r.curFilter = null;
            r.state = BroadcastRecord.IDLE;
            r.manifestSkipCount++;
            scheduleBroadcastsLocked();
            return;
        }

如果該廣播對于這些靜態的receiver最后是skip的話,直接return掉。


相反,如果Receiver可以接收的話,此時就會修改廣播狀態為APP_RECEIVE

        r.manifestCount++;
        r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
        r.state = BroadcastRecord.APP_RECEIVE; //修改該廣播的狀態為APP_RECEIVE.
        r.curComponent = component;
        r.curReceiver = info.activityInfo;

如果receiver所在的進程已經啟動了,調用processCurBroadcastLocked著手處理該廣播receiver

        // Is this receiver's application already running?
        if (app != null && app.thread != null && !app.killed) {
            try {
                app.addPackage(info.activityInfo.packageName,
                        info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);
                processCurBroadcastLocked(r, app, skipOomAdj);
                return;
            } 
            ...
        }

如果receiver所在的進程還沒有啟動,則先開啟該進程,

        if ((r.curApp=mService.startProcessLocked(...)) == null) {
            finishReceiverLocked(r, r.resultCode, r.resultData, r.resultExtras, r.resultAbort, false);
            scheduleBroadcastsLocked();
            r.state = BroadcastRecord.IDLE;
            return;
        }

2.2.1 AMS通知Receiver廣播到來

從上面2.2小節可知,deliverToRegisteredReceiverLocked將廣播deliver給動態注冊的廣播。它可以是在有序和無序廣播中被調用。

第一個階段依然是權限檢查,可以參考 2.2中第4步,基本一樣.

        if (ordered) { //針對有序廣播
            r.receiver = filter.receiverList.receiver.asBinder(); //找到receiver
            r.curFilter = filter;
            filter.receiverList.curBroadcast = r;
            r.state = BroadcastRecord.CALL_IN_RECEIVE;
            if (filter.receiverList.app != null) {
                r.curApp = filter.receiverList.app;
                filter.receiverList.app.curReceivers.add(r);
                mService.updateOomAdjLocked(r.curApp, true);
            }
        }
        try {
            if (filter.receiverList.app != null && filter.receiverList.app.inFullBackup) {
                if (ordered) {
                    skipReceiverLocked(r);
                }
            } else {
                performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
                        new Intent(r.intent), r.resultCode, r.resultData,
                        r.resultExtras, r.ordered, r.initialSticky, r.userId);
            }
            if (ordered) {
                r.state = BroadcastRecord.CALL_DONE_RECEIVE;
            }
        } catch (RemoteException e) {
            if (ordered) {
                r.receiver = null;
                r.curFilter = null;
                filter.receiverList.curBroadcast = null;
                if (filter.receiverList.app != null) {
                    filter.receiverList.app.curReceivers.remove(r);
                }
            }
        }
動態注冊廣播Receiver

可以參考上面動態注冊廣播的圖,deliverToRegisteredReceiverLocked會找到IIntenReceiver.Proxy,然后調用scheduleRegisteredReceiver去通知對應Receiver廣播發生了。

broadcast流程圖

2.2.2 有序廣播queue收到Receiver完成廣播處理

有序廣播的一個特點是,如果高優先級的Receiver處理了廣播,它有權abort該廣播,那后果是低優先級的Receiver就不會再接收到該廣播。

Receiver調用abortBroadcast

        public final void abortBroadcast() {
            mAbortBroadcast = true;
        }

并且Receiver會調用finishReceiver通知BroadcastQueue完成廣播處理

        public void sendFinished(IActivityManager am) {
            synchronized (this) {
                try {
                    if (mOrderedHint) {
                        am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
                                mAbortBroadcast, mFlags);
                    } else {
                        am.finishReceiver(mToken, 0, null, null, false, mFlags);
                    }
                } catch (RemoteException ex) {
                }
            }
        }

mOrderedHint表明該廣播是有序廣播,此時會向broadcastqueue傳遞相應的處理結果,以及mAbortBroadcast

AMS在收到finishReceiver后

public void finishReceiver(IBinder who, int resultCode, String resultData,
            Bundle resultExtras, boolean resultAbort, int flags) {
        final long origId = Binder.clearCallingIdentity();
        try {
            boolean doNext = false;
            BroadcastRecord r;

            synchronized(this) {
                BroadcastQueue queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0
                        ? mFgBroadcastQueue : mBgBroadcastQueue;
                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);
                }
                // updateOomAdjLocked() will be done here
                trimApplicationsLocked();
            }
    }

在finishReceiverLocked,會將r.resultAbort = resultAbort, 此時可以參見2.2小節第3步,如果廣播abort了,直接跳到下一個廣播cycle.

廣播處理cycle

三、sticky的廣播

sticky的廣播為粘性廣播,什么意思呢?
我們知道如果是一般的有序或是無序廣播,如果是廣播處理cycle中,所有的receiver已經處理完該廣播,則廣播隊列會將該條廣播給刪除掉,所以后面再注冊的Receiver是不會收到這條已經被刪除的廣播的。

而sticky廣播不一樣,如果廣播發送端指定一條廣播是sticky的廣播,則系統會一直記錄該sticky廣播,即使當時已經處理完sticky廣播的Receiver, sticky廣播也不會消失。當新注冊一個廣播receiver, AMS會立即向該receiver廣播該sticky廣播。

3.1 sticky廣播發送端

在broadcastIntentLocked函數中

            if (checkPermission(android.Manifest.permission.BROADCAST_STICKY, callingPid, callingUid)
                    != PackageManager.PERMISSION_GRANTED) {
                throw new SecurityException(msg);
            }
            if (requiredPermissions != null && requiredPermissions.length > 0) {
                return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
            }
            if (intent.getComponent() != null) {
                throw new SecurityException("Sticky broadcasts can't target a specific component");
            }
            if (userId != UserHandle.USER_ALL) {
                ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(UserHandle.USER_ALL);
                if (stickies != null) {
                    ArrayList<Intent> list = stickies.get(intent.getAction());
                    if (list != null) {
                        int N = list.size();
                        int i;
                        for (i=0; i<N; i++) {
                            if (intent.filterEquals(list.get(i))) {
                                throw new IllegalArgumentException( "Sticky broadcast " + intent + " for user "
                                        + userId + " conflicts with existing global broadcast");
                            }
                        }
                    }
                }
            }

上面的代碼表示 sticky的廣播

  • 獲得 android.permission.BROADCAST_STICKY
  • sticky廣播不能 enforce 權限
  • sticky廣播不能作用在某一個特殊的component上
  • sticky廣播如果不是針對所有用戶的話,會檢查它是否和當前sticky的廣播發生沖突

如果sticky廣播通過了上面的檢查,那就將sticky的廣播保存到 mStickyBroadcasts中

            ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
            if (stickies == null) {
                stickies = new ArrayMap<>();
                mStickyBroadcasts.put(userId, stickies);
            }
            ArrayList<Intent> list = stickies.get(intent.getAction());
            if (list == null) {
                list = new ArrayList<>();
                stickies.put(intent.getAction(), list);
            }
            final int stickiesCount = list.size();
            int i;
            for (i = 0; i < stickiesCount; i++) {
                if (intent.filterEquals(list.get(i))) {
                    // This sticky already exists, replace it.
                    list.set(i, new Intent(intent));
                    break;
                }
            }
            if (i >= stickiesCount) {
                list.add(new Intent(intent));
            }

3.2 sticky廣播Receiver

    public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId,
            int flags) {
            ...
            while (actions.hasNext()) {
                String action = actions.next();
                for (int id : userIds) {
                    ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id);
                    if (stickies != null) {
                        ArrayList<Intent> intents = stickies.get(action);
                        if (intents != null) {
                            if (stickyIntents == null) {
                                stickyIntents = new ArrayList<Intent>();
                            }
                            stickyIntents.addAll(intents);
                        }
                    }
                }
            }
        }

        ArrayList<Intent> allSticky = null;
        if (stickyIntents != null) {
            final ContentResolver resolver = mContext.getContentResolver();
            // Look for any matching sticky broadcasts...
            for (int i = 0, N = stickyIntents.size(); i < N; i++) {
                Intent intent = stickyIntents.get(i);
                if (filter.match(resolver, intent, true, TAG) >= 0) {
                    if (allSticky == null) {
                        allSticky = new ArrayList<Intent>();
                    }
                    allSticky.add(intent);
                }
            }
        }

看看剛注冊的Receiver是否可以接收sticky的廣播

            if (allSticky != null) {
                ArrayList receivers = new ArrayList();
                receivers.add(bf);

                final int stickyCount = allSticky.size();
                for (int i = 0; i < stickyCount; i++) {
                    Intent intent = allSticky.get(i);
                    BroadcastQueue queue = broadcastQueueForIntent(intent);
                    BroadcastRecord r = new BroadcastRecord(queue, intent, null,
                            null, -1, -1, false, null, null, OP_NONE, null, receivers,
                            null, 0, null, null, false, true, true, -1);
                    queue.enqueueParallelBroadcastLocked(r);
                    queue.scheduleBroadcastsLocked();
                }
            }

            return sticky;
        }
    }

將sticky的廣播加入到廣播隊列中,然后進入第二節的廣播處理cycle. Receiver就會收到該sticky的廣播了。

四、廣播 ANR

從有序廣播的流程可以看出,有序廣播是一個一個的將廣播deliver給廣播Receiver. 如果Receiver處理廣播的時間過長、或因為代碼錯誤導致死循環了, 那后面的Receiver將不會再得到調用了。這是系統不愿看到的, 所以應該會有個超時的機制。
從第2.2小節可以看出,兩種廣播隊列在初始化時,它們都有一個timeout時間,也就是超時時間,如果一個廣播Receiver超過了timeout時間還沒有處理完,此時廣播隊列就會觸發超時機制觸發ANR

在 processNextBroadcastLocked 找到要處理的有序廣播后,也就意味著要deliver了,在deliver之前,會設置超時時間,

        r.receiverTime = SystemClock.uptimeMillis();
        ...
        if (! mPendingBroadcastTimeoutMessage) {
            long timeoutTime = r.receiverTime + mTimeoutPeriod;
            setBroadcastTimeoutLocked(timeoutTime);
        }
    final void setBroadcastTimeoutLocked(long timeoutTime) {
        if (! mPendingBroadcastTimeoutMessage) {
            Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
            mHandler.sendMessageAtTime(msg, timeoutTime);
            mPendingBroadcastTimeoutMessage = true;
        }
    }

使用的機制是handler在mTimeoutPeriod后發送一條BROADCAST_TIMEOUT_MSG信息,如果在超時之前完成了,則會cancelBroadcastTimeoutLocked cancel掉該信息。

如果超時后會調用


    final void broadcastTimeoutLocked(boolean fromMsg) {
        ...
        //調度下一個receiver
        // Move on to the next receiver.
        finishReceiverLocked(r, r.resultCode, r.resultData,
                r.resultExtras, r.resultAbort, false);
        scheduleBroadcastsLocked();

        //觸發ANR
        if (!debugging && anrMessage != null) {
            mHandler.post(new AppNotResponding(app, anrMessage));
        }
    }

五、小結

Android的廣播Receiver注冊方式包括

  • 靜態注冊
  • 動態注冊

Android的廣播發送分為

  • 無序廣播 - sendBroadcast

  • 有序廣播 - sendOrderedBroadcast
    有序廣播會根據Receiver的優先級依次調度Receiver, 如果高優先級的Receiver完成廣播的處理后,調用了abortBroadcast, 則低優先級的Receiver將不會再收到該Broadcast了。

  • sticky的廣播 - sendStickyBroadcast
    需要獲得BROADCAST_STICKY權限,且sticky廣播不能指定一個特定的component上。
    只有新注冊的動態廣播receiver才能接收到sticky的廣播,靜態receiver不能收到sticky的廣播。見2.2第6步分析。


AMS提供了兩種廣播隊列,一種是background,一種是foreground的隊列。根據廣播中intent中的flag,即是否設置FLAG_RECEIVER_FOREGROUND來決定是將broadcast放入哪個隊列中。
如果是foreground隊列中的廣播,這會提升廣播receiver的優先級到foreground, 至于是怎么提升的,請參考Android Low memory killer

computeOomAdjLocked 函數中
else if (isReceivingBroadcastLocked(app, mTmpBroadcastQueue)) {
            adj = ProcessList.FOREGROUND_APP_ADJ;
            schedGroup = (mTmpBroadcastQueue.contains(mFgBroadcastQueue))
                    ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
            app.adjType = "broadcast";
            procState = ActivityManager.PROCESS_STATE_RECEIVER;
        }

isReceivingBroadcastLocked計算當前app的正在running的receivers是在哪些廣播隊列中(也就兩種,fg和bg)調度的,并放在mTmpBroadcastQueue中。 如果其中有從mFgBroadcastQueue調度的話,它的schedGroup會提升至SCHED_GROUP_DEFAULT , 否則SCHED_GROUP_BACKGROUND.


廣播ANR,廣播隊列中針對有序廣播會做超時處理,如果一個Receiver在timeout時間內還沒有處理完,此時會觸發ANR.

foreground的超時是10s
background的超時是60s.

還有一種本地廣播.
它其實就是一種進程內通信的方式,它不會與System進程通信,這樣就保證它更快,更安全。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,639評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內容