Android applications can broadcast messages to and from the Android system and other Android applications, similar to the publish-subscribe design pattern. These broadcasts are sent as events of interest occur. Android, for example, sends broadcasts when various system events occur, such as when the system starts up or the device starts charging. For example, apps can send custom broadcasts to notify other apps of events they might be interested in.

Radio registered

Register broadcast receiver component calls to AMS

  • Broadcast registration is generally divided into static registered broadcast and dynamic registered broadcast, dynamic registration and remote registration and local registration (LocalBroadcastManager), usually the most used should also be dynamic registered broadcast, then from the registerReceiver method to see how the broadcast is registered. Simply register a broadcast and set the action to BROADCAST_TEST_MESSAGE
IntentFilter intentFilter = new IntentFilter(BROADCAST_TEST_MESSAGE);
        registerReceiver(receiver,intentFilter);
Copy the code
  • The registerReceiver method also calls the ContextWrapper method. ContextWrapper calls ContextImpl’s registerReceiver method.

frameworks/base/core/java/android/app/ContextImpl.java

@Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        return registerReceiver(receiver, filter, null, null);
    }
    
@Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
            String broadcastPermission, Handler scheduler) {
        returnregisterReceiverInternal(receiver, getUserId(), filter, broadcastPermission, scheduler, getOuterContext(), 0); / / 1}Copy the code
  • From the above source code, registerReceiver calls the registerReceiver method with four parameters, and then calls the registerReceiverInternal method, Note that getOuterContext() corresponds to the mOuterContext variable of ContextImpl, which is the Activity component that calls the registered broadcast method. Then you see the registerReceiverInternal method

frameworks/base/core/java/android/app/ContextImpl.java

private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId, IntentFilter filter, String broadcastPermission, Handler scheduler, Context context, int flags) { IIntentReceiver rd = null; / / 1if(receiver ! = null) {if(mPackageInfo ! = null && context ! = null) {if(scheduler == null) { scheduler = mMainThread.getHandler(); //2 } rd = mPackageInfo.getReceiverDispatcher( receiver, context, scheduler, mMainThread.getInstrumentation(),true); / / 3}... } try { final Intent intent = ActivityManager.getService().registerReceiver( mMainThread.getApplicationThread(), mBasePackageName, rd, filter, broadcastPermission, userId, flags); //4 } catch (RemoteException e) { throw e.rethrowFromSystemServer(); }}Copy the code
  • IIntentReceiver is a local Binder object used for interprocess communication. Specific implementation is LoadApk. ReceiverDispatcher. InnerReceiver
  1. Comment 2 gets a Handler object that points to the main thread Handler object H for ActivityThread
  2. Note 3 calls the getReceiverDispatcher method of mPackageInfo, which is the LoadApk object, using the BroadcastReceiver as an argument.
  3. As you see in comment 4, there is no doubt that AMS’s registerReceiver method is called with AIDL and passed in ApplicationThread to facilitate subsequent AMS communication with the process currently registering the broadcast receiver.
  • We then return to the getReceiverDispatcher method of the LoadApk object

frameworks/base/core/java/android/app/LoadedApk.java

private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers = new ArrayMap<>(); static final class ReceiverDispatcher { final static class InnerReceiver extends IIntentReceiver.Stub { ....... }//1 ReceiverDispatcher(BroadcastReceiver receiver, Context context, Handler activityThread, Instrumentation instrumentation, boolean registered) { ...... mIIntentReceiver = new InnerReceiver(this, ! registered); mReceiver = receiver; mContext = context; mActivityThread = activityThread; mInstrumentation = instrumentation; mRegistered = registered; mLocation = new IntentReceiverLeaked(null); mLocation.fillInStackTrace(); } / / 2... public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r, Context context, Handler handler, Instrumentation instrumentation, boolean registered) { synchronized (mReceivers) { LoadedApk.ReceiverDispatcher rd = null; ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;if (registered) {
                map = mReceivers.get(context);
                if (map != null) {
                    rd = map.get(r);
                }
            }
            if(rd == null) { rd = new ReceiverDispatcher(r, context, handler, instrumentation, registered); / / 3if (registered) {
                    if(map == null) { map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>(); mReceivers.put(context, map); } map.put(r, rd); / / 4}}else {
                rd.validate(context, handler);
            }
            rd.mForgotten = false;
            returnrd.getIIntentReceiver(); / / 5}}... IIntentReceivergetIIntentReceiver() {
            returnmIIntentReceiver; 6}} / /Copy the code
  • From above source code
  1. Each Activity component that registers a broadcast receiver will have a corresponding ReceiverDispatcher object. If it does not exist, create a new ReceiverDispatcher object
  2. See the ReceiverDispatcher constructor in comment 2, which holds the broadcast receiver we registered, the context pointing to the Activity component that registered the broadcast and the main thread handler object for that component. And create an InnerReceiver that implements the IIntentReceiver native Binder interface
  3. Note 4 stores each ReceiverDispatcher in the ArrayMap with the broadcast receiver as the key and the context pointing to the corresponding Actiivty component as the key. Saves the ArrayMap that holds the correspondence between broadcast receivers and ReceiverDispatcher into the mReceivers in LoadApk
  4. The result, combined with comments 5 and 6, is the native Binder object that InnerReceiver supports interprocess communication
  • Then continue to analyze the AMS registerReceiver

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId,
            int flags) {
        ......
            
        synchronized (this) {
           
            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
            if (rl == null) {
                rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                        userId, receiver);
                if(rl.app ! = null) { ...... rl.app.receivers.add(rl); }else {
                    try {
                        receiver.asBinder().linkToDeath(rl, 0);
                    } catch (RemoteException e) {
                        return sticky;
                    }
                    rl.linkedToDeath = true; } mRegisteredReceivers.put(receiver.asBinder(), rl); / / 1}... BroadcastFilter bf = new BroadcastFilter(filter, rl,callerPackage, permission, callingUid, userId, instantApp, visibleToInstantApps); / / 2if (rl.containsFilter(filter)) {
                Slog.w(TAG, "Receiver with filter " + filter
                        + " already registered for pid " + rl.pid
                        + ", callerPackage is " + callerPackage);
            } else{ rl.add(bf); / / 3... mReceiverResolver.addFilter(bf); / / 4}... }Copy the code
  • From the above source code, there is a code to handle sticky broadcast, sticky events will remain in AMS until a new sticky event. Google has officially dismissed engagement events as outdated and won’t expand on them here.
  1. Receiver is stored in AMS HashMap type mRegisteredReceivers. Registration of broadcast does not register broadcast receivers into AMS. The corresponding IIntentReceiver object (InnerReceiver) associated with the registered broadcast receiver is saved (registered) in AMS
  • Note 2 wraps IntentFilter as a BroadcastFilter object. The BroadcastFilter can be used as a description of the broadcast receiver. The BroadcastFilter can be used as a description of the broadcast receiver. Note 3 uses ReceiverList to save the broadcast receiver for each component
  • Note 4 Save the description of the broadcast receiver object in the mReceiverResolver. If AMS receives the corresponding broadcast message later, it can find the corresponding broadcast receiver.
  • So much for the broadcast registration process, the next section continues with the broadcast sending process

