BroadcastReceiver management

Broadcast essentially provides a more flexible way to use Intents.

Regardless of whether an Activity or a Service is started, the function in the application is called through the sending and filtering of the Intent, but the function completed by the Activity, Service, and ContentProvider is fixed

As for BroadcastReceiver, you can do anything you want, and the way to send and receive broadcast is more flexible

understandBroadcastReceiver

BroadcastReceiver is such a simple component that there is no data structure to manage it even in ActivityThread.

The essence of a BroadcastReceiver is that it executes a method in an application through an Intent. It does not require a long-running object in an application

Although there is no specific data structure in the application to manage BroadcastReceiver, it is needed in AMS because BroadcastReceiver can be registered dynamically with AMS at run time, and data structures are needed in AMS to manage dynamic receivers

A BroadcastReceiver can receive broadcasts in two ways

  • throughAndroidManifestIn the<receiver/>Label registration
  • throughAMStheregisterReceiver()Interface to register

Knowledge about the use of broadcasting can be learned on the official website: Broadcasting basics

Let’s take a look at the important definitions of the BroadcastReceiver class:

public abstract class BroadcastReceiver {
    private PendingResult mPendingResult;
    public abstract void onReceive(Context context, Intent intent);
    public final PendingResult goAsync(a) {
        PendingResult res = mPendingResult;
        mPendingResult = null;
        returnres; }}Copy the code

To implement a BroadcastReceiver, you simply inherit BroadcastReceiver and implement its abstract method onReceive(). This is also the simplest use of radio.

There are also mPendingResult variables and goAsync() methods from the BroadcastReceiver class definition. What do they do? They are mainly used when a broadcast is received and some time-consuming operations need to be done, and the processing results may need to be returned

  • We know thatBroadcastReceiverThe object’sonReceive()If it is not returned for a long time, theANR
  • Therefore, if a time-consuming operation is to be performed, it must be done in another thread

The mPendingResult variable exists for this purpose. The goAsync() method provides an interface to get the mPendingResult variable.

You can see this from the implementation of the goAsync() method

  • Methods inmPendingResultObject after the pointer is returned. It will also bemPendingResultThe value of the set tonull

According to the official account:

Once the goAsync() method is executed, the pointer to the mPendingResult object needs to be passed to the new thread before onReceive() ends, and AMS must call the mPendingResult object’s Finish () method after the new thread completes processing.

Let’s look at the PendingResult finish() method:

    public final void finish(a) {
        if (mType == TYPE_COMPONENT) {
            final IActivityManager mgr = ActivityManager.getService();
            if (QueuedWork.hasPendingWork()) {
                QueuedWork.queue(new Runnable() {
                    @Override public void run(a) {
                        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);
            finalIActivityManager mgr = ActivityManager.getService(); sendFinished(mgr); }}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) {
            }
        }
    }
Copy the code

The core operation is to call AMS’s finishReceiver() method, which signals the end of the current broadcast receiver.

When the onReceive() call completes, AMS detects that mPendingResult is not empty and automatically executes the Finish () method. For broadcast receivers that have executed goAsync(), AMS will not actively execute Finish ()

The finish() method is very important and will be used at the end of this chapter

Classification of broadcasts

