There are several types of broadcast dispatches, but we’ll focus on unordered broadcast dispatches.

To send an unordered broadcast, we call the sendBroadcast method, which eventually calls the sendBroadcast method in ContextImpl.

ContextImpl to AMS

Sequence diagram:

Let’s look at the ContextImpl method first

@Override
public void sendBroadcast(Intent intent) {
    warnIfCallingFromSystemProcess();
    String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
    try {
        intent.prepareToLeaveProcess(this);
        ActivityManager.getService().broadcastIntent(
                mMainThread.getApplicationThread(), intent, resolvedType, null,
                Activity.RESULT_OK, null.null.null, AppOpsManager.OP_NONE, null.false.false,
                getUserId());
    } catch (RemoteException e) {
        throwe.rethrowFromSystemServer(); }}Copy the code

This is where AMS’s broadcastIntent method is eventually called.

public final int broadcastIntent(IApplicationThread caller,
        Intent intent, String resolvedType, IIntentReceiver resultTo,
        int resultCode, String resultData, Bundle resultExtras,
        String[] requiredPermissions, int appOp, Bundle bOptions,
        boolean serialized, boolean sticky, int userId) {
    enforceNotIsolatedCaller("broadcastIntent");
    synchronized(this){ intent = verifyBroadcastLocked(intent); final ProcessRecord callerApp = getRecordForAppLocked(caller); final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); int res = broadcastIntentLocked(callerApp, callerApp ! =null ? callerApp.info.packageName : null,
                intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
                requiredPermissions, appOp, bOptions, serialized, sticky,
                callingPid, callingUid, userId);
        Binder.restoreCallingIdentity(origId);
        returnres; }}Copy the code

In AMS’s broadcastIntent method, we first call verifyBroadcastLocked to check whether the broadcast is valid.

So let’s see how it’s detected, right

final Intent verifyBroadcastLocked(Intent intent) {
    // Determine if the intent has a descriptor
    if(intent ! =null && intent.hasFileDescriptors() == true) {
        throw new IllegalArgumentException("File descriptors passed in Intent");
    }
    // Get the flag set by the intent
    int flags = intent.getFlags();

    if(! mProcessesReady) {// If the system is booting, this method is entered
        
        If the intent is set to FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, no action is taken
        if((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) ! =0) {
            // This will be turned into a FLAG_RECEIVER_REGISTERED_ONLY later on if needed.
        } else if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
            // If the intent is not set to FLAG_RECEIVER_REGISTERED_ONLY, an error is reported.
            Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent
                    + " before boot completion");
            throw new IllegalStateException("Cannot broadcast before boot completed"); }}if((flags&Intent.FLAG_RECEIVER_BOOT_UPGRADE) ! =0) {
        throw new IllegalArgumentException(
                "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
    }

    if((flags & Intent.FLAG_RECEIVER_FROM_SHELL) ! =0) {
        switch (Binder.getCallingUid()) {
            case ROOT_UID:
            case SHELL_UID:
                break;
            default:
                Slog.w(TAG, "Removing FLAG_RECEIVER_FROM_SHELL because caller is UID "
                        + Binder.getCallingUid());
                intent.removeFlags(Intent.FLAG_RECEIVER_FROM_SHELL);
                break; }}return intent;
}
Copy the code

After checking, we call the broadcastIntentLocked method of the broadcastIntent

final int broadcastIntentLocked(ProcessRecord callerApp,
        String callerPackage, Intent intent, String resolvedType,
        IIntentReceiver resultTo, int resultCode, String resultData,
        Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
        boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {
...
int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
    if(! ordered && NR >0) {
        // If we are not serializing this broadcast, then send the
        // registered receivers separately so they don't wait for the
        // components to be launched.
        if (isCallerSystem) {
            checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
                    isProtectedBroadcast, registeredReceivers);
        }
        final BroadcastQueue queue = broadcastQueueForIntent(intent);
        // Create a BroadcastRecord
        BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                callerPackage, callingPid, callingUid, callerInstantApp, resolvedType,
                requiredPermissions, appOp, brOptions, registeredReceivers, resultTo,
                resultCode, resultData, resultExtras, ordered, sticky, false, userId);
        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast "+ r); final boolean replaced = replacePending && (queue.replaceParallelBroadcastLocked(r) ! =null);
        // Note: We assume resultTo is null for non-ordered broadcasts.
        if(! replaced) {// Add the BroadcastRecord to the queue
            queue.enqueueParallelBroadcastLocked(r);
            // Call scheduleBroadcastsLocked of the BroadcastQueue
            queue.scheduleBroadcastsLocked();
        }
        registeredReceivers = null;
        NR = 0; }...return ActivityManager.BROADCAST_SUCCESS;
}
Copy the code

Next we’ll look at the broadcast that needs to be sent from AMS to the BroadcastReceiver process

AMS to BroadcastReceiver process

Sequence diagram:

Let’s continue with scheduleBroadcastsLocked

public void scheduleBroadcastsLocked() {
    if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
            + mQueueName + "]: current="
            + mBroadcastsScheduled);

    if (mBroadcastsScheduled) {
        return;
    }
    mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
    mBroadcastsScheduled = true;
}
Copy the code

An mHandler of type BroadcastHandler sends a message identified as BROADCAST_INTENT_MSG