Activity registers broadcast to the AMS sequence diagram

Send broadcast

AMS finds dynamically registered broadcast receivers

sendBroadcast(new Intent(BROADCAST_TEST_MESSAGE));
Copy the code
  • In this section, we will continue to learn about the process of sending broadcast. There are several kinds of sending broadcast, namely ordinary broadcast, ordered broadcast, and sticky broadcast. The basic process is very similar, so this section starts with the sendBroadcast method. Send a broadcast as shown in BROADCAST_TEST_MESSAGE. The same sendBroadcast method that sends a broadcast ends up calling ContextImpl’s sendBroadcast method

frameworks/base/core/java/android/app/ContextImpl.java

 @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) { throw e.rethrowFromSystemServer(); }}Copy the code
  • The logic is simple: AMS’s broadcastIntent method is called directly by AIDL’s interprocess communication

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

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();
            try {
                return broadcastIntentLocked(callerApp,
                        callerApp ! = null ?callerApp.info.packageName : null, intent, resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid, callingPid, userId); //2 } finally { Binder.restoreCallingIdentity(origId); }}}Copy the code
  • The logic is clear: get the process Id to see who is sending the broadcast, and continue calling broadcastIntentLocked.

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

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 realCallingUid, int realCallingPid, int userId, boolean allowBackgroundActivityStarts) { intent = new Intent(intent); . // Add to the sticky listif requested.
        if (sticky) {
            if(checkPermission(android.Manifest.permission.BROADCAST_STICKY, callingPid, callingUid)//1 ! = PackageManager.PERMISSION_GRANTED) { ...... }... // We use userId directly here, since the"all" target is maintained
            // as a separate set of sticky broadcasts.
            if(userId ! = UserHandle.USER_ALL) { // But first,if this is not a broadcast to all users, then
                // make sure it doesn't conflict with an existing broadcast to // all users. ........ } } ArrayMap
      
       > stickies = mStickyBroadcasts.get(userId); if (stickies == null) { stickies = new ArrayMap<>(); mStickyBroadcasts.put(userId, stickies); / / 2}...
      ,>Copy the code
  • BroadcastIntentLocked has a long source code for broadcastIntentLocked, which includes actions defined by intEnts and sticky broadcasts. See note 1 if it is a sticky broadcast will check whether the corresponding application process to register the android Manifest. Permission. BROADCAST_STICKY permissions, then 2 place by user id as the key, The sticky broadcast set List is stored in mStickyBroadcasts of AMS SparseArray (the map key can only be an Int). Move on to the next piece of code for the broadcastIntentLocked method

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

// Figure out who all will receive this broadcast. 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); / / 1}if (intent.getComponent() == null) {
            if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
                // Query one target user at a time, excluding shell-restricted users
                .......
            } else {
                registeredReceivers = mReceiverResolver.queryIntent(intent,
                        resolvedType, false/*defaultOnly*/, userId); //2 } } final boolean replacePending = (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) ! = 0; / / 3... 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); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, callerInstantApp, resolvedType, requiredPermissions, appOp, brOptions, registeredReceivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId, allowBackgroundActivityStarts, timeoutExempt); //4 final boolean replaced = replacePending && (queue.replaceParallelBroadcastLocked(r) ! = null); //5 // Note: We assume resultTo is null for non-ordered broadcasts. if (! replaced) {//6 queue.enqueueParallelBroadcastLocked(r); //7 queue.scheduleBroadcastsLocked(); //8 } registeredReceivers = null; //9 NR = 0; }Copy the code
  1. From the above source, comment 1 goes to the PackageManagerService to find static broadcasts registered in the manifest file via the component name set in the Intent, and stores them in the receivers variable
  2. In conjunction with the broadcast registration analysis in the previous section, note 2 shows that registered broadcasts are placed in AMS’s mReceiverResolver object, so the broadcast receivers that need to receive broadcasts are stored in the registeredReceivers variable
  3. Considering the above two points, the analysis of the receivers variable stores the static broadcast, and the registeredReceivers variable stores the dynamic broadcast
  4. ReplacePending ending is true if the broadcast is not received at the end of the broadcast. The broadcast is not received at the end of the broadcast, but is saved to the dispatch queue and sent to the broadcast receiver through the Handler of the message mechanism. Replaces the old broadcast message with the current one
  5. Note 4: AMS wraps BroadcastRecord objects around broadcast receivers to be processed
  6. Combined with 6, 7, 8, if not to replace the current news, instructions are the latest to send messages, call the enqueueParallelBroadcastLocked to broadcast from the set of stored in the message queue BroadcastQueue mParallelBroadcasts, That is, save out-of-order broadcasts; The scheduleBroadcastsLocked method of the broadcast message queue then sends messages to the broadcast receiver
  7. Finally, comment 9 sets the current dynamic broadcast save object to NULL after it is sent
  • Then you see the scheduleBroadcastsLocked method of the BroadcastQueue

frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

 public void scheduleBroadcastsLocked() {...if (mBroadcastsScheduled) {
            return; } mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this)); //1 mBroadcastsScheduled =true;
    }
Copy the code
  • The BroadcastQueue Handler is used to send the BROADCAST_INTENT_MSG message, and the mBroadcastsScheduled value is set to true. It can be seen from this that the sending and receiving of broadcast is performed asynchronously, and the broadcast will not wait for the completion of AMS processing before being forwarded to the receiver. Then you see Hnadler’s message processing

frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

final BroadcastHandler mHandler;

    private final class BroadcastHandler extends Handler {
        public BroadcastHandler(Looper looper) {
            super(looper, null, true);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BROADCAST_INTENT_MSG: {
                    .....
                    processNextBroadcast(true); / / 1}break; . }}}Copy the code
  • Note 1 from the source code above, just call the processNextBroadcast method, and read on

frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

final void processNextBroadcast(boolean fromMsg) {
        synchronized (mService) {
            processNextBroadcastLocked(fromMsg, false); } } final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) { BroadcastRecord r; .if (fromMsg) {
            mBroadcastsScheduled = false; //1 } // First, deliver any non-serialized broadcasts right away.while(mParallelBroadcasts.size() > 0) { r = mParallelBroadcasts.remove(0); / / 2... final int N = r.receivers.size(); .for(int i=0; i<N; i++) { Object target = r.receivers.get(i); / / 3... deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target,false, i); //4 } addBroadcastToHistoryLocked(r); . }... }Copy the code
  • From above source code
  1. {mBroadcastsScheduled}} {mBroadcastsScheduled has been set to false, indicating that the BROADCAST_INTENT_MSG message sent by this Handler has already been processed
  2. Broadcast receiver BroadcastRecord object (mParallelBroadcasts) Then comment 3 obtain BroadcastFilter is registered in the radio receiver described by deliverToRegisteredReceiverLocked method for processing, along with all the method which the characteristic of the broadcast messages sent to the receiver

frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

private void deliverToRegisteredReceiverLocked(BroadcastRecord r, BroadcastFilter filter, boolean ordered, int index) { ....... try { ....... performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky, r.userId); . } } catch (RemoteException e) { ....... }Copy the code
  • Call BroadcastQueue’s performReceiveLocked method to send broadcasts described by BroadcastRecord objects to broadcast receivers corresponding to BroadcastFilter

frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

 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,dothe 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.getReportedProcState()); //1 } catch (RemoteException ex) { ...... }... }else{ receiver.performReceive(intent, resultCode, data, extras, ordered, sticky, sendingUser); / / 2}}Copy the code
  • From above source code
  1. The InnerReceiver is stored in AMS as a local Binder object. If the Activity component application process exists, Is with the aid of an AIDL ActivityThread call notes 1 application process. ApplicationThread scheduleRegisteredReceiver method, Otherwise, the associated performReceive method that implements the IIntentReceiver agent reference is invoked directly in comment 2.
  2. If it should process will describe the target radio receiver IIntentReceiver call scheduleRegisteredReceiver method as a parameter, so into the back registered broadcast application process