There are four types of broadcast in terms of how they are sent.

  • Broadcast: YesContextThe methods insendBroadcast()andsendBroadcastAsUser()The broadcast sent is a normal broadcast.
    • Regular broadcasts are typically sent to all registered recipients of the system currently
    • The order in which the receiver receives the broadcast is also uncertain
  • Orderly broadcast: YesContextThe methods insendOrderedBroadcast()andsendOrderedBroadcastAsUser()The broadcast sent is an ordered broadcast.
    • The order of sending an ordered broadcast is determined by the priority of the receiver. The default range is- 1000.1000But in fact there is no clear numerical limit that can be reachedintThe maximum
    • The priority of the receiver is matched byintent-filtertheandroid:priorityProperty to control
    • Receivers with the same priority will run in random order.
    • As the receiver executes sequentially, the receiver can either pass the results down or abort the broadcast completely so that it is no longer transmitted to other receivers
  • Sticky broadcast: yesContextThe methods insendStickyBroadcast()andsendOrderedBroadcastAsUser()The broadcast sent is a sticky broadcast.
    • Unlike the first two broadcasts, sticky broadcasts can be sent to recipients who sign up later in the system, or even to recipients in newly installed applications
    • The website explains that for safety reasons, theAndroid 9On, the two interfaces are marked asDeprecated
    • But that doesn’t stop it from beingFrameworkLet’s look at an example from the official website:
        IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        // For a sticky broadcast, an Intent with the current battery state can be registered when listening on it
        Intent batteryStatus = context.registerReceiver(null, ifilter);
    Copy the code
  • Local broadcast: YesLocalBroadcastManager.sendBroadcastThe broadcast sent is a local broadcast
    • Local broadcast sends a broadcast to a receiver in the same application as the sender
    • If you do not need to send broadcast across applications, you are advised to use local broadcast
    • This implementation is more efficient (no interprocess communication required)
    • And you don’t have to worry about any security issues when other applications send and receive broadcasts

Broadcast data structure

In AMS, all registered broadcast receivers are placed in the member variable mRegisteredReceivers, defined as follows:

final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
Copy the code

MRegisteredReceivers is a HashMap where

  • The IBinder object whose key is the receiver

  • Value is the ReceiverList object of the receiver

    • Because there may be more than one recipientIntentFilter, soReceiverListIt’s an array
    final class ReceiverList extends ArrayList<BroadcastFilter>
    Copy the code
    • whileBroadcastFilterIt’s inheritedIntentFilter, defined as follows:
    final class BroadcastFilter extends IntentFilter {
        final ReceiverList receiverList; // A reference to the owning receiver
        final String packageName;        // Package name of the application
        final String requiredPermission; // A string of permissions to declare when sending broadcasts
        final int owningUid;             // Uid of the application
        final int owningUserId;          // UserID of the application. }Copy the code

The key definitions of ReceiverList are as follows:

final class ReceiverList extends ArrayList<BroadcastFilter>
        implements IBinder.DeathRecipient {
    final ActivityManagerService owner;
    public final IIntentReceiver receiver; // IntentReceiver defined in user process
    public final ProcessRecord app;        // ProcessRecord object of the owning user process
    public final int pid;                  // Pid of the owning user process
    public final int uid;                  // Uid of the owning user process
    public final int userId;               / / user id
    BroadcastRecord curBroadcast = null;   //
    boolean linkedToDeath = false;         // Whether binder death listener is registered
}
Copy the code

When broadcasting, broadcast messages received in AMS are stored in mBroadcastQueues before being sent to receivers in user processes. MBroadcastQueues are an array of only two elements, defined as follows:

final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[2];
Copy the code

In addition, two variables are defined in AMS:

    BroadcastQueue mFgBroadcastQueue;
    BroadcastQueue mBgBroadcastQueue;
Copy the code

AMS associates these two variables with the mBroadcastQueues collection in its constructor:

    public ActivityManagerService(Context systemContext) {... mFgBroadcastQueue =new BroadcastQueue(this, mHandler,
                "foreground", BROADCAST_FG_TIMEOUT, false);
        mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
                "background", BROADCAST_BG_TIMEOUT, true);
        mBroadcastQueues[0] = mFgBroadcastQueue;
        mBroadcastQueues[1] = mBgBroadcastQueue; . }Copy the code
  • mFgBroadcastQueueIt’s used to hold theFLAG_RECEIVER_FOREGROUNDFlag broadcast, which requires the receiver process toforegroundRun at the priority of the
  • If not specified, ordinary broadcasts are saved inmBgBroadcastQueueIn the

Let’s look at the main structure of the BroadcastQueue class:

