Android 广播机制详解

313 阅读13分钟

核心思想:发布/订阅模式 (Publish/Subscribe)

想象一下现实中的广播电台:

  1. 电台 (发送者 - Sender) :发布一条消息(比如“明天有暴雨”)。
  2. 收音机 (接收者 - Receiver) :需要调到特定频道并且打开开关才能收到这个消息。
  3. 消息 (广播 - Broadcast) :电台发出的具体内容(“明天有暴雨”)。

Android 广播机制非常类似:

  1. 发送者 (Broadcast Sender) :一个组件(Activity, Service, 甚至是另一个 BroadcastReceiver)发出一个广播事件 (Broadcast)
  2. 广播 (Broadcast) :一个携带了特定信息和意图(Intent)的对象。这个 Intent 包含了动作 (Action)  来标识广播的类型(比如 "android.intent.action.BATTERY_LOW")。
  3. 接收者 (Broadcast Receiver) :一个组件,专门负责监听 (订阅)  特定类型的广播(通过 IntentFilter 指定 Action 等条件)。当匹配的广播发出时,系统会调用它的 onReceive() 方法。
  4. 系统 (Android OS) :扮演“广播塔”的角色,负责将发出的广播分发给所有注册监听该类型广播的接收者。

关键概念详解

  1. Broadcast (广播事件):

    • 本质是一个 Intent 对象。

    • 核心属性:

      • Action (动作):  最重要的标识符!是一个字符串,表示发生了什么事件(例如:Intent.ACTION_BOOT_COMPLETED - 系统启动完成, "com.example.myapp.MY_CUSTOM_ACTION" - 自定义事件)。
      • Data (数据):  可选的 URI 数据。
      • Category (类别):  可选的附加信息。
      • Extras (附加数据):  一个 Bundle 对象,可以携带任意自定义的键值对数据。
    • 类型:

      • 显式广播 (Explicit Broadcast):  明确指定了目标接收者组件的类名。通常用于应用内部通信(因为你知道具体要发给谁)。
      • 隐式广播 (Implicit Broadcast):  只指定了 Action(可能还有 Data/Category),不指定具体接收者。系统会寻找所有注册监听该 Action 的接收者。常用于系统事件应用间通信(例如通知电量低、网络变化)。
  2. BroadcastReceiver (广播接收者):

    • 一个继承自 BroadcastReceiver 的类。

    • 核心方法:void onReceive(Context context, Intent intent)

      • 当匹配的广播到达时,系统自动调用此方法。
      • context: 当前上下文。
      • intent: 接收到的广播 Intent,包含所有发送的信息。
    • 重要限制:

      • onReceive() 方法运行在主线程 (UI 线程)
      • 在 onReceive() 中不能执行耗时操作(超过 10 秒会导致 ANR - Application Not Responding)。
      • 如果需要耗时操作,必须启动一个 Service 或使用 JobScheduler/WorkManager
  3. IntentFilter (意图过滤器):

    • 一个对象,用于声明一个 BroadcastReceiver 对哪些广播感兴趣

    • 主要指定过滤条件:

      • <action>  必须匹配的 Action 字符串(至少一个)。
      • <data>  (可选)匹配特定的 URI 模式和 MIME 类型。
      • <category>  (可选)匹配特定的类别。
    • 作用:  告诉系统,“我只想接收带有这些 Action/Data/Category 的广播”。

  4. 注册方式 (如何让收音机“调频”并“开机”):

    • 静态注册 (Manifest-declared):

      • 在 AndroidManifest.xml 文件中声明 <receiver> 标签。

      • 特点:

        • 接收者长期存活,即使 App 未启动(只要系统在运行且事件发生)。
        • 主要用来监听系统全局事件(开机完成、时区改变、安装应用等)。
        • Android 8.0 (API 26+) 重大限制:  出于性能和电量考虑,大多数隐式广播不能再在 Manifest 中静态注册(除了少数系统豁免的白名单广播)。必须使用动态注册。
      • 示例 (AndroidManifest.xml):

        <receiver android:name=".MyBootReceiver"
            android:exported="true"> <!-- 是否允许其他应用发送广播给它 -->
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
        
    • 动态注册 (Context-registered):

      • 在代码中(通常在 Activity 或 Service 的 onCreate()/onResume())使用 Context.registerReceiver() 方法注册。

      • 在对应的地方(如 onDestroy()/onPause())使用 Context.unregisterReceiver() 注销。

      • 特点:

        • 接收者生命周期与注册它的 Context(如 Activity)绑定。Context 销毁时,接收者自动失效。避免内存泄漏的关键!
        • 适用于应用内部特定界面/服务需要时才监听的广播。
        • 不受 Android 8.0 隐式广播限制影响(因为接收者生命周期可控)。
      • 示例 (Activity 中):

        public class MyActivity extends AppCompatActivity {
            private MyDynamicReceiver mReceiver;
        
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                ...
                // 1. 创建接收者实例
                mReceiver = new MyDynamicReceiver();
                // 2. 创建 IntentFilter,指定要监听的 Action
                IntentFilter filter = new IntentFilter();
                filter.addAction("com.example.myapp.MY_DYNAMIC_ACTION");
                filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); // 监听网络变化
                // 3. 动态注册
                registerReceiver(mReceiver, filter);
            }
        
            @Override
            protected void onDestroy() {
                super.onDestroy();
                // 4. 非常重要!在 Context 销毁时注销接收者
                unregisterReceiver(mReceiver);
            }
        
            // 内部类定义动态接收者
            private class MyDynamicReceiver extends BroadcastReceiver {
                @Override
                public void onReceive(Context context, Intent intent) {
                    String action = intent.getAction();
                    if ("com.example.myapp.MY_DYNAMIC_ACTION".equals(action)) {
                        // 处理自定义广播
                    } else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
                        // 处理网络变化广播
                    }
                }
            }
        }
        
  5. 发送广播 (如何“发布电台消息”):

    • 使用 Context.sendBroadcast()Context.sendOrderedBroadcast(), 或 Context.sendStickyBroadcast() (已废弃) 方法。

    • 普通广播 (Normal Broadcast - sendBroadcast()):

      • 最常见的发送方式。
      • 异步发送给所有匹配的接收者。
      • 接收者没有执行顺序,也无法中断广播的传递。
    • 有序广播 (Ordered Broadcast - sendOrderedBroadcast()):

      • 接收者按照优先级 (android:priority 属性或在 IntentFilter 中设置) 依次接收广播。

      • 高优先级的接收者先收到,可以:

        • 修改广播内容 (Result Extras):  通过 setResultExtras(Bundle) 等传递数据给下一个接收者。
        • 中断广播 (Abort):  调用 abortBroadcast() 阻止广播继续传递给后面的低优先级接收者。
      • 常用于需要处理链或需要最终结果的场景(例如“电池状态收集”)。

    • 粘性广播 (Sticky Broadcast - sendStickyBroadcast()):

      • 已废弃 (Deprecated)!  Android 5.0 (API 21) 开始废弃,Android 13 (API 33) 彻底移除。
      • 发送后,广播会驻留在系统中。之后注册的、匹配该广播的接收者会立即收到最后一次发送的粘性广播
      • 常用于需要获取最新状态(如最新电量)的场景,但现在推荐使用其他机制(如 SharedPreferencesLiveData, 直接查询系统服务如 BatteryManager)。
  6. 本地广播 (Local Broadcast):

    • 使用 LocalBroadcastManager (现在也推荐使用 LiveData 或 EventBus 替代)。

    • 核心特点:

      • 广播的发送和接收只在同一个 App 进程内进行
      • 更安全:  其他 App 无法发送或接收这些广播。
      • 更高效:  避免了进程间通信 (IPC) 的开销。
    • 用法 (已弃用 API,仅作理解):

      // 获取 LocalBroadcastManager 实例
      LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
      // 注册接收者 (类似动态注册,但用 lbm)
      lbm.registerReceiver(receiver, filter);
      // 发送本地广播
      lbm.sendBroadcast(intent);
      // 注销
      lbm.unregisterReceiver(receiver);
      

