如果应用注册为接收广播,则在每次发送广播时,应用的接收器都会消耗资源。 如果多个应用注册为接收基于系统事件的广播,则会引发问题:触发广播的系统事件会导致所有应用快速地连续消耗资源,从而降低用户体验。 为了缓解这一问题,Android 7.0(API 级别 24)对广播施加了一些限制,如后台优化中所述。 Android 8.0(API 级别 26)让这些限制更为严格。
隐式广播受限
适配 Android 8.0 或更高版本的应用无法继续在其清单中为隐式广播注册广播接收器。 _隐式广播_是一种不专门针对该应用的广播。
如果当前隐式广播无法分发到静态注册的receiver中,则会打印类似如下的log:
W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.PACKAGE_RESTARTED dat=package:com.android.soundrecorder flg=0x10 (has extras) } to com.xiaomi.misubscreenui/.receiver.LightDeviceStatusReceiver
主要解决方法有如下几种:
- 应用可以继续在它们的清单中注册显式广播。
- 应用可以在运行时使用 Context.registerReceiver() 为任意广播(不管是隐式还是显式)注册接收器。
- 需要签名权限的广播不受此限制所限,因为这些广播只会发送到使用相同证书签名的应用,而不是发送到设备上的所有应用。
总结就是:发送显示广播、动态注册、声明签名权限。
// 静态注册的receiver
if (!skip) {
// android o+直接返回APP_START_MODE_DELAYED_RIGID
final int allowed = mService.getAppStartModeLOSP(
info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
// 不允许启动
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
Slog.w(TAG, "Background execution disabled: receiving "
+ r.intent + " to "
+ component.flattenToShortString());
skip = true;
// 不允许后台接收
} else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
// 隐式广播&不在豁免名单中&不需要签名权限
|| (r.intent.getComponent() == null
&& r.intent.getPackage() == null
&& ((r.intent.getFlags()
& Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
&& !isSignaturePerm(r.requiredPermissions))) {
mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
component.getPackageName());
Slog.w(TAG, "Background execution not allowed: receiving "
+ r.intent + " to "
+ component.flattenToShortString());
skip = true;
}
}
}
隐式广播豁免
受 Android 8.0(API 级别 26)后台执行限制的影响,以 API 级别 26 或更高级别为目标的应用无法再在其清单中注册用于隐式广播的广播接收器。不过,有几种广播目前不受这些限制的约束。无论应用以哪个 API 级别为目标,都可以继续为以下广播注册监听器。
注意:尽管这些隐式广播仍在后台运行,但您应避免为其注册监听器。
也就是如下这些隐式广播处于后台清单文件注册的也能接收到
- ACTION_BOOT_COMPLETED
@BroadcastBehavior(includeBackground = true)
public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
- allow-implicit-broadcast 列表内的intent会在broadcastIntentLocked中添加
if (getBackgroundLaunchBroadcasts().contains(action)) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
}
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
}
豁免的原因这些广播仅在首次启动时发送一次,而且许多应用需要接收此广播以调度作业、闹钟等。
- ACTION_USER_INITIALIZE、“android.intent.action.USER_ADDED”、“android.intent.action.USER_REMOVED”
这些广播受特许权限保护,因此大多数普通应用都无法接收它们。
- “android.intent.action.TIME_SET”、ACTION_TIMEZONE_CHANGED、ACTION_NEXT_ALARM_CLOCK_CHANGED
当时间、时区或闹钟发生更改时,时钟应用可能需要接收这些广播以更新闹钟。
仅在语言区域发生更改时发送,这种情况并不常见。当语言区域发生更改时,应用可能需要更新其数据。
- ACTION_USB_ACCESSORY_ATTACHED、ACTION_USB_ACCESSORY_DETACHED、ACTION_USB_DEVICE_ATTACHED、ACTION_USB_DEVICE_DETACHED
如果某个应用需要了解这些与 USB 有关的事件,除了为广播进行注册,目前还没有很好的替代方法。
- ACTION_CONNECTION_STATE_CHANGED、ACTION_CONNECTION_STATE_CHANGED、ACTION_ACL_CONNECTED、ACTION_ACL_DISCONNECTED
如果应用接收到针对这些蓝牙事件的广播,则用户体验不太可能受到影响。
- ACTION_CARRIER_CONFIG_CHANGED、TelephonyIntents.ACTION_*_SUBSCRIPTION_CHANGED、“TelephonyIntents.SECRET_CODE_ACTION”、ACTION_PHONE_STATE_CHANGED、ACTION_PHONE_ACCOUNT_REGISTERED、ACTION_PHONE_ACCOUNT_UNREGISTERED
OEM 电话应用可能需要接收这些广播。
有些应用需要了解登录帐号的更改,以便为新帐号和已更改的帐号设置调度的操作。
具有帐号可见性的应用会在帐号被移除后收到此广播。如果应用只需要对此帐号更改执行操作,则强烈建议应用使用此广播,而不是使用已弃用的 LOGIN_ACCOUNTS_CHANGED_ACTION。
仅在用户明确清除“设置”中的数据时发送,因此广播接收器不太可能对用户体验造成显著影响。
某些应用可能需要在其他软件包被移除时更新其存储的数据;对于这些应用来说,除了为此广播进行注册,没有很好的替代方法。
注意:其他与软件包相关的广播(例如 ACTION_PACKAGE_REPLACED)未能免受新限制的约束。这些广播很常见,豁免的话可能会影响性能。
应用需要接收此广播,以在用户拨打电话时采取相应操作。
此直播的发送频率不高;某些应用需要接收它来了解设备的安全状态已发生更改。
由日历提供程序发送,以向日历应用发布事件提醒。由于日历提供程序并不知道日历应用是什么,因此此广播必须是隐式的。
- ACTION_MEDIA_MOUNTED、ACTION_MEDIA_CHECKING、ACTION_MEDIA_UNMOUNTED、ACTION_MEDIA_EJECT、ACTION_MEDIA_UNMOUNTABLE、ACTION_MEDIA_REMOVED、ACTION_MEDIA_BAD_REMOVAL
这些广播会在用户与设备的物理互动(安装或移除存储卷)或启动初始化(可用卷装载时)过程中发送,并且通常受用户控制。
短信接收者应用需要依赖这些广播。
常用规避方式
发送方添加上FLAG_RECEIVER_INCLUDE_BACKGROUND flag
注意这个flag是hide api,三方App会访问不了,但是可以指定flag值0x01000000
@SystemApi
public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);