专栏首页wOw的Android小站[Android] Android O 广播限制

[Android] Android O 广播限制

问题

因为项目需要迁移到8.0平台,发现有一个系统应用打不开,从log发现如下描述:

BroadcastQueue: Background execution not allowed: receiving Intent {...}

实际上,在Android O,像下面的隐式广播都不再起作用:

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

通常来讲,这个广播会被所有注册这个action的receiver接收到。即便是在Android O版本,还有两类receiver仍然会接收这个广播:

  1. targetSdkVersion <= 25的应用
  2. 通过registerReceiver()注册的并有已经启动的进程的应用

但是通过Manifest文件静态注册的BroadcastReceiver是不会接收这个广播的,反而会打印出一些开头提到的log

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

症结

这个问题看起来可能与电池有关,因为自从Android 6.0中引入Doze模式以来,各种后台处理会引起类似现象。但事实上,电池问题是次要的,真正的原因是进程混乱。

对此谷歌工程师是这样反馈的:

为了帮助了解发生了什么,我需要澄清一下,此更改的目的并不直接与电池使用有关,而是要解决平台中长期存在的问题:处于内存压力下的设备可能会进入错误的内存抖动状态。这些状态通常是由于广播引起的:某些广播或广播的发送相对频繁,许多应用程序正在通过清单进行监听(因此需要启动以接收它),但是没有足够的RAM来保留所有状态这些应用程序的进程在缓存中进行处理,因此,每次发送广播时,系统最终都会不断地在各个进程中进行跳动。无论设备当前是否已接通电源,这都是一个问题。实际上,这在Android TV设备(始终插上电源)上可能经常会成为问题,因为它们的RAM往往很紧!

这就好理解了,尤其是很多开发者为了唤醒自己的app,注册了非常多的静态广播(我看过喜马拉雅注册了100多个静态广播,毫无下限),每当系统发送一个广播时,就会出现唤醒很多app的情况,又因为系统内存有限,启动一些app后另一些app又被杀掉。这样不仅耗电,还影响使用性能。

Android O 广播限制

如果应用注册了BroadcastReceiver,则每次发送广播的时候,应用的BroadcastReceiver都会消耗资源。如果多个应用注册了接收基于系统事件的广播,就会出现,触发广播的系统事件会导致所有应用快速的连续消耗资源,从而降低用户体验。为了缓解这个问题,Android N对广播施加了一些限制,而Android O让这些限制更加严格。

Android N做的限制

  • Android 7.0 及其更高版本不再接收 CONNECTIVITY_ACTION 的静态广播(在清单注册的receiver)。但是如果是通过 Context.registerReceiver() 注册的动态广播,相关BroadcastReceiver还是可以接收到 CONNECTIVITY_ACTION 广播。
  • 应用无法再发送或者接收 ACTION_NEW_PICTUREACTION_NEW_VIDEO 广播了。这项优化影响所有的App,不只是针对Android 7.0。

Android O进一步的限制

  • 在Android 8.0 或更高版本的应用无法继续在其AndroidManifest中为隐式广播注册BroadcastReceiver。 隐式广播是一种不专门针对该应用的广播。 例如,ACTION_PACKAGE_REPLACED 就是一种隐式广播,因为该广播将被发送给所有已注册侦听器,让后者知道设备上的某些软件包已被替换。 不过,ACTION_MY_PACKAGE_REPLACED 不是隐式广播,因为不管已为该广播注册侦听器的其他应用有多少,它都会只被发送给软件包已被替换的应用。
  • 应用可以继续在它们的清单中注册显式广播。
  • 应用可以在运行时使用 Context.registerReceiver() 为任意广播(不管是隐式还是显式)注册接收器。
  • 需要签名权限的广播不受此限制所限,因为这些广播只会发送到使用相同证书签名的应用,而不是发送到设备上的所有应用。

在许多情况下,之前注册隐式广播的应用使用 JobScheduler 作业可以获得类似的功能。 例如,一款社交照片应用可能需要不时地执行数据清理,并且倾向于在设备连接到充电器时执行此操作。 之前,应用已经在清单中为 ACTION_POWER_CONNECTED 注册了一个接收器;当应用接收到该广播时,它会检查清理是否必要。 为了迁移到 Android 8.0 或更高版本,应用将该接收器从其清单中移除。 应用将清理作业安排在设备处于空闲状态和充电时运行。

例外的隐式广播

很多隐式广播当前已不受此限制所限。 应用可以继续在其清单中为这些广播注册接收器,不管应用适配哪个 API 级别。