使用广播的典型场景

  1. 监听系统事件:  开机完成、时区改变、电池电量低、屏幕开/关、网络连接变化、耳机插入拔出、安装/卸载应用、短信接收 (需要权限)、电话状态 (需要权限)。
  2. 应用内部通信:  一个后台服务完成任务后通知前台 Activity 更新 UI (通常结合动态注册和本地广播/LiveData)。不同组件之间传递消息。
  3. 应用间通信 (受限):  发送一个自定义广播,其他应用如果有接收者监听该 Action 就能收到。但由于权限和安全问题,实际应用场景不如系统广播广泛。需要谨慎设计权限 (android:permission) 来控制谁可以发送/接收。

总结-使用

  1. 广播 = 消息 (Intent) ,由发送者发出。

  2. 接收者 (BroadcastReceiver)  通过 IntentFilter 声明自己关心哪些广播。

  3. 注册方式:

    • 静态注册 (Manifest):  长期监听系统事件 (Android 8.0+ 受限)。
    • 动态注册 (代码):  灵活监听,生命周期与 Context 绑定,必须记得注销!
  4. 发送方式:

    • 普通广播 (sendBroadcast) :异步发给所有接收者。
    • 有序广播 (sendOrderedBroadcast) :按优先级传递,可修改/中断。
    • (已废弃) 粘性广播 (sendStickyBroadcast) :驻留系统,新接收者立得。
  5. 本地广播:  进程内高效安全通信 (推荐用 LiveData 等替代)。

  6. onReceive() 限制:  主线程,禁耗时操作!

  7. Android 8.0+:  严格限制 Manifest 静态注册隐式广播,多用动态注册。

