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,把所有标签列出来。然后问自己三个问题:

  1. 这个广播是系统广播还是自定义广播?
  2. App是否需要在未启动时接收?
  3. 有没有替代方案?

第二步:分类处理

我一般分成三类:

  • 可删除的: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 知识体系总览

下面这张图,是我梳理的广播接收器兼容性核心逻辑。你看一遍应该就能明白整体脉络:

广播接收器兼容性决策树 广播接收器 Manifest注册 Context注册 Android 8.0+ 隐式广播受限 自定义隐式广播 → 收不到 解决方案:迁移到Context注册 不受隐式广播限制 需注意生命周期管理 必须成对注册/注销 豁免白名单 BOOT_COMPLETED CONNECTIVITY_ACTION 等

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