case BROADCAST_INTENT_MSG: {
    if (DEBUG_BROADCAST) Slog.v(
            TAG_BROADCAST, "Received BROADCAST_INTENT_MSG");
    processNextBroadcast(true);
} break;
Copy the code

The processNextBroadcast method is also called in the Handler. The processNextBroadcast method handles both out-of-order and ordered broadcasts.

final void processNextBroadcast(boolean fromMsg) { ... if (fromMsg) { mBroadcastsScheduled = false; } / / traverse stored disorderly radio while (mParallelBroadcasts. The size () > 0) {/ / get disorderly radio r = mParallelBroadcasts. Remove (0); r.dispatchTime = SystemClock.uptimeMillis(); r.dispatchClockTime = System.currentTimeMillis(); . for (int i=0; i<N; i++) { Object target = r.receivers.get(i); if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Delivering non-ordered on [" + mQueueName + "] to registered " + target + ": " + r); deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i); } addBroadcastToHistoryLocked(r); if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast [" + mQueueName + "] " + r); }}Copy the code

Radio here first to get to the chaos, and then call deliverToRegisteredReceiverLocked method, and access to radio instance into the inside. Take a look at deliverToRegisteredReceiverLocked method

private void deliverToRegisteredReceiverLocked(BroadcastRecord r, BroadcastFilter filter, boolean ordered, int index) { ... try { if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST, "Delivering to " + filter + " : " + r); if (filter.receiverList.app ! = null && filter.receiverList.app.inFullBackup) { // Skip delivery if full backup in progress // If it's an ordered broadcast, we need to continue to the next receiver. 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) { Slog.w(TAG, "Failure sending broadcast " + r.intent, e); if (ordered) { r.receiver = null; r.curFilter = null; filter.receiverList.curBroadcast = null; if (filter.receiverList.app ! = null) { filter.receiverList.app.curReceivers.remove(r); }}}}Copy the code

Most of the code, which checks for things related to broadcast permissions, is omitted here, and if all checks pass, the performReceiveLocked method is called.

void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver, Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) throws RemoteException { // Send the intent to the receiver asynchronously using one-way binder calls. if (app ! = null) { if (app.thread ! = null) { // If we have an app thread, do the call through that so it is // correctly ordered with other one-way calls. try { app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras, ordered, sticky, sendingUser, app.repProcState); // TODO: Uncomment this when (b/28322359) is fixed and we aren't getting // DeadObjectException when the process isn't actually dead. //} catch (DeadObjectException ex) { // Failed to call into the process. It's dying so just let it die and move on. // throw ex; } catch (RemoteException ex) { // Failed to call into the process. It's either dying or wedged. Kill it gently. synchronized (mService) { Slog.w(TAG, "Can't deliver broadcast to " + app.processName + " (pid " + app.pid + "). Crashing it."); app.scheduleCrash("can't deliver broadcast"); } throw ex; } } else { // Application has died. Receiver doesn't exist. throw new RemoteException("app.thread must not be null"); } } else { receiver.performReceive(intent, resultCode, data, extras, ordered, sticky, sendingUser); }}Copy the code

To judge whether the process of the broadcast receiver exists, if there is a will call ApplicationThread scheduleRegisteredReceiver method. This brings us back to the client side

public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
        int resultCode, String dataStr, Bundle extras, boolean ordered,
        boolean sticky, int sendingUser, int processState) throws RemoteException {
    updateProcessState(processState, false);
    receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
            sticky, sendingUser);
}
Copy the code

The performReceive method of the IIntentReceiver object is called. IIntentReceiver is used to broadcast cross-process communication. Implemented as LoadedApk. ReceiverDispatcher. InnerReceiver.

@Override public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { final LoadedApk.ReceiverDispatcher rd; if (intent == null) { Log.wtf(TAG, "Null intent received"); rd = null; } else { rd = mDispatcher.get(); } if (ActivityThread.DEBUG_BROADCAST) { int seq = intent.getIntExtra("seq", -1); Slog.i(ActivityThread.TAG, "Receiving broadcast " + intent.getAction() + " seq=" + seq + " to " + (rd ! = null ? rd.mReceiver : null)); } if (rd ! = null) { rd.performReceive(intent, resultCode, data, extras, ordered, sticky, sendingUser); } else { ... }}Copy the code

The performReceive method of ReceiverDispatcher is called

public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { final Args args = new Args(intent, resultCode, data, extras, ordered, sticky, sendingUser); if (intent == null) { Log.wtf(TAG, "Null intent received"); } else { if (ActivityThread.DEBUG_BROADCAST) { int seq = intent.getIntExtra("seq", -1); Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq + " to " + mReceiver); } } 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); }}}}Copy the code

Encapsulate the broadcast intent parameters as Args, and then call the mActivityThread post method to pass in the Args. MActivityThread is a Handler that points to H. Since args.geTrunnable () is passed in, what does getRunnable do

final class Args extends BroadcastReceiver.PendingResult { ... public final Runnable getRunnable() { return () -> { ... try { ClassLoader cl = mReceiver.getClass().getClassLoader(); intent.setExtrasClassLoader(cl); intent.prepareToEnterProcess(); setExtrasClassLoader(cl); receiver.setPendingResult(this); receiver.onReceive(mContext, intent); } catch (Exception e) { ... }... }; }}Copy the code

The onReceive method of receiver is called so that registered broadcasts can be notified.