public final class BroadcastQueue {
    final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
    final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();
}
Copy the code
  • mParallelBroadcastsUsed to hold all regular broadcasts
  • mOrderedBroadcastsUsed to hold all ordered broadcasts

Where’s the sticky radio? All sticky broadcasts in the system are saved in the AMS member variable mStickyBroadcasts, which is defined as follows:

    final SparseArray<ArrayMap<String, ArrayList<Intent>>> mStickyBroadcasts =
            new SparseArray<ArrayMap<String, ArrayList<Intent>>>();
Copy the code
  • mStickyBroadcastsIs aSparseArrayType array, usedThe user IdAs an index, it savesArrayMapobject
  • ArrayMapThe object stores all the sticky broadcasts sent by a user, with each record marked asIntenttheactionThe string is used as the index, and the stored content is aArrayListobject
  • ArrayListObject that stores the containingactiontheIntent

The registration process for broadcasts

Dynamic registration is done through the interface registerReceiver(). When an application registers a receiver, it does not call the AMS interface directly, but the method in the Context class. Because the AMS interface needs to provide the IBinder object of the application ApplicationThread class as the parameter, it is difficult to obtain this object in the application.

RegisterReceiver () in Context finally calls ContextImpl’s registerReceiverInternal() method as follows:

class ContextImpl{
    private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
            IntentFilter filter, String broadcastPermission,
            Handler scheduler, Context context, int flags) {...try {
            final Intent intent = ActivityManager.getService().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
                    broadcastPermission, userId, flags);
            if(intent ! =null) {
                intent.setExtrasClassLoader(getClassLoader());
                intent.prepareToEnterProcess();
            }
            return intent;
        } catch (RemoteException e) {
            throwe.rethrowFromSystemServer(); }}}Copy the code

The registerReceiverInternal() method ends up calling AMS’s registerReceiver() method with the following code:

    public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId,
            int flags) {
        // Make sure it is not isolated
        enforceNotIsolatedCaller("registerReceiver");
        ArrayList<Intent> stickyIntents = null;
        ProcessRecord callerApp = null;
        final booleanvisibleToInstantApps = (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) ! =0;
        int callingUid;
        int callingPid;
        boolean instantApp;
        synchronized(this) {...// Get action information from the registration information
            Iterator<String> actions = filter.actionsIterator();
            if (actions == null) {
                ArrayList<String> noAction = new ArrayList<String>(1);
                noAction.add(null);
                actions = noAction.iterator();
            }
            // Get user ID
            int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
            // Loop through the action
            while (actions.hasNext()) {
                String action = actions.next();
                // Iterate over the sticky broadcast Map under each userID
                for (int id : userIds) {
                    // Get the sticky broadcast Map under a specific userID
                    ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id);
                    if(stickies ! =null) {
                        // Find the intEnts in the Map that match the current action information
                        ArrayList<Intent> intents = stickies.get(action);
                        if(intents ! =null) {
                            if (stickyIntents == null) {
                                stickyIntents = new ArrayList<Intent>();
                            }
                            // Add it to the stickyIntents collection
                            stickyIntents.addAll(intents);
                        }
                    }
                }
            }
        }
        ArrayList<Intent> allSticky = null;
        if(stickyIntents ! =null) {
            // Sticky broadcasts are not empty
            final ContentResolver resolver = mContext.getContentResolver();
            // Find all intents that match the current request
            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 = newArrayList<Intent>(); } allSticky.add(intent); }}}// The first sticky in the list is returned directly back to the client.Intent sticky = allSticky ! =null ? allSticky.get(0) : null;
        // If the receiver passed in at registration is empty
        // Return the sticky object directly
        if (receiver == null) {
            return sticky;
        }
        synchronized (this) {
            if(callerApp ! =null && (callerApp.thread == null|| callerApp.thread.asBinder() ! = caller.asBinder())) {// Original caller already died
                return null;
            }
            // Check whether the registered receiver is already registered
            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
            if (rl == null) {
                // If rL is empty, it is not registered
                // Create an object
                rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                        userId, receiver);
                if(rl.app ! =null) {
                    // If a process object is specified, the receiver is stored in the process object
                    // The object can be released in time when the process destroys it
                    final int totalReceiversForApp = rl.app.receivers.size();
                    if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
                        throw newIllegalStateException(...) ; } rl.app.receivers.add(rl); }else {
                    // Registers the death notification of the receiver if no process object is specified
                    try {
                        receiver.asBinder().linkToDeath(rl, 0);
                    } catch (RemoteException e) {
                        return sticky;
                    }
                    rl.linkedToDeath = true;
                }
                mRegisteredReceivers.put(receiver.asBinder(), rl);// Save the new RL}...// The following is mainly for repeated registrations of receiver
            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
                    permission, callingUid, userId, instantApp, visibleToInstantApps);
            if (rl.containsFilter(filter)) {
                // The filter for this submission has been included. Simply print it and do no other processing
                Slog.w(TAG, "Receiver with filter " + filter
                        + " already registered for pid " + rl.pid
                        + ", callerPackage is " + callerPackage);
            } else {
                // Add the filter that does not contain this submission to the collection
                rl.add(bf);
                if(! bf.debugCheck()) { Slog.w(TAG,"==> For Dynamic broadcast");
                }
                mReceiverResolver.addFilter(bf);
            }
            // Continue with the sticky broadcast part
            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);
                    // Get the send queue (bg or fg) based on the intent flag
                    BroadcastQueue queue = broadcastQueueForIntent(intent);
                    // Create a new broadcast object
                    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 the broadcast
                    queue.enqueueParallelBroadcastLocked(r);
                    // Sends the Intent to the specified processqueue.scheduleBroadcastsLocked(); }}returnsticky; }}Copy the code

