11、广播接收器兼容性:隐式广播限制,Context注册与Manifest注册的升级策略
广播接收器,说白了就是Android系统里的一种消息监听机制。但到了Android 8.0以后,这东西变得有点「敏感」了。我刚开始接手系统升级项目时,就吃过这个亏——明明广播注册得好好的,升级到Android 9之后,突然收不到消息了。嗯,今天我们就来聊聊这件事。
11.1 隐式广播限制:到底限制了什么?
先搞清楚一个概念:什么是隐式广播?
显式广播,就是明确指定了接收器包名和类名的广播。比如:
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.example.app",
"com.example.app.MyReceiver"));
sendBroadcast(intent);
隐式广播呢,就是只通过Action来匹配,不指定具体接收方:
Intent intent = new Intent("com.example.MY_ACTION");
sendBroadcast(intent);
Android 8.0(API 26)开始,系统对隐式广播做了严格限制——Manifest中静态注册的接收器,基本收不到隐式广播了。为什么?说白了就是为了省电。你想想看,每个App都在Manifest里注册一堆广播,系统每次发广播都要唤醒一堆App,电池扛不住啊。
核心规则:
- Android 8.0+:Manifest注册的接收器,无法接收隐式广播
- Context注册的接收器:不受此限制,照常工作
- 系统广播(如BOOT_COMPLETED、CONNECTIVITY_ACTION等):有白名单豁免
- 显式广播:完全不受影响
我在项目中遇到过一个问题:某个三方SDK在Manifest里注册了自定义Action的广播,Android 7上跑得好好的,升级到Android 9后完全失效。查了半天才发现是隐式广播限制搞的鬼。
11.2 哪些广播被豁免了?
系统也知道一刀切太狠,所以留了一些后门。以下这些广播,即使Android 8.0+也允许Manifest注册:
| 广播Action | 说明 | 我个人的建议 |
|---|---|---|
| BOOT_COMPLETED | 开机完成广播 | 尽量用JobScheduler替代 |
| CONNECTIVITY_ACTION | 网络状态变化 | 建议用NetworkCallback |
| ACTION_POWER_CONNECTED/DISCONNECTED | 电源插拔 | 这个可以保留 |
| ACTION_BATTERY_LOW/OK | 电量变化 | 注意不要频繁触发 |
| ACTION_NEW_OUTGOING_CALL | 去电广播 | 权限敏感,慎用 |
| ACTION_USER_PRESENT | 用户解锁 | 替代方案:KeyguardManager |
但要注意,这个豁免列表每个Android版本都在变。我记得Android 12又收紧了一批。所以我的习惯是:能不依赖Manifest广播就别依赖。
11.3 Context注册 vs Manifest注册:怎么选?
这个问题其实没有标准答案,但根据我的经验,可以按这个思路来:
什么时候用Manifest注册?
- App需要监听开机广播,在用户未打开App时启动服务
- 系统级广播,且在白名单内
- 显式广播(指定了包名和类名)
- App有系统权限,或者本身就是系统应用
什么时候用Context注册?
- App已经启动,需要在运行时监听广播
- 自定义的隐式广播
- 只需要在App前台时接收的广播
- 与Activity或Service生命周期绑定的广播
我的个人习惯:
能用Context注册就别用Manifest。Context注册的生命周期可控,不会出现「App都关了还在收广播」的尴尬情况。而且Context注册不受隐式广播限制,兼容性最好。
11.4 升级策略:从Manifest迁移到Context注册
如果你正在做一个系统升级项目,从Android 7升级到Android 10+,那广播接收器这块肯定要动刀。我整理了一套迁移策略:
第一步:梳理所有Manifest广播
打开AndroidManifest.xml,把所有
- 这个广播是系统广播还是自定义广播?
- App是否需要在未启动时接收?
- 有没有替代方案?
第二步:分类处理
我一般分成三类:
- 可删除的:App启动后才会用到的广播,直接删掉Manifest注册,改成Context注册
- 需保留的:系统白名单内的广播,且确实需要在App未启动时触发
- 需改造的:自定义隐式广播,要么改成显式广播,要么改成Context注册
第三步:代码改造示例
这是Manifest注册的典型写法:
<!-- AndroidManifest.xml -->
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="com.example.MY_CUSTOM_ACTION" />
</intent-filter>
</receiver>
改成Context注册后:
// 在Activity或Service的onCreate中注册
private MyReceiver receiver = new MyReceiver();
private IntentFilter filter = new IntentFilter("com.example.MY_CUSTOM_ACTION");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerReceiver(receiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
注意:
Context注册一定要成对出现!register和unregister必须匹配。我曾经见过一个同事,在Activity里注册了广播,但onDestroy里忘了注销,结果Activity销毁后广播还在收,导致内存泄漏。这个问题在系统升级测试中特别容易暴露出来。
11.5 知识体系总览
下面这张图,是我梳理的广播接收器兼容性核心逻辑。你看一遍应该就能明白整体脉络:
11.6 避坑指南
最后,分享几个我踩过的坑:
坑1:不要依赖系统广播的时序
我曾经在项目里依赖BOOT_COMPLETED和CONNECTIVITY_ACTION的先后顺序,结果不同厂商的ROM里顺序完全不一样。后来改成用JobScheduler,世界清净了。
坑2:Context注册别忘了取消
这个前面提过,但值得再说一遍。尤其是Fragment里注册广播,Fragment的生命周期比Activity更复杂,很容易漏掉unregister。
坑3:Android 12的广播限制又收紧了
Android 12开始,连一些系统广播的Manifest注册也被限制了。比如ACTION_SCREEN_ON/OFF,现在必须用Context注册。所以做系统升级项目时,一定要针对目标版本做全面测试。
嗯,广播接收器这块其实不难,但细节特别多。你只要记住一个原则:能用Context注册就别用Manifest,能用JobScheduler就别用广播。这样兼容性问题能少一大半。
公众号:蓝海资料掘金营,微信deep3321