注意:即使这些隐式广播仍然可以在后台工作,但你应该尽量避免对它们注册监听。

  • ACTION_LOCKED_BOOT_COMPLETED,ACTION_BOOT_COMPLETED 因为这些广播只在开机时发送一次,并且很多app需要接收这个广播来安排作业等操作。
  • ACTION_USER_INITIALIZE,”android.intent.action.USER_ADDED”,”android.intent.action.USER_REMOVED” 这些广播被privileged权限所保护,大多数普通app并收不到这些广播。
  • “android.intent.action.TIME_SET”, ACTION_TIMEZONE_CHANGED, ACTION_NEXT_ALARM_CLOCK_CHANGED 当时间、时区或闹钟有变化时,时钟应用会需要接收这些广播以更新时钟。
  • ACTION_LOCALE_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_ACCOUNT_REMOVED 拥有帐户可见性的应用在删除帐户后会收到此广播。 如果这是应用程序需要执行的唯一帐户更改,则强烈建议应用程序使用此广播,而不要使用不建议使用的LOGIN_ACCOUNTS_CHANGED_ACTION
  • ACTION_PACKAGE_DATA_CLEARED 仅当用户从“设置”中明确清除其数据时才发送,因此广播接收器不太可能严重影响用户体验。
  • ACTION_PACKAGE_FULLY_REMOVED 某些应用可能需要在删除另一个软件包后更新其存储的数据; 对于这些应用,没有什么好办法可以注册此广播。 Note: 其他与包相关的广播(例如,ACTION_PACKAGE_REPLACED)不受新限制。 这些广播非常普遍,因此可能会对性能产生影响,因此将其排除在外。
  • ACTION_NEW_OUTGOING_CALL 响应用户发出呼叫而采取行动的应用需要接收此广播。
  • ACTION_DEVICE_OWNER_CHANGED 该广播不是经常发送; 一些应用程序需要接收它,以便他们知道设备的安全状态已更改。
  • ACTION_EVENT_REMINDER 由calendar provider发送,以将事件提醒发布到日历应用程序。 由于日历提供程序不知道日历应用程序是什么,因此此广播必须是隐式的。
  • ACTION_MEDIA_MOUNTED,ACTION_MEDIA_CHECKING,ACTION_MEDIA_UNMOUNTED,ACTION_MEDIA_EJECT,ACTION_MEDIA_UNMOUNTABLE,ACTION_MEDIA_REMOVED,ACTION_MEDIA_BAD_REMOVAL 这些广播是由于用户与设备的物理交互(安装或删除存储卷)或作为启动初始化的一部分(随着可用卷被挂载)而发送的,因此它们并不常见,通常在用户的控制之下 。
  • SMS_RECEIVED_ACTION,WAP_PUSH_RECEIVED_ACTION SMS收件人apps依赖这些广播。

Android O问题的解决方案

检查在应用的清单中定义的BroadcastReceiver。 如果清单里为显式广播声明了接收器,则必须予以替换。 可能的解决方法包括:

  • 通过调用 Context.registerReceiver() 动态创建BroadcastReceiver而不是在清单中声明一个静态的BroadcastReceiver。
  • 使用JobSchedular检查条件是否会触发隐式广播。
  • 如果在单个进程中使用广播在应用程序组件之间进行通信,请切换为使用LocalBroadcastManager。
  • 如果使用广播在自己的多个进程中的应用程序组件之间进行通信,请切换为使用显式广播。
  • 如果你想接收系统发送的隐式广播(例如ACTION_PACKAGE_ADDED),建议保持targetSdkVersion为25或更低。
  • 如果非要发送隐式广播,则可以通过查找接收方并发送各个显式广播来突破禁令: 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); } }

官方文档建议:在大多数情况下,应用都可以使用 JobScheduler 克服这些限制。 这种方法允许应用安排其在未活跃运行时执行工作,不过仍能够使系统可以在不影响用户体验的情况下安排这些作业。 Android 8.0 提供针对 JobScheduler 的多项改进,让用户可以更轻松地使用计划作业取代 Service 和BroadcastReceiver: JobScheduler 改进。后面会整理一篇JobScheduler继续跟进这个问题。

参考

https://commonsware.com/blog/2017/04/11/android-o-implicit-broadcast-ban.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [设计模式]之二十:模板方法模式

    定义一个操作中算法的骨架,将一些步骤延迟到子类中。模板方法使得子类可以不改变算法的结果即可重定义该算法的某些特定步骤。

    wOw
  • [Java] 使用EnumSet代替位运算简化代码逻辑

    然后代码逻辑里是大量的成员变量的判断,显得非常臃肿而且难读懂,大量的if-else判断让代码逻辑很脆弱,稍微一个情况没考虑好就会出现难以排查的bug。

    wOw
  • [Objective-C]用Block实现链式编程

    看这篇博客时最快让你上手ReactiveCocoa之基础篇看到作者介绍链式编程那一块,发现自己的钻研精神不足。想想自己使用链式编程也有段时间了,对,就是 Mas...

    wOw
  • 【spring cloud】 网关Zuul(过滤:安全、监控、限流、路由)

    1. 添加依赖 创建项目tcloud-gateway-zuulserver , pom.xml内容如下

    用户5640963
  • 入坑GitHub,先当女装大佬?给这个2200+星的项目跪了……

    56名贡献者在这个名为Dress的项目里集结,上传了百余张女装照,收获了2200余颗星。

    量子位
  • SAS | 如何网络爬虫抓取网页数据

    本人刚刚完成SAS正则表达式的学习,初学SAS网络爬虫,看到过一些前辈大牛们爬虫程序,感觉很有趣。现在结合实际例子,浅谈一下怎么做一些最基本的网页数据抓取。第一...

    CDA数据分析师
  • Spring Boot 使用 Druid 连接池

    Spring Boot 1.x 版本中,默认使用的数据库连接池为:Tomcat JDBC;到了 Spring Boot 2.x,也切换到了更高性能的 Hikar...

    happyJared
  • Java每日一练(2017/7/16)

    最新通知 ●回复"每日一练"获取以前的题目! ●【新】Ajax知识点视频更新了!(回复【学习视频】获取下载链接) ●答案公布时间:为每期发布题目的第二天 ★【新...

    Java学习
  • Asp .Net Core 读取appsettings.json配置文件

    Asp .Net Core 如何读取appsettings.json配置文件?最近也有学习到如何读取配置文件的,主要是通过 IConfiguratio...

    小世界的野孩子
  • LINQ驱动数据的查询功能

    一、LINQ概念       LINQ是微软在.NetFramework3.5中新加入的语言功能,在语言中以程序代码方式处理集合的能力。 1.1 LINQ VS...

    用户1055830

扫码关注云+社区

领取腾讯云代金券