The broadcast receiver calls back the onReceive method to receive the message

frameworks/base/core/java/android/app/ActivityThread.java

// This function exists to make sure all receiver dispatching is
        // correctly ordered, since these are one-way calls and the binder driver
        // applies transaction ordering per object for such calls.
        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); / / 1}Copy the code
  • By the previous analysis, ApplicationThread source scheduleRegisteredReceiver method as shown above, the above source call performReceive method is actually InnerReceiver method, and then to look down

frameworks/base/core/java/android/app/LoadedApk.java

final static class InnerReceiver extends IIntentReceiver.Stub { final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher; final LoadedApk.ReceiverDispatcher mStrongRef; InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) { mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd); mStrongRef = strong ? rd : null; } @Override public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { final LoadedApk.ReceiverDispatcher rd; . rd = mDispatcher.get(); .if(rd ! = null) { rd.performReceive(intent, resultCode, data, extras, ordered, sticky, sendingUser); / / 1}... }}}Copy the code
  • See note 1, rd. Access points to is LoadedApk ReceiverDispatcher object, he by a weak reference mDispatcher object access, Finally, the performReceive method of ReceiverDispatcher is called to handle the broadcast message described by the Intent, and we move on

frameworks/base/core/java/android/app/LoadedApk.java

static final class ReceiverDispatcher { ...... final Handler mActivityThread; . 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); / / 1if (intent == null || !mActivityThread.post(args.getRunnable())//2 {
                .......
            }
        }
