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.