Android O and the Implicit Broadcast Ban

One of the more controversial changes in Android O — for apps with a sufficiently-high targetSdkVersion — is the effective ban on implicit broadcasts. Let’s take a deeper dive into what this means and why we are here.

WTF? (W = What)

On Android O, code like this no longer works the way that you expect:

sendBroadcast(new Intent("this.is.an.implicit.broadcast"));

Normally, this broadcast would be received by all receivers that are registered for that custom action string. Even on O, two sets of receivers will still receive the broadcast:

  • Those whose apps have targetSdkVersion of 25 or lower

  • Those that were registered via registerReceiver() of some already-running process

However, manifest-registered receivers of apps with a higher targetSdkVersion will not receive the broadcast. Instead, a message like this one will appear in LogCat:

04-11 14:12:36.340 753-763/? W/BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.PACKAGE_REMOVED dat=package:com.commonsware.cwac.cam2.demo flg=0x4000010 (has extras) } to com.commonsware.android.sysevents.pkg/.OnPackageChangeReceiver

WTF? (W = Why)

You might think that the concern was tied to the battery, as this seems like another front in the ongoing “war on background processing” that has been going on since Doze mode was introduced in Android 6.0.

As it turns out, battery is of secondary importance. The real reason is process churn.

Quoting a Google engineer, who may or may not be Dianne Hackborn:

To help understand what is going on, I need to clarify that the purpose of this change is not directly related to battery use, but rather to address long-standing issues we have had in the platform where devices that are under memory pressure can get in to bad thrashing states. Very often these states are due to broadcasts: some broadcast or broadcasts are being sent relatively frequently, which a lot of applications are listening to through their manifest (so need to be launched to receive it), but there is not enough RAM to keep all of those app proceses in cache, so the system ends up continually thrashing through processes each time the broadcast is sent.

This is an issue regardless of whether the device is currently plugged in to power. In fact, this can more frequently be an issue on Android TV devices (which are always plugged in to power) because they tend to be fairly tight on RAM!

Certainly, there are plenty of system-sent implicit broadcasts that have lots of registered receivers. Years ago, I ran some code to count how many apps registered for ACTION_BOOT_COMPLETED, and an off-the-shelf device had 70-odd such receivers. Hence, when ACTION_BOOT_COMPLETED goes out, Android needs to fork 70-odd processes, create 70-odd receivers, and deliver 70-odd broadcasts. As a result, process lifetime tends to be fairly short, as Android needs to keep terminating processes to free up system RAM for yet more processes.

(ironically, ACTION_BOOT_COMPLETED is not affected by the ban, as while there are lots of registrants, that broadcast goes out infrequently)

WTF? (W = Who)

This affects:

  • Implicit broadcasts sent by the system, except for a handful of whitelisted ones and those that are actually explicit broadcasts that happen to have an action string

  • Implicit broadcasts sent by apps, intended to be delivered to other apps

  • Implicit broadcasts sent by apps, intended to be delivered only to themselves, based off of old programming patterns that used system broadcasts instead of an in-process event bus (e.g., LocalBroadcastManager)

Most developers will worry most about the system-sent broadcasts, as those are numerous.

WTFW? (W = What’s and Workaround, Respectively)

If you are using broadcasts for communicating between app components within a single process, switch to using LocalBroadcastManager.

If you are using broadcasts for communicating between app components within multiple processes of your own, switch to using explicit broadcasts.

If you are receiving system-sent implicit broadcasts (e.g., ACTION_PACKAGE_ADDED), keep your targetSdkVersion at 25 or lower, until we figure out better workarounds that (hopefully) do not involve polling.

If you are sending implicit broadcasts, you can break through the ban by finding the receivers and sending individual explicit broadcasts instead:

private static void sendImplicitBroadcast(Context ctxt, Intent i) {
  PackageManager pm=ctxt.getPackageManager();
  List<ResolveInfo> matches=pm.queryBroadcastReceivers(i, 0);

  for (ResolveInfo resolveInfo : matches) {
    Intent explicit=new Intent(i);
    ComponentName cn=
      new ComponentName(resolveInfo.activityInfo.applicationInfo.packageName,
        resolveInfo.activityInfo.name);

    explicit.setComponent(cn);
    ctxt.sendBroadcast(explicit);
  }
}

Unfortunately, this brings back the process churn, and if lots of developers do this, there may be reprisals from Google. You might try introducing some delay between the broadcasts, inside the loop, to spread out the impact. However, this starts to get tricky if you spread it out over more than a few seconds (e.g., do you now need an IntentService and a WakeLock? what if your process is terminated before the broadcast loop is completed?).

If you are receiving implicit broadcasts from another app, ask the developer of that app what the plan is for Android O. Perhaps they will use the above technique, or perhaps they will switch to some alternative communications pattern.