Copy the code
  1. From the source code above, comment 1 creates the Args object and wraps the Intent that describes the broadcast message
  2. Note also that mActivityThread points to the internal Handler class H that the ActivityThread object represents for the main thread and executes Runnable as shown below

frameworks/base/core/java/android/app/LoadedApk.java

final class Args extends BroadcastReceiver.PendingResult { private Intent mCurIntent; private final boolean mOrdered; private boolean mDispatched; private boolean mRunCalled; . public final RunnablegetRunnable() {
                return() -> { final BroadcastReceiver receiver = mReceiver; . final IActivityManager mgr = ActivityManager.getService(); final Intent intent = mCurIntent; mCurIntent = null; mDispatched =true;
                    mRunCalled = true; . try { ..... receiver.onReceive(mContext, intent); //1 } catch (Exception e) { ....... }... }; }}Copy the code
  • The BroadcastReceiver’s onReceive method is called by Runnable’s run method, and the BroadcastReceiver’s onReceive method is called by Runnable’s run method.

Send broadcast sequence diagram

review

  • Broadcast sending and receiving are asynchronous. Communication components can be in the same process or in different processes
  • The registration of a broadcast actually saves the InnerReceiver associated with a broadcast to AMS so that subsequent broadcast messages can be forwarded to the corresponding broadcast receiver
  • It is easy to understand the similarities between the Service binding process and the broadcast registration process
  • The broadcast mechanism is based on the event-driven model of publish and subscribe messages, which is the embodiment of the observer pattern in the source code
  • The broadcast mechanism is mainly used to pass messages between Android components. The Binder mechanism is used to implement the underlying implementation, and its registry relies on AMS, so the broadcast sender does not need to know whether the broadcast receiver exists or not, and the coupling between them is reduced

reference

  • Book “Android system scenario source code analysis” third edition
  • Android 10 source code address