BrocastReceiver process details

Keywords: Java Android

Registration of broadcasts is divided into static registration and dynamic registration. Static registration configures the receiver node in the xml and parses the xml file after the application starts. The specific registration process is handled by the PackagerManagerService. This article focuses on the dynamic registration of broadcasts, i.e. calling the registerReceiver method in Java code to register broadcasts. The specific use of this method in development is not described here.

1. Register Broadcast

Whether you register a broadcast in an Activity or a Service, you call the ContextWrapper$registerReceiver method. ContextWrapper is a subclass of Context, in which the abstract method registerReceiver is defined and implemented in the subclass ContextWrapper.

ContextWrapper.java

@Override
    public Intent registerReceiver(
        BroadcastReceiver receiver, IntentFilter filter) {
        return mBase.registerReceiver(receiver, filter);
    }

ContextImpl.java

 @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        return registerReceiver(receiver, filter, null, null);
    }

    //Continue to view...

    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
            String broadcastPermission, Handler scheduler) {
        return registerReceiverInternal(receiver, getUserId(),
                filter, broadcastPermission, scheduler, getOuterContext(), 0);
    }

    //Continue to view...

    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) {
                    scheduler = mMainThread.getHandler();
                }
                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();
            }
        }
        
	//...code

            final Intent intent = ActivityManager.getService().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
                    broadcastPermission, userId, flags);           
          
        //...code
    }

The main work here is to encapsulate the BroadcastReceiver object as an object of type InnerReceiver, an internal class of ReceiverDispatcher, through the variable mPackageInfo. Because the registration process is cross-process communication between application processes and AMS, BroadcastReceiver, as an Android component, cannot communicate directly across processes, so it needs to be relayed through InnerReceiver, a Binder class. This is similar to encapsulating a ServiceConnection as an internal class of a ServiceDispatcher when bindService is used to communicate across processes.

ActivityManagerService.java

public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId,
            int flags) {
    
    //...code
    
            if (callerApp != null && (callerApp.thread == null
                    || callerApp.thread.asBinder() != caller.asBinder())) {
                // Original caller already died
                return null;
            }
            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);
            }

     //...code

            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);

    //...code
	    	    	    
}

AMS eventually stores the remote objects InnerReceiver and IntentFilter, and the entire broadcast registration process is complete.

2. Send a broadcast

As with dynamically registered broadcasts, sending broadcasts actually calls the ContextWrapper$sendBroadcast method;
ContextWrapper.java

  public void sendBroadcast(Intent intent) {
        mBase.sendBroadcast(intent);
    }

The variable mBase implementation class is ContextImpl, just like dynamically registered broadcasts.

ContextImpl.java

public void sendBroadcast(Intent intent) {
    //...

    ActivityManager.getService().broadcastIntent(
                    mMainThread.getApplicationThread(), intent, resolvedType, null,
                    Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
                    getUserId());
   
    //...		 
}

Inside the broadcastIntentLocked, matching broadcasters are found based on IntentFilter and a series of filters are applied. The broadcastReceiver that eventually meets the criteria is added to the broadcastQueue.

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) {        
		//...

        synchronized(this) {
            intent = verifyBroadcastLocked(intent);
		
        //...

            int res = broadcastIntentLocked(callerApp,
                    callerApp != null ? callerApp.info.packageName : null,
                    intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
                    requiredPermissions, appOp, bOptions, serialized, sticky,
                    callingPid, callingUid, userId);
        //...     
            return res;
        }
    }
    
	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) {
	
	    //...

	    // By default broadcasts do not go to stopped apps.
            intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);

	    //...

	    registeredReceivers = mReceiverResolver.queryIntent(intent,
                        resolvedType, false /*defaultOnly*/, userId);

            //...

	    BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                    callerPackage, callingPid, callingUid, callerInstantApp, resolvedType,
                    requiredPermissions, appOp, brOptions, registeredReceivers, resultTo,
                    resultCode, resultData, resultExtras, ordered, sticky, false, userId);

	    //...

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

}

BroadcastQueue then sends the broadcast to the corresponding broadcast recipient.

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;
}

The scheduleBroadcastsLocked method of BroadcastQueue does not immediately send a broadcast, but instead sends a BROADCAST_INTENT_MSG type message, the processNextBroadcast method is called when BroadcastQueue receives the message.

ProceNextBroadcast method of BroadcastQueue:

   // First, deliver any non-serialized broadcasts right away.
    while (mParallelBroadcasts.size() > 0) {
        r = mParallelBroadcasts.remove(0);
        r.dispatchTime = SystemClock.uptimeMillis();
        r.dispatchClockTime = System.currentTimeMillis();

        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
            Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
                System.identityHashCode(r));
            Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
                System.identityHashCode(r));
        }

        final int N = r.receivers.size();
        if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
                + mQueueName + "] " + r);
        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);
    }

You can see that broadcasts in the collection are traversed and sent to all their corresponding receivers, using the deliverToRegisteredReceiverLocked method implementation. DeliverToRegisteredReceiverLocked is responsible for sending the broadcast to a specific receiver, and the performReceiveLocked method is called internally to complete the sending process.

  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.getReportedProcState());
                // 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);
        }
    }

ApplicationThread.class

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);
}

Here receiver is the Binder object ReceiverDispatcher.InnerReceiver encapsulated by the BroadcastReceiver object at the time of registration, and performReceive is implemented as follows:

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);
        }
    }
}

In the code above, an Args object is created that implements the Runnable interface and uses the ActivityThread post method, which has several lines of code in the run method of Args:

receiver.setPendingResult(this);
receiver.onReceive(mContext, intent);

At this point receiver's onReceive has been called in the main thread, and at this point, the entire send process is finished

Posted by harman on Sat, 04 Dec 2021 09:42:02 -0800