The registerReceiver() method looks complicated, but many are for sticky broadcasts:

  • If sticky broadcasts are not processed, only check whether the receiver is registered

    • Not registered, can be created to savereceiverThe object ofReceiverListAnd add it tomRegisteredReceiversIn the collection
    • registeredreceiverYou can re-register as long asPid, ` ` uidanduseridIt’s the same
      • Duplicate registration is used to register with areceiverAdd newIntentFilter
  • And for sticky broadcast

    • If the callregisterReceiver()When the parametersreceiverfornull, immediately return to find the first sticky broadcastIntent
    • Otherwise, all in the system will be found and passed in at registration timeIntentMatch the sticky broadcast and passBroadcastQueueTo send the

Let’s take a look at the transmission of the broadcast

The process of sending a broadcast

AMS broadcastIntent() calls AMS’s broadcastIntent() method, which uses a Context class to send a broadcast.

After the broadcastIntent() method simply checks the caller’s permissions, it calls the internal broadcastIntentLocked() method to complete the broadcast delivery.

BroadcastIntentLocked () method

  • First check the Intent to broadcast

    • If it’s some systemIntent, the corresponding method processing is called
    • If it is a sticky broadcast, the broadcastIntentjoinAMSStickiness broadcast list
  • Finally, find all the receivers and invoke them one by one

The sequence diagram of the broadcast transmission is as follows:

AMS.broadcastIntentLocked