源码分析 - Android 广播机制原理与调用链路

理解了如何使用广播后,我们深入看看 Android 系统内部是如何实现广播机制的。核心在于理解 AMS (ActivityManagerService)  的作用和 IPC (进程间通信)

核心原理概述

  1. 中心枢纽 - AMS:  广播机制的核心是 ActivityManagerService (AMS)。它运行在 system_server 进程,是 Android 系统的大脑,负责管理四大组件、进程生命周期以及所有广播的发送和分发

  2. 注册过程 (订阅):  当一个组件(无论是静态还是动态注册)注册一个 BroadcastReceiver 时,最终都需要向 AMS 报告:“我对 XXX 类型的广播感兴趣”。

  3. 发送过程 (发布):  当某个组件调用 sendBroadcast() 时,最终会调用到 AMS。AMS 负责:

    • 查找所有匹配该广播 Intent 的 BroadcastReceiver (已注册的)。
    • 根据广播类型(普通/有序)和优先级,决定分发的顺序和方式。
    • 将广播分发给目标接收者所在的进程。
  4. 分发过程 (执行):  AMS 通过 IPC (Binder)  机制通知接收者所在进程的 AMS 客户端代理(通常是 ActivityThread 中的 ApplicationThread 或 H 类) 。该进程内部再通过 Handler 机制,将调用 BroadcastReceiver.onReceive() 的任务抛到主线程的消息队列中执行。

  5. 异步与线程:  发送广播 (sendBroadcast) 本身是异步的,调用后会立即返回。接收者的 onReceive() 一定在主线程执行。

关键源码类与调用链路

我们追踪一个典型的动态注册接收者接收普通广播的流程:

