What exactly did the registerReceiver, sendBroadcast, and onReceive systems for analyzing broadcasts do?

What exactly did the registerReceiver, sendBroadcast, and onReceive systems for analyzing broadcasts do?

Introduction

BroadcastReceiver is one of the four major components of Android. It acts as an important communication method within the application and between processes. It can deliver a message to the subscribed broadcast receiver in the form of broadcast. Let's analyze the broadcast below. What exactly did the Android source code do after registering to receive the message?

Source code analysis

registerReceiver

Timing diagram

Code explanation

We follow the above sequence diagram to explain the code

  1. First broadcast receiver in MainActivity registerReceiver

    public class MainActivity extends Activity {
          @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
          	//
    				registerReceiver(new ReceiverB(),filterB);
        }
    ... ...
    }
     

Click on registerReceiver and we find that it is not a method in Activity, but the parent class ContextWrapper of Activity

  1. ContextWrapper registerReceiver

    public class Activity extends ContextThemeWrapper
            implements LayoutInflater.Factory2,
            Window.Callback, KeyEvent.Callback,
            OnCreateContextMenuListener, ComponentCallbacks2,
            Window.OnWindowDismissedCallback, WindowControllerCallback,
            AutofillManager.AutofillClient {
              
              .... ...
              
            }
     
    public class ContextThemeWrapper extends ContextWrapper {
      .... ...
    }
     
    public class ContextWrapper extends Context {
          @Override
        public Intent registerReceiver(
            BroadcastReceiver receiver, IntentFilter filter) {
            return mBase.registerReceiver(receiver, filter);
        }
    }
     

    The member variable mBase here is Context. I know that ContextImp inherits Context after reading the source code of Application startup. Then let's look at the specific registerReceiver method of the inherited class.

  2. ContextImp registerReceiver

    class ContextImpl extends Context {   
    @Override
        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
                String broadcastPermission, Handler scheduler, int flags) {
            return registerReceiverInternal(receiver, getUserId(),
                    filter, broadcastPermission, scheduler, getOuterContext(), flags);
        }
    }
     
  3. Continue to see registerReceiverInternal

        private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
                IntentFilter filter, String broadcastPermission,
                Handler scheduler, Context context, int flags) {
            IIntentReceiver rd = null;
            if (receiver != null) {
                if (mPackageInfo != null && context != null) {
                    if (scheduler == null) {
                        // ActivityThread H  
                        scheduler = mMainThread.getHandler();
                    }
                    // IIntentReceiver   AMS   Handler  
                    rd = mPackageInfo.getReceiverDispatcher(
                        receiver, context, scheduler,
                        mMainThread.getInstrumentation(), true);
                } else {
                    if (scheduler == null) {
                        scheduler = mMainThread.getHandler();
                    }
                    rd = new LoadedApk.ReceiverDispatcher(
                            receiver, context, scheduler, null, true).getIIntentReceiver();
                }
            }
            try {
                // AMS   registerReceiver 
                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) {
                throw e.rethrowFromSystemServer();
            }
        }
     

    The function of registering the broadcast receiver finally enters the registerReceiverInternal function of ContextImpl, where the member variable mPackageInfo is an instance of LoadApk, which is responsible for processing broadcast reception.

  4. ActivityThread H

    The Handler obtained by mMainThread.getHandler() is used to distribute the broadcast sent by AMS.

     private class H extends Handler {
       ...
       public void handleMessage(Message msg) {
       case RECEIVER:                
            handleReceiver((ReceiverData)msg.obj);
                        maybeSnapshot();
                       
         break;
       }
       
       ....
     }
     
  5. Continue to look at handleReceiver

        private void handleReceiver(ReceiverData data) {
    				....
            
            // Application  
            Application app;
            //
            BroadcastReceiver receiver;
            // Context
            ContextImpl context;
            try {
                // Applicaiton
                app = packageInfo.makeApplication(false, mInstrumentation);
                //
                context = (ContextImpl) app.getBaseContext();
                if (data.info.splitName != null) {
                    context = (ContextImpl) context.createContextForSplit(data.info.splitName);
                }
                java.lang.ClassLoader cl = context.getClassLoader();
                data.intent.setExtrasClassLoader(cl);
                data.intent.prepareToEnterProcess();
                data.setExtrasClassLoader(cl);
                //
                receiver = (BroadcastReceiver)cl.loadClass(component).newInstance();
            } catch (Exception e) {
            try {
                if (localLOGV) Slog.v(
                sCurrentBroadcastIntent.set(data.intent);
                receiver.setPendingResult(data);
                //onReceive  
            receiver.onReceive(context.getReceiverRestrictedContext(),
                        data.intent);
            } catch (Exception e) {
               ...
            } finally {
                sCurrentBroadcastIntent.set(null);
            }
    				....
        }
     
  6. mPackageInfo.getReceiverDispatcher function implementation

    //LoadeApk
        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);
                    if (registered) {
                        if (map == null) {
                            map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
                            //Context   key,map   values   mReveivers  
                            mReceivers.put(context, map);
                        }
                        map.put(r, rd);
                    }
                } else {
                    rd.validate(context, handler);
                }
                rd.mForgotten = false;
                return rd.getIIntentReceiver();
            }
        }
     

    In the getReceivcerDispatcher function in the LoadedAPK class, first look at whether r has been instantiated, if not, create one, and save it in an HM collection with r as the key value, and this map is stored in mReceivers, In this way, as long as an Activity and BroadcastReceiver are given, you can check whether there is a corresponding broadcast receiver publisher in LoadedAPK.

    Now after returning to the ContextImpl.registerReceiverInternal function, after obtaining the Binder object of the IIntentReceiver type, it starts to register in the AMS. The specific code is shown in the following small points.

  7. AMS registerReceiver

        /**
         *   Binder  
         * @return Intent
         */
        public Intent registerReceiver(IApplicationThread caller, String callerPackage,
                IIntentReceiver receiver, IntentFilter filter, String permission, int userId,
                int flags) {
            enforceNotIsolatedCaller("registerReceiver");
            ArrayList<Intent> stickyIntents = null;
            ProcessRecord callerApp = null;
            final boolean visibleToInstantApps
                    = (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
            int callingUid;
            int callingPid;
            boolean instantApp;
            synchronized(this) {
                if (caller != null) {
                    //1.  ProcessRecord
                    callerApp = getRecordForAppLocked(caller);
                    if (callerApp == null) {
    							... ...
                    }
       
                  		... ...
    
                //2.   Action   sticky  
                Iterator<String> actions = filter.actionsIterator();
                if (actions == null) {
                    ArrayList<String> noAction = new ArrayList<String>(1);
                    noAction.add(null);
                    actions = noAction.iterator();
                }
    
          		... ...
                
                //3.   ReceiverList
                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);
                } else if (rl.uid != callingUid) {
                   		... ...
                }
    
                //4.   BroadcastFilter   ReceiverList  
                BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
                        permission, callingUid, userId, instantApp, visibleToInstantApps);
                rl.add(bf);
                if (!bf.debugCheck()) {
                    Slog.w(TAG, "==> For Dynamic broadcast");
                }
                mReceiverResolver.addFilter(bf);
    
             		... ...
    
                return sticky;
            }
        }
     
    • According to Note 1 above, get the pid and uid corresponding to the process;

    • Note 2 Get all the Actions of IntentFilter;

    • Note 3 The receiver of the broadcast receiver is saved in a ReceiverList. The host process of this list is rl.app, which is the process where MainActivity is located.

    • Note 4 is just to save the broadcast receiver, but it has not been associated with the filter, here is a BroadcastFilter to associate the broadcast receiver list rl with the filter, and then save it in the AMS member variable mReceiverResolver, so that the broadcast will be received The receiver and the receiver filter of the broadcast type to be received are stored in AMS, and the corresponding broadcast can be received and processed later.

