11、BroadcastReceiver 管理:AMS对动态/静态广播的注册、分发、有序广播流程

广播,说白了就是 Android 系统里的一种「喊话」机制。一个组件喊一嗓子,其他感兴趣的组件就能听到。但这里面的门道,比你想象的要深得多。

我刚开始接触广播时,觉得这玩意儿挺简单——注册一下,收到消息,完事。直到有一次线上事故,静态广播没收到,排查了整整两天……嗯,从那以后,我再也不敢小看 BroadcastReceiver 的管理了。

今天我们就来聊聊,AMS 到底是怎么管理这些广播的。从注册到分发,再到有序广播的流转,我把这些年踩过的坑一并告诉你。

11.1 广播的两种注册方式

广播接收器有两种注册方式:动态注册和静态注册。这两种方式,AMS 的处理逻辑完全不同。

11.1.1 动态注册

动态注册是在代码里调用 registerReceiver() 完成的。说白了,就是你的应用主动告诉 AMS:「嘿,我对某个广播感兴趣,有消息了通知我。」

AMS 收到注册请求后,会做以下几件事:

  • 解析 IntentFilter,提取 action、category、data 等信息
  • 创建一个 ReceiverDispatcher 对象,用于管理这个接收器
  • 将接收器信息存入 AMS 的 mRegisteredReceivers 列表中
  • 同时建立 action 到接收器的映射关系,方便快速查找

这里有个关键点:动态注册的接收器,生命周期和注册它的组件绑定。组件销毁了,接收器也就自动注销了。我曾经见过有人忘记在 onDestroy() 里调用 unregisterReceiver(),结果导致内存泄漏,AMS 里残留了一堆无效的接收器。

注意:动态注册的接收器必须在主线程中注册和注销。如果你在子线程里搞,AMS 会直接抛异常。

11.1.2 静态注册

静态注册是在 AndroidManifest.xml 里声明的。这种方式不需要代码参与,应用安装时,PackageManagerService(PMS)就会解析清单文件,把接收器信息存起来。

AMS 在处理静态广播时,会去 PMS 查询所有匹配的接收器。这个过程涉及到跨进程通信,所以性能上比动态注册要慢一些。

我个人习惯是:能动态注册的就别用静态注册。为什么呢?因为 Android 8.0 之后,静态注册的隐式广播大部分都被限制了。除非你声明了特定的权限,或者发送的是显式广播,否则静态接收器根本收不到消息。

核心区别:
  • 动态注册:由 AMS 的 mRegisteredReceivers 管理,生命周期跟随组件
  • 静态注册:由 PMS 解析并存储,AMS 通过 PMS 查询获取

11.2 广播的分发流程

广播的分发,是 AMS 最核心的工作之一。我们来看看,当你调用 sendBroadcast() 时,系统内部到底发生了什么。

11.2.1 发送广播的入口

应用层调用 sendBroadcast() 后,会通过 Binder 调用到 AMS 的 broadcastIntent() 方法。AMS 会做以下检查:

  • 权限检查:发送者是否有权限发送该广播
  • 广播类型检查:是普通广播、有序广播还是粘性广播
  • 接收器匹配:找出所有符合条件的接收器

我记得有一次,我发送了一个自定义广播,结果死活收不到。排查了半天,发现是权限声明的问题。AMS 在分发广播前,会检查发送者和接收者的权限,任何一个不匹配,广播就会被丢弃。

11.2.2 接收器的匹配过程

AMS 匹配接收器时,会分两步走:

  1. 动态接收器匹配:遍历 mRegisteredReceivers,检查 IntentFilter 是否匹配
  2. 静态接收器匹配:通过 PMS 查询所有静态注册的接收器,再做一次过滤

匹配完成后,AMS 会把所有符合条件的接收器封装成一个 BroadcastRecord 对象。这个对象里包含了广播的所有信息,以及待分发的接收器列表。

小技巧:如果你想让广播更快到达,可以优先使用动态注册。因为 AMS 处理动态接收器时,不需要跨进程查询 PMS,速度会快很多。

11.3 有序广播的完整流程

有序广播和普通广播最大的区别在于:接收器是按顺序执行的,而且可以中断广播的传播。

你想想看,普通广播就像在群里发消息,所有人都能同时看到。而有序广播就像开会时一个个发言,每个人说完才能轮到下一个,而且主持人可以随时叫停。

11.3.1 有序广播的注册

有序广播的接收器注册方式和普通广播一样,区别在于发送时调用的是 sendOrderedBroadcast()。AMS 在处理有序广播时,会为每个接收器设置优先级,优先级高的先执行。

优先级在动态注册时通过 IntentFilter 的 setPriority() 设置,静态注册时通过 android:priority 属性设置。数值越大,优先级越高。

11.3.2 分发过程

AMS 分发有序广播时,会按以下步骤执行:

  1. 从 BroadcastRecord 中取出优先级最高的接收器
  2. 通过 Binder 调用接收器的 onReceive() 方法
  3. 等待接收器执行完毕(同步执行)
  4. 检查接收器是否调用了 abortBroadcast()
  5. 如果调用了,停止分发;否则继续下一个接收器

这里有个坑:有序广播的接收器是在主线程中执行的。如果你的 onReceive() 方法耗时太长,会阻塞整个广播的分发流程。我曾经在项目中遇到过,一个接收器做了网络请求,导致后续所有接收器都超时了。

警告:有序广播的 onReceive() 方法最多只能执行 10 秒。超过这个时间,AMS 会认为接收器超时,直接跳过它继续分发。如果你的接收器需要做耗时操作,请使用 goAsync() 或启动 Service。

11.3.3 结果传递

有序广播还有一个特性:接收器可以修改广播的结果数据。通过 setResultData()setResultExtras() 等方法,每个接收器都能修改结果,下一个接收器拿到的是修改后的数据。

这个特性在某些场景下非常有用。比如,你可以让第一个接收器做一些预处理,第二个接收器再基于处理后的数据做业务逻辑。

11.4 核心流程图

下面这张图,是我根据 AMS 源码梳理出来的广播分发流程。你看完应该能对整个链路有个清晰的认识。

AMS 广播分发流程 应用调用 sendBroadcast() AMS 权限检查 + 广播类型判断 匹配接收器 动态接收器 (mRegisteredReceivers) | 静态接收器 (PMS查询) 有序广播? 并行分发所有接收器 按优先级顺序分发 分发完成 分发完成

11.5 避坑指南

做广播开发这么多年,我总结了几条经验,分享给你:

  • 不要在主线程做耗时操作:广播的 onReceive() 默认在主线程执行,超过 10 秒会被 AMS 干掉
  • 动态注册记得注销:在 onDestroy()onPause() 中调用 unregisterReceiver(),避免内存泄漏
  • 有序广播慎用 abort:调用 abortBroadcast() 会中断后续所有接收器,确保你真的需要这么做
  • 静态广播注意版本限制:Android 8.0 之后,大部分隐式静态广播都被限制了,改用动态注册或显式广播
个人建议:如果你需要跨进程通信,优先考虑使用 LiveData 或 Flow,而不是广播。广播的性能开销比较大,而且容易出问题。只有在系统广播或跨应用通信的场景下,才推荐使用广播。

好了,关于 BroadcastReceiver 的管理,我们就聊到这里。记住,广播的核心在于 AMS 如何管理接收器的生命周期和分发顺序。理解了这些,你就能在项目中游刃有余地使用广播了。


公众号:蓝海资料掘金营,微信deep3321