Let’s look at the implementation:

    final int broadcastIntentLocked(...). {
        intent = newIntent(intent); . userId = mUserController.handleIncomingUser(callingPid, callingUid, userId,true,
                ALLOW_NON_FULL, "broadcast", callerPackage);
        // Check whether the user sending the broadcast is running
        if(userId ! = UserHandle.USER_ALL && ! mUserController.isUserOrItsParentRunning(userId)) {if((callingUid ! = SYSTEM_UID || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) ==0)
                    && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
                returnActivityManager.BROADCAST_FAILED_USER_STOPPED; }}finalString action = intent.getAction(); .// Check whether the broadcast is protected
        final boolean isProtectedBroadcast;
        try {
            isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
        } catch (RemoteException e) {
            Slog.w(TAG, "Remote exception", e);
            return ActivityManager.BROADCAST_SUCCESS;
        }
        // Check whether the process sending the broadcast is system
        final boolean isCallerSystem;
        switch (UserHandle.getAppId(callingUid)) {
            case ROOT_UID:
            case SYSTEM_UID:
            case PHONE_UID:
            case BLUETOOTH_UID:
            case NFC_UID:
            case SE_UID:
                isCallerSystem = true;
                break;
            default: isCallerSystem = (callerApp ! =null) && callerApp.persistent;
                break;
        }
        if(! isCallerSystem) {if (isProtectedBroadcast) {
                // It is not a system process, but the broadcast is protected
                // Throws a security exception.throw new SecurityException(msg);
            } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
                    || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
                // Neither system nor protected broadcast
                // However, some special broadcasts cannot be sent, so there are some restrictions
                if (callerPackage == null) {...throw new SecurityException(msg);
                } else if(intent.getComponent() ! =null) {
                    if(! intent.getComponent().getPackageName().equals( callerPackage)) { String msg ="Permission Denial: not allowed to send broadcast "
                                + action + " to "
                                + intent.getComponent().getPackageName() + " from "+ callerPackage; .throw newSecurityException(msg); }}else {
                    // Limit broadcast to their own package.intent.setPackage(callerPackage); }}}if(action ! =null) {
            // Check whether the current action is in the allow-implicit-broadcast tag in SystemConfig
            // Check whether the current action supports implicit broadcast
            if (getBackgroundLaunchBroadcasts().contains(action)) {
                ...
                // If so, add a Flag
                This flag indicates that the broadcast can be sent as an implicit broadcast
                intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
            }
            switch (action) {
                // Check to handle some special actions
                // This section examines some operations related to PMS
                case Intent.ACTION_UID_REMOVED:
                case Intent.ACTION_PACKAGE_REMOVED:
                case Intent.ACTION_PACKAGE_CHANGED:
                ...
                    // If this broadcast is from PMS and is about application deletion or application change
                    // All activities managed by the application need to be removed from the task
                    break; .case "com.android.launcher.action.INSTALL_SHORTCUT":
                    // As of O, we no longer support this broadcasts, even for pre-O apps.
                    // Apps should now be using ShortcutManager.pinRequestShortcut().
                    Log.w(TAG, "Broadcast " + action
                            + " no longer supported. It will not be delivered.");
                    returnActivityManager.BROADCAST_SUCCESS; }... }// For sticky broadcasts
        if (sticky) {
            / / check whether radio sender statement. The android permission. BROADCAST_STICKY permissions
            if(checkPermission(android.Manifest.permission.BROADCAST_STICKY, callingPid, callingUid) ! = PackageManager.PERMISSION_GRANTED) { ...throw new SecurityException(msg);
            }
            if(requiredPermissions ! =null && requiredPermissions.length > 0) {
                // Sending sticky broadcasts cannot specify permission
                return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
            }
            if(intent.getComponent() ! =null) {
                // Sticky broadcast cannot specify a specific component name
                throw new SecurityException(
                        "Sticky broadcasts can't target a specific component");
            }
            if(userId ! = UserHandle.USER_ALL) {If the broadcast is not for all users, check to see if there is an identical broadcast for all users
                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))) {
                                // An exception is thrown when the same broadcast is detected
                                throw new IllegalArgumentException(
                                        "Sticky broadcast " + intent + " for user "
                                        + userId + " conflicts with existing global broadcast");
                            }
                        }
                    }
                }
            }
            // Save broadcasts to mStickyBroadcasts
            ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
            if (stickies == null) {
                stickies = new ArrayMap<>();
                mStickyBroadcasts.put(userId, stickies);
            }
            // Add an Intent list
            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))) {
                    // Override the Intent if it already exists
                    list.set(i, new Intent(intent));
                    break; }}if (i >= stickiesCount) {
                list.add(newIntent(intent)); }}int[] users;
        if (userId == UserHandle.USER_ALL) {
            // Broadcast to all users to get all user ids
            users = mUserController.getStartedUserArray();
        } else {
            // Broadcast to a specific user
            users = new int[] {userId};
        }
        // Count all receivers that receive the Intent
        List receivers = null;
        List<BroadcastFilter> registeredReceivers = null;
        if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
            FLAG_RECEIVER_REGISTERED_ONLY is not specified in the Intent
            // Collect static receivers
            receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
        }
        if (intent.getComponent() == null) {
            // If no Component is specified, all receivers matching the Intent are found
            if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
                // For broadcasts sent to all users, and the user sending the broadcast is shell
                for (int i = 0; i < users.length; i++) {
                    / / traverse userID.// Check mReceiverResolver for dynamically registered broadcast receivers
                    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); }}final booleanreplacePending = (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) ! =0;
        intNR = registeredReceivers ! =null ? registeredReceivers.size() : 0;
        if(! ordered && NR >0) {
            // If it is not ordered broadcast, it is normal broadcast
            if (isCallerSystem) {
                checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
                        isProtectedBroadcast, registeredReceivers);
            }
            // Get the send queue
            final BroadcastQueue queue = broadcastQueueForIntent(intent);
            // Create a BroadcastRecord object
            BroadcastRecord r = newBroadcastRecord(...) ; .if(! replaced) { queue.enqueueParallelBroadcastLocked(r);// Join the parallel queue
                queue.scheduleBroadcastsLocked(); // Send a broadcast
            }
            registeredReceivers = null;
            NR = 0;
        }
        int ir = 0;
        if(receivers ! =null) {
            Statically registered receivers exist.intNT = receivers ! =null ? receivers.size() : 0;
            int it = 0;
            ResolveInfo curt = null;
            BroadcastFilter curr = null;
            while (it < NT && ir < NR) {
                if (curt == null) {
                    // Get the static broadcast receiver
                    curt = (ResolveInfo)receivers.get(it);
                }
                if (curr == null) {
                    // Get the dynamic broadcast receiver
                    curr = registeredReceivers.get(ir);
                }
                if (curr.getPriority() >= curt.priority) {
                    // Preferentially inserts the dynamic broadcast receiver into the static broadcast receiver
                    // Dynamic receivers are inserted after static receivers with the same priority
                    receivers.add(it, curr);
                    ir++;
                    curr = null;
                    it++;
                    NT++;
                } else {
                    // Skip to the next ResolveInfo in the final list.
                    it++;
                    curt = null; }}}// Add the remaining dynamic recipients to the queue
        while (ir < NR) {
            if (receivers == null) {
                receivers = newArrayList(); } receivers.add(registeredReceivers.get(ir)); ir++; }...if((receivers ! =null && receivers.size() > 0) || resultTo ! =null) {
            // Get the broadcast send queue
            BroadcastQueue queue = broadcastQueueForIntent(intent);
            // Create a broadcast object
            BroadcastRecord r = newBroadcastRecord(...) ; .final BroadcastRecord oldRecord =
                    replacePending ? queue.replaceOrderedBroadcastLocked(r) : null;
            if(oldRecord ! =null) {
                // If an old broadcast exists, call performReceiveLocked to perform the broadcast
                if(oldRecord.resultTo ! =null) {
                    final BroadcastQueue oldQueue = broadcastQueueForIntent(oldRecord.intent);
                    try {
                        // This method is called onReceived()oldQueue.performReceiveLocked(...) ; }catch(RemoteException e) { ... }}}else {
                queue.enqueueOrderedBroadcastLocked(r); // Add the broadcast to the order queue
                queue.scheduleBroadcastsLocked();       // Send a broadcast}}...return ActivityManager.BROADCAST_SUCCESS;
    }
Copy the code

BroadcastIntentLocked () is broadcastIntentLocked().

  • The first is to check whether the broadcast sent is some special broadcast
    • Especially since thePMSAnnouncements about application installation changes issued in
  • The next step is to determine if it’s a sticky broadcast, and if it is
    • First check some relevant permissions, such as sticky broadcast can not be carriedpermissionAnd,IntentYou must specifyComponent
    • Then create the corresponding data structure and add it tomStickyBroadcastsIn the collection
  • Then find the corresponding broadcast receiver for that broadcast
    • If the broadcast is an ordinary broadcast, it is preferentially sent to the dynamic receiver
    • If the broadcast is ordered, dynamic receivers and static receivers are sorted according to their priorities and then createdBroadcastRecordObject and add to the ordered broadcast queue
  • Finally, throughscheduleBroadcastsLocked()Send broadcast

As you can see from the code implementation above, static receivers must be sorted between each other regardless of the broadcast, and the static receivers will be ranked before the dynamic receivers for the same priority.

Why do static receivers have to be placed in an ordered queue? For a static receiver, the process to which it belongs may or may not already be running. If the process is not running, you need to start it first. First of all, process startup is time-consuming and may fail. This process can only be handled one by one rather than simply sending a group message.

At this point, both the broadcast receiver and the broadcast content are checked and ready, and we continue to learn about scheduleBroadcastsLocked() broadcasts

BroadcastQueue.scheduleBroadcastsLocked

The scheduleBroadcastsLocked() method simply sends a BROADCAST_INTENT_MSG message as follows:

    public void scheduleBroadcastsLocked(a) {... mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG,this));
        mBroadcastsScheduled = true;
    }
Copy the code

The BROADCAST_INTENT_MSG message is handled by calling the processNextBroadcast() method

    case BROADCAST_INTENT_MSG: {
        ...
        processNextBroadcast(true);
    } break;
    
    final void processNextBroadcast(boolean fromMsg) {
        synchronized (mService) {
            processNextBroadcastLocked(fromMsg, false); }}Copy the code

Call to processNextBroadcastLocked () method. This method is quite long, let’s briefly analyze:

final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) { BroadcastRecord r; .// Loop through messages in the collection mParallelBroadcasts
    while (mParallelBroadcasts.size() > 0) {
        r = mParallelBroadcasts.remove(0); r.dispatchTime = SystemClock.uptimeMillis(); r.dispatchClockTime = System.currentTimeMillis(); .final intN = r.receivers.size(); .for (int i=0; i<N; i++) { Object target = r.receivers.get(i); .// call notification receiver
            deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i);
        }
        addBroadcastToHistoryLocked(r);
    }
    // Next process the mPendingBroadcast collection
    // This set holds broadcasts waiting for the process to start
    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 {
            Slog.w(TAG, "pending app ["
                    + mQueueName + "]" + mPendingBroadcast.curApp
                    + " died before responding to broadcast");
            mPendingBroadcast.state = BroadcastRecord.IDLE;
            mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
            mPendingBroadcast = null; }}// Process broadcasts in mOrderedBroadcasts
    boolean looped = false;
    do{... r = mOrderedBroadcasts.get(0);
        boolean forceReceive = false; .intnumReceivers = (r.receivers ! =null)? r.receivers.size() :0;
        if (mService.mProcessesReady && r.dispatchTime > 0) {
            long now = SystemClock.uptimeMillis();
            // The timeout of the outperiod is 10*1000
            // The timeout of bgGround's outperiod is 60*1000
            if ((numReceivers > 0) && (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
                // If timeout occurs, the broadcast is terminated. broadcastTimeoutLocked(false); // forcibly finish this broadcast
                forceReceive = true; r.state = BroadcastRecord.IDLE; }}...if (r.receivers == null || r.nextReceiver >= numReceivers
                || r.resultAbort || forceReceive) {
            // There are no additional receivers
            if(r.resultTo ! =null) {
                try{...// Pass the broadcast result to the process that sent the broadcast
                    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) {
                    r.resultTo = null; . }}... }}while (r == null); .if (nextReceiver instanceofBroadcastFilter) { BroadcastFilter filter = (BroadcastFilter)nextReceiver; .// Send to the receiver process
        deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
        if (r.receiver == null| |! r.ordered) { ...// The situation that has already been handled
            r.state = BroadcastRecord.IDLE;
            // Process the next broadcast
            scheduleBroadcastsLocked();
        } else {
            if(brOptions ! =null && brOptions.getTemporaryAppWhitelistDuration() > 0) { scheduleTempWhitelistLocked(filter.owningUid, brOptions.getTemporaryAppWhitelistDuration(), r); }}return; }... ProcessRecord app = mService.getProcessRecordLocked(targetProcess, info.activityInfo.applicationInfo.uid,false); .if(app ! =null&& app.thread ! =null && !app.killed) {
        // If the receiver is static and the process corresponding to the receiver has been started
        try {
            app.addPackage(info.activityInfo.packageName,
                    info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);
            / / call processCurBroadcastLocked inform receiver
            processCurBroadcastLocked(r, app, skipOomAdj);
            // Return directly
            return; }... }...// The process is not started. Call startProcessLocked to start the process
    if((r.curApp=mService.startProcessLocked(...) ) = =null) {...// If the process fails to start, process the next broadcast
        scheduleBroadcastsLocked();
        r.state = BroadcastRecord.IDLE;
        return;
    }
    mPendingBroadcast = r;
    mPendingBroadcastRecvIndex = recIdx;
}
Copy the code