阶段 1:接收者注册 (动态注册为例)

  1. ContextImpl.registerReceiver(BroadcastReceiver, IntentFilter) : (在 App 进程)

    • 这是你在 Activity/Service 中调用的方法。ContextImpl 是 Context 的实际实现者。
  2. ContextImpl.registerReceiverInternal() : (在 App 进程)

    • 进行一些参数处理和封装。核心是创建一个 IIntentReceiver 对象 (LoadedApk.ReceiverDispatcher.InnerReceiver)。
    • IIntentReceiver:  这是一个 Binder 接口。它是 App 进程暴露给 AMS 的一个“钩子”(Hook)。AMS 后续需要通过这个 Binder 接口来通知 App 进程有广播到达。
  3. ActivityManagerService.registerReceiver() : (跨进程 IPC 调用到 system_server 进程)

    • 通过 Binder IPC (IActivityManager 接口),App 进程将 IIntentReceiver 和 IntentFilter 等信息发送给 AMS。
  4. AMS 处理注册:

    • AMS 将接收者信息(包括 IIntentReceiver 这个 Binder 引用、IntentFilter、注册的进程信息等)存储在自己的内部数据结构(如 mRegisteredReceivers)中。这相当于在 AMS 的“订阅清单”上添加了一条记录。

阶段 2:发送广播

  1. ContextImpl.sendBroadcast(Intent) : (在发送者 App 进程)

    • 发送者调用此方法。
  2. ContextImpl.sendBroadcast() -> ActivityManagerService.broadcastIntent() : (跨进程 IPC 调用到 system_server 进程)

    • 通过 Binder IPC (IActivityManager 接口),将广播 Intent 和相关信息(如发送者权限、是否是普通广播等)发送给 AMS。
  3. AMS 处理广播 (broadcastIntentLocked()):  (在 system_server 进程)

    • 匹配接收者:  AMS 根据收到的 Intent(主要是 Action),在自己的“订阅清单”(mRegisteredReceivers 静态注册列表、动态注册列表)中查找所有匹配该 Intent 的 BroadcastReceiver
    • 处理有序广播:  如果是 sendOrderedBroadcast(),AMS 会按优先级对匹配的接收者排序,并可能设置结果接收者。
    • 权限检查:  检查发送者是否有权限发送此广播,检查接收者是否有权限接收此广播(根据注册时声明的 android:permission)。
    • 加入分发队列:  将满足条件的接收者(封装成 BroadcastRecord 对象)加入到相应的分发队列(前台广播队列、后台广播队列)。
    • 调度分发:  AMS 唤醒广播分发线程 (BroadcastQueue 的工作线程) 开始处理队列中的广播。

阶段 3:分发广播到接收者进程 & 执行 onReceive()

  1. BroadcastQueue.processNextBroadcast() : (在 system_server 进程的广播分发线程)

    • 从队列中取出一个 BroadcastRecord
    • 对于该广播记录中的每一个接收者:
  2. BroadcastQueue.deliverToRegisteredReceiverLocked() : (在 system_server 进程)

    • 执行最终的权限检查(动态注册时可能没检查接收权限)。
    • 关键调用:performReceiveLocked() -> app.thread.scheduleRegisteredReceiver()
    • app.thread:  这是发送者注册时传递给 AMS 的那个 IIntentReceiver 对象(即 LoadedApk.ReceiverDispatcher.InnerReceiver)所在的进程对应的 IApplicationThread Binder 代理对象。它代表了目标接收者 App 进程。
  3. IApplicationThread.scheduleRegisteredReceiver() : (跨进程 IPC 调用到接收者 App 进程)

    • 通过 Binder IPC,AMS 通知接收者进程:“你注册的接收者(由 IIntentReceiver 标识)有一个广播到了,Intent 数据是 XXX”。
  4. 接收者进程处理 (ApplicationThread.scheduleRegisteredReceiver()):  (在接收者 App 进程的 Binder 线程池)

    • ApplicationThread 是 AMS 与 App 进程通信的主要接口实现,运行在 App 进程的 Binder 线程。
    • 它根据 AMS 传过来的 IIntentReceiver 引用,找到对应的 LoadedApk.ReceiverDispatcher 对象。
  5. ReceiverDispatcher.performReceive(Intent, resultCode, resultData, ...) : (在接收者 App 进程的 Binder 线程)

    • 将接收到的 Intent 和相关信息封装成一个 Args 对象。

    • 关键步骤:mActivityThread.post(args.getRunnable())

      • mActivityThread 是一个 Handler (实际上是 ActivityThread 内部的 H Handler)。
      • args.getRunnable() 返回一个 Runnable,其 run() 方法的核心是调用 receiver.onReceive(context, intent)
  6. Handler.post(Runnable) : (在接收者 App 进程)

    • 这个 Runnable 被发送到 App 进程的主线程消息队列
  7. 主线程 Looper 处理消息:

    • 当主线程 Looper 处理到这个 Runnable 消息时,执行其 run() 方法。
  8. Runnable.run() -> BroadcastReceiver.onReceive(Context, Intent) : (在接收者 App 进程的主线程)

    • 最终,你的 BroadcastReceiver 子类的 onReceive() 方法在主线程被调用,处理广播逻辑!