Next we look at sendBroadcast().

onReceive

Timing diagram

  1. The Activity sends a broadcast through sendBroadcast and finally the Binder sends it to AMS. AMS finds the corresponding broadcast receiver according to the action type of the broadcast, and then broadcasts the broadcast into its own message queue to complete the first part of the broadcast asynchronous distribution.

        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) {
            intent = new Intent(intent);
          
    			...... ....
            
            if (intent.getComponent() == null) {
    
              	...... ....
                  
                } else {
                    // Intent   BroadcastFilter  
                    registeredReceivers = mReceiverResolver.queryIntent(intent,
                            resolvedType, false /*defaultOnly*/, userId);
                }
            }
    
          	...... ....
              
                if (!replaced) {
                    queue.enqueueParallelBroadcastLocked(r);
                    //
                    queue.scheduleBroadcastsLocked();
                }
                registeredReceivers = null;
                NR = 0;
            }
    
                 	...... ....
    
            return ActivityManager.BROADCAST_SUCCESS;
        }
     
  2. AMS processes the broadcast in the message loop, and distributes the broadcast to the registered ReceiverDispatch through the Binder mechanism, and ReceiverDispatch broadcasts the broadcast into the message queue of the process where MainActivity is located, completing the second part of asynchronous message distribution.

        public void scheduleBroadcastsLocked() {
            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
                    + mQueueName + "]: current="
                    + mBroadcastsScheduled);
    
            if (mBroadcastsScheduled) {
                return;
            }
            // Handler  
            mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
            mBroadcastsScheduled = true;
        }
     
        private final class BroadcastHandler extends Handler {
    			......
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case BROADCAST_INTENT_MSG: {
                        if (DEBUG_BROADCAST) Slog.v(
                                TAG_BROADCAST, "Received BROADCAST_INTENT_MSG");
                        //
                        processNextBroadcast(true);
                    } break;
                   ....
                }
            }
        }
     
  3. The internal class Args of ReceiverDispatch processes this broadcast in the thread message loop where MainActivity is located, and finally distributes this broadcast to the onReceiver of the registered Receiver instance for processing.

    public final class LoadedApk {
      ...
         static final class ReceiverDispatcher {
           
               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);
                            // onReceiver
                            receiver.onReceive(mContext, intent);
                        } catch (Exception e) {
                          ...
                    };
                }
            }
         }
      
      ...
    }
     

summary

This is almost the end of registration and receiving source code analysis. In short, broadcasting is a subscription-publishing process. BroadcastReceiver is stored through some maps. The key is to encapsulate the information of these broadcasts, such as Action, when a broadcast is published. Query the BroadcastReceiver registered with the broadcast IntentFilter through AMS to this map, and then distribute the broadcast to each subscribed object through ReceiverDispatch, thus completing the entire communication process.

Thank you for reading. Thank you!