ProcessNextBroadcastLocked () method is a collection of radio receiver to send to send handle traversal, notification to the application of real partly in deliverToRegisteredReceiverLocked () method, we detailed a look.

BroadcastQueue.deliverToRegisteredReceiverLocked

The method is as follows:

    private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
            BroadcastFilter filter, boolean ordered, int index) {... performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,newIntent(r.intent), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky, r.userId); . }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) {... app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras, ordered, sticky, sendingUser, app.repProcState); . }}else{ receiver.performReceive(intent, resultCode, data, extras, ordered, sticky, sendingUser); }}Copy the code

We have in performReceiveLocked method clearly see the app. The thread words, this means that began to call the interfaces in the application process, namely the scheduleRegisteredReceiver ActivityThread () interface, as follows:

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

ActivityThread calls the performReceive() method of IIntentReceiver

The IIntentReceiver is an AIDL, the trace implementation is in LoadedApk, and the specific assembly process is done when ContextImpl is initialized

Let’s look at the key definitions in LoadedApk

LoadedApk.performReceive

The code is as follows:

class LoadedApk{
    static final class ReceiverDispatcher {
        final static class InnerReceiver extends IIntentReceiver.Stub {...@Override
            public void performReceive(...). {
                finalLoadedApk.ReceiverDispatcher rd; .// Call the internal performReceive() methodrd.performReceive(...) ; . }}...public void performReceive(...). {
            final Args args = newArgs(...) ; .// The focus here is on executing the Runnable object obtained through args.geTrunnable ()
            // The onReceive() method callback is executed in the Runnable object
            if (intent == null| |! mActivityThread.post(args.getRunnable())) {if(mRegistered && ordered) { IActivityManager mgr = ActivityManager.getService(); . args.sendFinished(mgr); }}}final class Args extends BroadcastReceiver.PendingResult {...public final Runnable getRunnable(a) {
                return() - > {finalBroadcastReceiver receiver = mReceiver; .try{.../ / onReceive callbackreceiver.onReceive(mContext, intent); }...if(receiver.getPendingResult() ! =null) {
                        // mPendingResult is not empty. Finish ()finish(); }}; }}}}Copy the code

So the transmission of a broadcast ends…

Finally, I finished the chapter of AMS with a lot of stumbling, but I still have an impression on the overall process. Keep this note for your review

Next learning Android graphics display system, come on 💪