静态注册流程差异

  1. 注册:  App 安装时,系统解析 AndroidManifest.xml,将静态注册的 <receiver> 信息存储在 PackageManagerService (PMS) 中。当系统启动或 App 安装/更新后,这些信息会被 PMS 同步给 AMS。

  2. 接收广播:  当匹配的隐式广播发出时,AMS 同样会找到静态注册的接收者。如果接收者所在的 App 进程尚未运行

    • AMS 会先启动该 App 进程
    • 在 App 进程启动并初始化 (Application.onCreate() 之后, ActivityThread 初始化过程中),会向 AMS 报告进程启动完成。
    • AMS 随后将之前收到的、匹配该 App 静态接收者的未处理广播(如果还在有效期内)发送给该进程。
    • 进程启动后,后续流程与动态注册类似(AMS -> ApplicationThread.scheduleReceiver() -> ActivityThread.handleReceiver() -> 创建 Receiver 实例 -> onReceive() 在主线程执行)。
  3. Android 8.0+ 限制:  对于大多数隐式广播,AMS 在匹配静态注册接收者时会检查该广播类型是否在豁免列表 (白名单) 中。如果不在,则跳过这些静态注册的接收者,只分发给动态注册的接收者。

调用链路总结图 (简化版)

[发送者 App 进程]
Context.sendBroadcast(Intent)
  --> (Binder IPC) -->
      [system_server 进程 - AMS]
      AMS.broadcastIntentLocked()
        |--> 匹配注册的接收者 (静态/动态)
        |--> 权限检查
        |--> 将 BroadcastRecord 加入队列
        |--> BroadcastQueue 处理线程 processNextBroadcast()
             |--> 对每个匹配的接收者:
                  AMS.deliverToRegisteredReceiverLocked()
                    --> (Binder IPC) -->
                        [接收者 App 进程]
                        ApplicationThread.scheduleRegisteredReceiver() (在 Binder 线程)
                          --> 找到 ReceiverDispatcher
                          --> Handler.post(Runnable) (发送到主线程队列)
                              [接收者 App 主线程]
                              Runnable.run()
                                --> BroadcastReceiver.onReceive(Context, Intent) // 最终执行!

关键点总结 (原理与源码)

  1. 核心枢纽是 AMS:  所有广播的注册、发送、匹配、分发都由 AMS 在 system_server 进程中集中管理。

  2. IPC 无处不在:

    • 注册时:App -> AMS (报告订阅信息)。
    • 发送时:App -> AMS (报告广播事件)。
    • 分发时:AMS -> App (通知接收者执行)。
  3. IIntentReceiver 是桥梁:  动态注册时,App 通过这个 Binder 对象告诉 AMS “通知我的方式”。AMS 分发时通过这个 Binder 回调 App。

  4. ApplicationThread 是 App 的 AMS 代理:  AMS 通过调用 IApplicationThread 的方法(如 scheduleRegisteredReceiver)来调度 App 进程内的操作。

  5. onReceive() 在主线程的奥秘:  AMS 通过 IPC 通知到 App 进程的 Binder 线程,App 进程内部通过 Handler 机制将 onReceive() 的执行切换到主线程

  6. 静态注册涉及进程启动:  AMS 可能需要先启动目标 App 进程才能将广播分发给它。

  7. Android 8.0+ 限制的本质:  AMS 在分发隐式广播时,会过滤掉 Manifest 中静态注册且不在豁免列表中的接收者。