吃透 Android BroadcastReceiver:从通信到避坑实战指南

目录

前言:为什么 BroadcastReceiver 是 Android 通信的 “万能信使”?

一、BroadcastReceiver 基础认知:到底什么是广播?

1.1 一句话搞懂 BroadcastReceiver 的核心作用

1.2 必须澄清的 3 个常见误解

1.3 广播的 3 个核心特性

1.4 广播的典型适用场景

二、广播的分类:3 种维度划分,避免用错场景

2.1 按发送方式划分:有序广播 vs 无序广播

1. 无序广播(Normal Broadcast)

2. 有序广播(Ordered Broadcast)

2.2 按注册方式划分:静态广播 vs 动态广播

1. 静态广播(Manifest 注册)

2. 动态广播(代码注册)

2.3 按传播范围划分:全局广播 vs 本地广播

1. 全局广播(Global Broadcast)

2. 本地广播(Local Broadcast)

三、广播的核心:注册方式与生命周期(实战示例)

3.1 动态广播(代码注册):灵活可控,优先推荐

示例 1:动态广播实现应用内通信(登录成功通知)

步骤 1:创建广播接收器(接收登录成功消息)

步骤 2:在 Activity 中注册 / 注销广播

步骤 3:运行测试,观察日志

关键说明:

3.2 静态广播(Manifest 注册):兼容系统事件

示例 2:静态广播实现开机自启(系统事件监听)

步骤 1:创建广播接收器(接收开机广播)

步骤 2:在 Manifest 中注册广播(关键步骤)

步骤 3:创建 SyncService(开机后启动的服务)

测试说明:

关键说明:

3.3 广播的生命周期:极简且特殊

1. 生命周期流程:

2. 核心特点:

3. 避免生命周期相关坑点:

四、广播的进阶用法:有序广播、本地广播与跨应用通信

4.1 有序广播:按优先级处理,支持中断与数据修改

示例 3:有序广播实现 “消息审核”(优先级与中断)

步骤 1:定义 3 个广播接收器(不同优先级)

步骤 2:在 Manifest 中注册有序广播(指定优先级)

步骤 3:发送有序广播并测试

测试结果:

关键说明:

4.2 本地广播:应用内安全通信,高效无泄漏

示例 4:LocalBroadcastManager 实现应用内数据同步

步骤 1:创建本地广播接收器

步骤 2:在 Activity 中注册 / 注销本地广播

步骤 3:在 Service 中发送本地广播

本地广播与全局广播的核心区别:

4.3 跨应用广播:实现应用间消息传递

示例 5:跨应用广播实现 “应用 A 通知应用 B 更新数据”

步骤 1:应用 A(发送方)发送跨应用广播

步骤 2:应用 B(接收方)注册广播接收

步骤 3:应用 B 在 Manifest 中注册广播(允许跨应用接收)

跨应用广播的关键注意事项:

五、系统广播:监听系统状态变化(常用场景汇总)

5.1 常用系统广播 Action 与权限

示例 6:动态注册监听网络状态变化

关键说明:

六、Android 8.0 + 广播限制与适配方案(重点避坑)

6.1 Android 8.0 + 广播的核心限制

6.2 适配方案:如何让广播在 Android 8.0 + 正常工作?

方案 1:动态注册替代静态注册(优先推荐)

方案 2:发送广播时指定接收方包名

方案 3:使用前台服务配合广播

方案 4:使用 PendingIntent 发送广播

方案 5:使用系统允许的例外广播

七、广播常见坑点与避坑指南(实战经验总结)

7.1 坑点 1:静态广播注册后无法接收

7.2 坑点 2:Android 8.0 后自定义广播接收不到

7.3 坑点 3:广播onReceive()中做耗时操作导致 ANR

7.4 坑点 4:动态广播未注销导致内存泄漏

7.5 坑点 5:跨应用广播接收不到,权限配置错误

7.6 坑点 6:广播传递大数据导致崩溃

八、总结:BroadcastReceiver 核心知识点图谱


class 卑微码农:
    def __init__(self):
        self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
        self.发量 = 100  # 初始发量
        self.咖啡因耐受度 = '极限'
        
    def 修Bug(self, bug):
        try:
            # 试图用玄学解决问题
            if bug.严重程度 == '离谱':
                print("这一定是环境问题!")
            else:
                print("让我看看是谁又没写注释...哦,是我自己。")
        except Exception as e:
            # 如果try块都救不了,那就...
            print("重启一下试试?")
            self.发量 -= 1  # 每解决一个bug,头发-1
 
 
# 实例化一个我
我 = 卑微码农()

前言:为什么 BroadcastReceiver 是 Android 通信的 “万能信使”?

刚入门 Android 时,我曾被 “组件间通信” 搞得头大 ——Activity 想通知 Service 执行任务,后台数据更新后要刷新 UI,系统网络变化要及时响应,这些场景到底该用什么实现?

直到接触了 BroadcastReceiver(广播接收器),才发现它的核心价值:跨组件、跨应用的 “消息中转站” 。不管是系统状态变化(如开机、网络切换、电量低),还是应用内组件交互(如登录成功通知、下载完成提醒),广播都能轻松搞定。

但很多开发者用不好广播,要么遇到 “静态广播接收不到”“Android 8.0 后广播失效” 的问题,要么因滥用广播导致内存泄漏、耗电过快。其实广播的逻辑不复杂,关键是分清类型、掌握注册方式、避开系统限制。

本文会把 BroadcastReceiver 的基础概念、类型划分、注册方式、生命周期、实战场景、避坑指南全讲透。包含 10 个完整可运行的原创示例,从基础入门到进阶实战,新手能直接上手,进阶开发者能夯实基础、避开坑点。

建议先收藏,再跟着示例一步步实操,遇到问题可以在评论区交流~


一、BroadcastReceiver 基础认知:到底什么是广播?

1.1 一句话搞懂 BroadcastReceiver 的核心作用

BroadcastReceiver(简称 “广播”)是 Android 四大组件之一,核心作用是接收和传递 “消息事件” ,实现跨组件、跨应用的异步通信。

简单说:广播就像现实中的 “电台广播”—— 发送者(电台)发出特定频率的信号,接收者(收音机)调至对应频率就能收到消息。在 Android 中:

  • 发送者:可以是系统(如开机完成、网络变化)、应用内组件(Activity、Service)、其他应用;
  • 接收者:BroadcastReceiver 实例,通过 “注册” 监听特定 “动作(Action)”,收到消息后执行对应逻辑;
  • 消息:Intent 对象,可携带数据(如网络类型、电量百分比)。

1.2 必须澄清的 3 个常见误解

  • 误解 1:BroadcastReceiver 是独立线程?—— 错!广播的onReceive()方法默认运行在主线程(UI 线程) ,不能做耗时操作(否则会触发 ANR);
  • 误解 2:广播能实时通信?—— 错!广播是异步通信,发送者发送后无需等待接收者响应,无法保证消息传递的实时性;
  • 误解 3:广播越多越好?—— 错!广播是全局事件,滥用会导致系统资源消耗增加、耗电加快,还可能引发安全问题(如敏感数据通过广播泄露)。

1.3 广播的 3 个核心特性

  • 跨组件通信:无需直接持有组件引用,Activity、Service、Fragment、Application 之间可随意通信;
  • 跨应用通信:支持不同应用间传递消息(如应用 A 发送广播,应用 B 接收并处理);
  • 系统事件监听:能接收 Android 系统发出的各类事件广播(如开机、屏幕亮灭、电量变化、网络切换),快速响应系统状态。

1.4 广播的典型适用场景

  • 系统状态监听:网络变化、电量低提醒、开机自启、屏幕亮灭、安装 / 卸载应用;
  • 应用内通信:登录成功后通知所有组件、下载完成后刷新 UI、Service 后台任务执行结果反馈;
  • 跨应用通信:应用 A 分享数据给应用 B、第三方应用接收系统推送的消息、应用间功能调用通知。

二、广播的分类:3 种维度划分,避免用错场景

BroadcastReceiver 的分类维度有 3 种,不同类型的广播适用场景、使用方式差异很大,这是掌握广播的基础。

2.1 按发送方式划分:有序广播 vs 无序广播

这是最核心的分类,决定了广播的传递逻辑和处理顺序。

1. 无序广播(Normal Broadcast)
  • 核心特点:异步、无顺序、不可中断 。所有注册了对应 Action 的接收者都会收到广播,接收顺序不确定,无法阻止其他接收者接收。
  • 发送方式:sendBroadcast(Intent intent)
  • 优点:效率高,适合无需优先级、无需中断的场景;
  • 缺点:无法修改广播数据,无法阻止后续接收者;
  • 适用场景:普通通知类消息(如下载完成提醒、登录成功通知)。
2. 有序广播(Ordered Broadcast)
  • 核心特点:同步、有顺序、可中断 。接收者按 “优先级” 从高到低依次接收,高优先级接收者可修改广播数据、中断广播(阻止低优先级接收者接收)。
  • 发送方式:sendOrderedBroadcast(Intent intent, String receiverPermission)
  • 优先级设置:在注册时通过android:priority属性设置(范围 - 1000~1000,数值越高优先级越高);
  • 优点:支持优先级排序、数据修改、广播中断,适合需要按顺序处理的场景;
  • 缺点:效率低于无序广播,需处理权限问题;
  • 适用场景:需要优先级处理的消息(如系统通知、权限申请结果反馈)。

2.2 按注册方式划分:静态广播 vs 动态广播

注册是广播接收者 “监听消息” 的前提,不同注册方式的生命周期、适用场景完全不同,也是最容易踩坑的地方。

1. 静态广播(Manifest 注册)
  • 注册方式:在 AndroidManifest.xml 中通过<receiver>标签注册,指定要监听的 Action;
  • 生命周期:应用未启动时也能接收广播(系统会唤醒应用进程);
  • 优点:无需手动注册 / 注销,适合监听系统事件(如开机自启);
  • 缺点:灵活性低,Android 8.0 后受严格限制(大部分后台静态广播失效);
  • 适用场景:系统开机广播、应用安装 / 卸载广播等必须在应用未启动时接收的场景。
2. 动态广播(代码注册)
  • 注册方式:在代码中通过registerReceiver()方法注册,在onDestroy()中通过unregisterReceiver()注销;
  • 生命周期:与注册组件(如 Activity、Service)绑定,组件销毁前必须注销,否则内存泄漏;
  • 优点:灵活性高,可动态注册 / 注销,不受 Android 8.0 后台限制影响;
  • 缺点:组件销毁前需手动注销,否则会抛出异常;
  • 适用场景:应用内组件通信、临时监听系统事件(如页面可见时监听网络变化)。

2.3 按传播范围划分:全局广播 vs 本地广播

1. 全局广播(Global Broadcast)
  • 传播范围:整个系统,所有应用都能接收(只要注册了对应 Action);
  • 优点:支持跨应用通信;
  • 缺点:安全性低(可能被恶意应用监听或伪造广播)、消耗系统资源;
  • 适用场景:跨应用消息传递、系统事件监听。
2. 本地广播(Local Broadcast)
  • 传播范围:仅当前应用内部,其他应用无法接收;
  • 实现方式:通过LocalBroadcastManager类发送和注册广播;
  • 优点:安全性高(避免数据泄露)、效率高(不占用系统全局资源)、无权限限制;
  • 缺点:不支持跨应用通信;
  • 适用场景:应用内组件间通信(如 Activity 与 Service、Fragment 与 Activity)。

三、广播的核心:注册方式与生命周期(实战示例)

广播的使用核心是 “注册 - 接收 - 处理 - 注销”,不同注册方式的实现逻辑差异很大,下面通过完整示例逐个拆解。

3.1 动态广播(代码注册):灵活可控,优先推荐

动态广播是目前开发中最常用的方式,不受 Android 8.0 后后台广播限制,适合大多数场景。

示例 1:动态广播实现应用内通信(登录成功通知)

场景:用户登录成功后,发送广播通知所有组件(如刷新个人信息、跳转首页)。

步骤 1:创建广播接收器(接收登录成功消息)
public class LoginSuccessReceiver extends BroadcastReceiver {
    private static final String TAG = "BroadcastTest";
    // 定义广播Action(建议用包名前缀,避免冲突)
    public static final String ACTION_LOGIN_SUCCESS = "com.example.broadcast.ACTION_LOGIN_SUCCESS";

    // 核心方法:接收广播后触发(运行在主线程)
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "收到登录成功广播");
        // 1. 验证Action(避免接收无关广播)
        if (ACTION_LOGIN_SUCCESS.equals(intent.getAction())) {
            // 2. 接收广播携带的数据
            String username = intent.getStringExtra("username");
            String token = intent.getStringExtra("token");
            Log.d(TAG, "登录用户:" + username + ",Token:" + token);

            // 3. 执行后续逻辑(如刷新UI、保存Token)
            // 注意:onReceive()不能做耗时操作,最多执行10秒(否则ANR)
            Toast.makeText(context, "登录成功!欢迎" + username, Toast.LENGTH_SHORT).show();
        }
    }
}
步骤 2:在 Activity 中注册 / 注销广播
public class LoginActivity extends AppCompatActivity {
    private LoginSuccessReceiver mLoginReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        // 1. 初始化广播接收器
        initBroadcastReceiver();

        // 模拟登录按钮点击
        findViewById(R.id.btn_login).setOnClickListener(v -> {
            // 登录逻辑(模拟验证成功)
            String username = "张三";
            String token = "abc123456";

            // 2. 发送登录成功广播
            sendLoginSuccessBroadcast(username, token);

            // 3. 跳转首页
            startActivity(new Intent(this, HomeActivity.class));
        });
    }

    // 初始化广播接收器(动态注册)
    private void initBroadcastReceiver() {
        mLoginReceiver = new LoginSuccessReceiver();
        // 创建IntentFilter,指定要监听的Action
        IntentFilter filter = new IntentFilter();
        filter.addAction(LoginSuccessReceiver.ACTION_LOGIN_SUCCESS);
        // 注册广播(参数:接收器实例、IntentFilter)
        registerReceiver(mLoginReceiver, filter);
    }

    // 发送登录成功广播
    private void sendLoginSuccessBroadcast(String username, String token) {
        Intent intent = new Intent(LoginSuccessReceiver.ACTION_LOGIN_SUCCESS);
        // 携带数据(支持基本数据类型、String、Parcelable等)
        intent.putExtra("username", username);
        intent.putExtra("token", token);
        // 发送无序广播
        sendBroadcast(intent);
    }

    // 4. 组件销毁时注销广播(必须!避免内存泄漏)
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mLoginReceiver != null) {
            unregisterReceiver(mLoginReceiver);
        }
    }
}
步骤 3:运行测试,观察日志
  • 点击 “登录” 按钮,发送广播;
  • 广播接收器的onReceive()方法被触发,打印用户信息并弹出 Toast;
  • 退出 Activity 时,广播被注销,无内存泄漏风险。
关键说明:
  • 动态广播的生命周期与注册组件(如 Activity)绑定,组件销毁前必须注销;
  • IntentFilter是 “过滤条件”,只有 Action 匹配的广播才会被接收;
  • onReceive()运行在主线程,禁止耗时操作(如网络请求、文件读写),如需耗时处理,需启动 Service 执行。

3.2 静态广播(Manifest 注册):兼容系统事件

静态广播在 Android 8.0(API 26)后受到严格限制 —— 系统禁止大部分 “后台静态广播”(即应用未启动时无法接收),仅保留部分系统核心广播(如开机、安装应用)仍支持静态注册。

示例 2:静态广播实现开机自启(系统事件监听)

场景:应用需要在设备开机后自动启动服务(如后台数据同步、推送服务)。

步骤 1:创建广播接收器(接收开机广播)
public class BootCompleteReceiver extends BroadcastReceiver {
    private static final String TAG = "BroadcastTest";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "收到开机完成广播");
        // 验证Action(系统开机广播Action)
        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            // 开机后执行逻辑(如启动服务)
            Intent serviceIntent = new Intent(context, SyncService.class);
            // Android 8.0后启动后台服务需用startForegroundService()
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                context.startForegroundService(serviceIntent);
            } else {
                context.startService(serviceIntent);
            }
            Log.d(TAG, "开机后启动同步服务");
        }
    }
}
步骤 2:在 Manifest 中注册广播(关键步骤)
<!-- 1. 添加开机权限 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<!-- 2. 注册静态广播接收器 -->
<receiver
    android:name=".BootCompleteReceiver"
    android:exported="true"> <!-- 允许接收系统广播 -->
    <intent-filter>
        <!-- 指定监听的系统Action:开机完成 -->
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

<!-- 3. 注册需要启动的Service -->
<service
    android:name=".SyncService"
    android:exported="false" />
步骤 3:创建 SyncService(开机后启动的服务)
public class SyncService extends Service {
    private static final String TAG = "BroadcastTest";

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "SyncService创建,开始后台同步");
        // 模拟后台数据同步(需放在子线程)
        new Thread(() -> {
            try {
                Thread.sleep(2000);
                Log.d(TAG, "数据同步完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
测试说明:
  • 由于开机广播需要设备重启,可通过 Android Studio 的 “Emulator Control” 发送模拟开机广播(无需真实重启);
  • 设备开机后,系统会发送ACTION_BOOT_COMPLETED广播,静态注册的接收器接收后启动 SyncService;
  • 注意:Android 12 及以上,部分设备可能需要授予 “自启动权限” 才能生效。
关键说明:
  • 静态广播无需手动注册 / 注销,但需在 Manifest 中配置完整;
  • android:exported="true"表示允许接收来自应用外部的广播(如系统广播),否则无法接收;
  • Android 8.0 后,仅系统核心广播支持静态注册,自定义静态广播需配合 “前台服务” 或 “广播权限” 才能生效。

3.3 广播的生命周期:极简且特殊

BroadcastReceiver 的生命周期比 Activity、Service 简单得多,核心只有两个阶段:

1. 生命周期流程:

注册广播 → 接收广播 → 触发onReceive() → 执行完成 → 接收器实例销毁

2. 核心特点:
  • 生命周期极短:onReceive()方法执行完成后,BroadcastReceiver 实例会被系统立即销毁,无法持有状态;
  • 无后台存活:接收器不能在onReceive()之外执行任务,所有逻辑必须在该方法内完成(或启动其他组件执行);
  • 主线程执行:默认运行在主线程,onReceive()执行时间不能超过 10 秒,否则会触发 ANR(应用无响应)。
3. 避免生命周期相关坑点:
  • 不要在onReceive()中创建非静态内部类(如 Handler),否则会持有接收器引用,导致内存泄漏;
  • 不要在onReceive()中启动 Activity(会弹出弹窗影响用户体验),如需跳转,可添加FLAG_ACTIVITY_NEW_TASK标记;
  • 不要在onReceive()中长时间阻塞(如Thread.sleep(20000)),会直接触发 ANR。

四、广播的进阶用法:有序广播、本地广播与跨应用通信

基础的无序广播只能满足简单场景,实际开发中还会用到有序广播、本地广播、跨应用广播等进阶用法,下面结合示例详细讲解。

4.1 有序广播:按优先级处理,支持中断与数据修改

有序广播的核心是 “优先级排序” 和 “数据交互”,高优先级接收者可以修改广播数据,甚至中断广播,阻止低优先级接收者接收。

示例 3:有序广播实现 “消息审核”(优先级与中断)

场景:应用发送一条 “评论消息” 广播,高优先级的 “审核接收器” 先验证内容,合法则放行,非法则中断广播。

步骤 1:定义 3 个广播接收器(不同优先级)
// 1. 高优先级:审核接收器(优先级1000)
public class AuditReceiver extends BroadcastReceiver {
    private static final String TAG = "OrderedBroadcastTest";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "审核接收器(高优先级)接收广播");
        // 接收广播数据
        String comment = intent.getStringExtra("comment");
        Log.d(TAG, "待审核评论:" + comment);

        // 模拟审核逻辑:包含敏感词则中断广播
        if (comment.contains("敏感词")) {
            Log.d(TAG, "评论包含敏感词,中断广播");
            abortBroadcast(); // 中断广播,低优先级接收器无法接收
            Toast.makeText(context, "评论包含敏感词,发送失败", Toast.LENGTH_SHORT).show();
        } else {
            Log.d(TAG, "评论审核通过,放行广播");
            // 可修改广播数据(添加审核标记)
            Bundle bundle = new Bundle();
            bundle.putString("audit_result", "审核通过");
            setResultExtras(bundle); // 保存修改后的数据
        }
    }
}

// 2. 中优先级:通知接收器(优先级500)
public class NotifyReceiver extends BroadcastReceiver {
    private static final String TAG = "OrderedBroadcastTest";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "通知接收器(中优先级)接收广播");
        // 获取审核接收器传递的数据
        Bundle extras = getResultExtras(true);
        String auditResult = extras.getString("audit_result");
        String comment = intent.getStringExtra("comment");
        Log.d(TAG, "审核结果:" + auditResult + ",评论:" + comment);
        // 执行通知逻辑(如发送推送)
    }
}

// 3. 低优先级:统计接收器(优先级100)
public class StatisticReceiver extends BroadcastReceiver {
    private static final String TAG = "OrderedBroadcastTest";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "统计接收器(低优先级)接收广播");
        // 执行统计逻辑(如记录评论数量)
    }
}
步骤 2:在 Manifest 中注册有序广播(指定优先级)
<!-- 注册有序广播接收器,通过priority指定优先级 -->
<receiver
    android:name=".AuditReceiver"
    android:exported="false">
    <intent-filter android:priority="1000"> <!-- 最高优先级 -->
        <action android:name="com.example.broadcast.ACTION_COMMENT_SEND" />
    </intent-filter>
</receiver>

<receiver
    android:name=".NotifyReceiver"
    android:exported="false">
    <intent-filter android:priority="500"> <!-- 中优先级 -->
        <action android:name="com.example.broadcast.ACTION_COMMENT_SEND" />
    </intent-filter>
</receiver>

<receiver
    android:name=".StatisticReceiver"
    android:exported="false">
    <intent-filter android:priority="100"> <!-- 低优先级 -->
        <action android:name="com.example.broadcast.ACTION_COMMENT_SEND" />
    </intent-filter>
</receiver>
步骤 3:发送有序广播并测试
public class CommentActivity extends AppCompatActivity {
    private EditText etComment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_comment);
        etComment = findViewById(R.id.et_comment);

        // 发送有序广播
        findViewById(R.id.btn_send_comment).setOnClickListener(v -> {
            String comment = etComment.getText().toString().trim();
            Intent intent = new Intent("com.example.broadcast.ACTION_COMMENT_SEND");
            intent.putExtra("comment", comment);

            // 发送有序广播(参数:Intent、接收者权限(null表示无权限))
            sendOrderedBroadcast(intent, null);
        });
    }
}
测试结果:
  • 场景 1:评论内容为 “这是一条正常评论”:
    D/OrderedBroadcastTest: 审核接收器(高优先级)接收广播
    D/OrderedBroadcastTest: 待审核评论:这是一条正常评论
    D/OrderedBroadcastTest: 评论审核通过,放行广播
    D/OrderedBroadcastTest: 通知接收器(中优先级)接收广播
    D/OrderedBroadcastTest: 审核结果:审核通过,评论:这是一条正常评论
    D/OrderedBroadcastTest: 统计接收器(低优先级)接收广播
    
  • 场景 2:评论内容为 “这是一条包含敏感词的评论”:
    D/OrderedBroadcastTest: 审核接收器(高优先级)接收广播
    D/OrderedBroadcastTest: 待审核评论:这是一条包含敏感词的评论
    D/OrderedBroadcastTest: 评论包含敏感词,中断广播
    
    (中、低优先级接收器未接收广播)
关键说明:
  • 优先级范围为-1000~1000,数值越高,接收顺序越靠前;
  • abortBroadcast()方法只能在有序广播中使用,用于中断广播传递;
  • setResultExtras()getResultExtras()用于接收者之间传递数据,实现交互。

4.2 本地广播:应用内安全通信,高效无泄漏

本地广播(LocalBroadcast)的传播范围仅限于当前应用,不会被其他应用接收或伪造,安全性高、效率高,适合应用内组件间通信。

示例 4:LocalBroadcastManager 实现应用内数据同步

场景:Service 下载文件完成后,通过本地广播通知 Activity 刷新 UI。

步骤 1:创建本地广播接收器
public class DownloadCompleteReceiver extends BroadcastReceiver {
    private static final String TAG = "LocalBroadcastTest";
    public static final String ACTION_DOWNLOAD_COMPLETE = "com.example.broadcast.ACTION_DOWNLOAD_COMPLETE";
    // 回调接口:通知Activity刷新UI
    private OnDownloadCompleteListener mListener;

    // 构造方法:传入回调接口
    public DownloadCompleteReceiver(OnDownloadCompleteListener listener) {
        mListener = listener;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
            String filePath = intent.getStringExtra("file_path");
            long fileSize = intent.getLongExtra("file_size", 0);
            Log.d(TAG, "本地广播:下载完成,路径:" + filePath + ",大小:" + fileSize + "KB");
            // 回调Activity刷新UI
            if (mListener != null) {
                mListener.onComplete(filePath, fileSize);
            }
        }
    }

    // 回调接口
    public interface OnDownloadCompleteListener {
        void onComplete(String filePath, long fileSize);
    }
}
步骤 2:在 Activity 中注册 / 注销本地广播
public class DownloadActivity extends AppCompatActivity implements DownloadCompleteReceiver.OnDownloadCompleteListener {
    private LocalBroadcastManager mLocalBroadcastManager;
    private DownloadCompleteReceiver mDownloadReceiver;
    private TextView tvDownloadStatus;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_download);
        tvDownloadStatus = findViewById(R.id.tv_download_status);

        // 1. 初始化本地广播管理器
        mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
        // 2. 注册本地广播
        registerLocalBroadcast();

        // 模拟下载按钮点击
        findViewById(R.id.btn_start_download).setOnClickListener(v -> {
            // 启动下载服务
            startService(new Intent(this, DownloadService.class));
            tvDownloadStatus.setText("下载中...");
        });
    }

    // 注册本地广播
    private void registerLocalBroadcast() {
        mDownloadReceiver = new DownloadCompleteReceiver(this);
        IntentFilter filter = new IntentFilter();
        filter.addAction(DownloadCompleteReceiver.ACTION_DOWNLOAD_COMPLETE);
        // 本地广播注册(与全局广播的区别:用LocalBroadcastManager注册)
        mLocalBroadcastManager.registerReceiver(mDownloadReceiver, filter);
    }

    // 实现回调接口:刷新UI
    @Override
    public void onComplete(String filePath, long fileSize) {
        tvDownloadStatus.setText("下载完成!路径:" + filePath + ",大小:" + fileSize + "KB");
    }

    // 注销本地广播
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mDownloadReceiver != null) {
            mLocalBroadcastManager.unregisterReceiver(mDownloadReceiver);
        }
    }
}
步骤 3:在 Service 中发送本地广播
public class DownloadService extends Service {
    private static final String TAG = "LocalBroadcastTest";

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 模拟文件下载(子线程中执行)
        new Thread(() -> {
            try {
                Log.d(TAG, "开始下载文件...");
                Thread.sleep(3000); // 模拟下载耗时
                // 模拟下载结果
                String filePath = "/sdcard/download/test.apk";
                long fileSize = 2048; // 2MB

                // 发送本地广播(通知下载完成)
                Intent broadcastIntent = new Intent(DownloadCompleteReceiver.ACTION_DOWNLOAD_COMPLETE);
                broadcastIntent.putExtra("file_path", filePath);
                broadcastIntent.putExtra("file_size", fileSize);
                // 用LocalBroadcastManager发送本地广播
                LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);

                stopSelf(); // 停止服务
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        return START_NOT_STICKY;
    }
}
本地广播与全局广播的核心区别:
特性本地广播(LocalBroadcast)全局广播(Global Broadcast)
传播范围仅当前应用整个系统
安全性高(无泄露 / 伪造风险)低(可能被监听 / 伪造)
注册 / 发送方式通过 LocalBroadcastManager通过 Context(sendBroadcast)
权限要求可设置权限限制
适用场景应用内组件通信跨应用通信、系统事件监听

4.3 跨应用广播:实现应用间消息传递

跨应用广播是全局广播的典型用法,允许一个应用发送广播,其他应用注册对应 Action 后接收并处理。

示例 5:跨应用广播实现 “应用 A 通知应用 B 更新数据”
步骤 1:应用 A(发送方)发送跨应用广播
public class SenderActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sender);

        // 发送跨应用广播
        findViewById(R.id.btn_send_cross_app).setOnClickListener(v -> {
            Intent intent = new Intent("com.example.broadcast.ACTION_CROSS_APP_UPDATE");
            // 携带数据(跨应用传递)
            intent.putExtra("update_data", "应用A的最新数据");
            // Android 8.0后跨应用广播需指定包名(否则接收不到)
            intent.setPackage("com.example.receiverapp"); // 接收方应用包名
            // 发送广播(可设置权限,限制接收方)
            sendBroadcast(intent);
            Toast.makeText(this, "跨应用广播发送成功", Toast.LENGTH_SHORT).show();
        });
    }
}
步骤 2:应用 B(接收方)注册广播接收
// 应用B中创建广播接收器
public class CrossAppReceiver extends BroadcastReceiver {
    private static final String TAG = "CrossAppTest";

    @Override
    public void onReceive(Context context, Intent intent) {
        if ("com.example.broadcast.ACTION_CROSS_APP_UPDATE".equals(intent.getAction())) {
            String updateData = intent.getStringExtra("update_data");
            Log.d(TAG, "收到应用A的跨应用广播:" + updateData);
            Toast.makeText(context, "收到更新数据:" + updateData, Toast.LENGTH_SHORT).show();
            // 执行更新逻辑(如刷新本地数据库)
        }
    }
}
步骤 3:应用 B 在 Manifest 中注册广播(允许跨应用接收)
<receiver
    android:name=".CrossAppReceiver"
    android:exported="true"> <!-- 允许接收跨应用广播 -->
    <intent-filter>
        <action android:name="com.example.broadcast.ACTION_CROSS_APP_UPDATE" />
    </intent-filter>
</receiver>
跨应用广播的关键注意事项:
  • Android 8.0 后,跨应用广播必须指定接收方包名(setPackage()),否则系统会视为 “后台广播” 而拦截;
  • 接收方必须设置android:exported="true",否则无法接收跨应用广播;
  • 为了安全,可给广播设置权限:发送方发送时指定权限,接收方必须声明该权限才能接收;
    <!-- 发送方和接收方都需在Manifest中声明权限 -->
    <permission
        android:name="com.example.broadcast.PERMISSION_CROSS_APP"
        android:protectionLevel="normal" />
    
    <!-- 接收方需添加权限声明 -->
    <uses-permission android:name="com.example.broadcast.PERMISSION_CROSS_APP" />
    
    <!-- 发送方发送广播时指定权限 -->
    sendBroadcast(intent, "com.example.broadcast.PERMISSION_CROSS_APP");
    

五、系统广播:监听系统状态变化(常用场景汇总)

Android 系统内置了大量广播,用于通知应用系统状态变化。开发中无需自己发送,只需注册对应 Action 即可接收,下面列举最常用的系统广播及用法。

5.1 常用系统广播 Action 与权限

系统广播 Action触发场景所需权限适用注册方式
Intent.ACTION_BOOT_COMPLETED设备开机完成RECEIVE_BOOT_COMPLETED静态注册(推荐)
Intent.ACTION_CONNECTIVITY_CHANGE网络状态变化(连接 / 断开)无(Android 6.0 后)动态注册
Intent.ACTION_BATTERY_LOW电池电量低(通常 < 15%)动态注册
Intent.ACTION_BATTERY_OKAY电池电量恢复(>20%)动态注册
Intent.ACTION_SCREEN_ON屏幕点亮动态注册
Intent.ACTION_SCREEN_OFF屏幕熄灭动态注册
Intent.ACTION_PACKAGE_ADDED安装新应用静态 / 动态注册
Intent.ACTION_PACKAGE_REMOVED卸载应用静态 / 动态注册
Intent.ACTION_TIME_TICK时间变化(每分钟一次)动态注册

示例 6:动态注册监听网络状态变化

public class NetworkChangeReceiver extends BroadcastReceiver {
    private static final String TAG = "SystemBroadcastTest";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "收到网络状态变化广播");
        // 检查网络状态
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork());
            if (capabilities != null) {
                if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
                    Toast.makeText(context, "当前网络:WiFi", Toast.LENGTH_SHORT).show();
                } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
                    Toast.makeText(context, "当前网络:移动数据", Toast.LENGTH_SHORT).show();
                } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
                    Toast.makeText(context, "当前网络:以太网", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(context, "当前无网络连接", Toast.LENGTH_SHORT).show();
                }
            } else {
                Toast.makeText(context, "当前无网络连接", Toast.LENGTH_SHORT).show();
            }
        } else {
            // 兼容Android 6.0以下版本
            NetworkInfo networkInfo = cm.getActiveNetworkInfo();
            if (networkInfo != null && networkInfo.isConnected()) {
                if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                    Toast.makeText(context, "当前网络:WiFi", Toast.LENGTH_SHORT).show();
                } else if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                    Toast.makeText(context, "当前网络:移动数据", Toast.LENGTH_SHORT).show();
                }
            } else {
                Toast.makeText(context, "当前无网络连接", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

// 在Activity中注册/注销
public class NetworkMonitorActivity extends AppCompatActivity {
    private NetworkChangeReceiver mNetworkReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_network_monitor);
        // 注册网络广播
        mNetworkReceiver = new NetworkChangeReceiver();
        IntentFilter filter = new IntentFilter(Intent.ACTION_CONNECTIVITY_CHANGE);
        registerReceiver(mNetworkReceiver, filter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 注销广播
        unregisterReceiver(mNetworkReceiver);
    }
}

关键说明:

  • 部分系统广播(如网络变化)在 Android 8.0 后仅支持动态注册,静态注册无法接收;
  • 监听网络状态需在 Manifest 中添加 “查看网络状态” 权限(Android 6.0 后无需动态申请):
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    
  • 系统广播的 Action 是系统预定义的,需准确引用(避免拼写错误)。

六、Android 8.0 + 广播限制与适配方案(重点避坑)

Android 8.0(API 26)引入了 “后台执行限制”,对广播的使用做了严格限制,这是很多开发者遇到 “广播失效” 的核心原因。

6.1 Android 8.0 + 广播的核心限制

  • 限制 1:后台静态广播失效 。应用未启动时,静态注册的广播接收器无法接收大部分广播(系统核心广播除外,如开机、安装应用);
  • 限制 2:后台应用无法接收隐式广播 。隐式广播是指未指定具体组件的广播(如只设置 Action,未设置 Component),后台应用无法接收;
  • 限制 3:自定义广播需指定包名或组件 。发送自定义广播时,必须通过setPackage()setComponent()指定接收方,否则后台应用无法接收。

6.2 适配方案:如何让广播在 Android 8.0 + 正常工作?

方案 1:动态注册替代静态注册(优先推荐)

对于非系统核心广播,尽量使用动态注册,不受后台限制影响。

方案 2:发送广播时指定接收方包名

发送自定义广播时,通过intent.setPackage("接收方包名")指定接收方,避免隐式广播被拦截。

// Android 8.0+发送自定义广播的正确方式
Intent intent = new Intent("com.example.broadcast.ACTION_CUSTOM");
intent.setPackage("com.example.myapp"); // 指定接收方应用包名
intent.putExtra("data", "测试数据");
sendBroadcast(intent);
方案 3:使用前台服务配合广播

如果需要在应用后台接收广播,可启动前台服务,在服务中动态注册广播(前台服务优先级高,不会被系统轻易杀死)。

方案 4:使用 PendingIntent 发送广播

对于需要延迟发送的广播(如定时任务),可通过PendingIntent发送,不受后台限制影响。

// 使用PendingIntent发送广播
Intent intent = new Intent("com.example.broadcast.ACTION_DELAY");
PendingIntent pendingIntent = PendingIntent.getBroadcast(
        this, 0, intent, PendingIntent.FLAG_IMMUTABLE
);

// 定时发送(5秒后)
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmManager.setExactAndAllowWhileIdle(
            AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + 5000,
            pendingIntent
    );
} else {
    alarmManager.setExact(
            AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + 5000,
            pendingIntent
    );
}
方案 5:使用系统允许的例外广播

Android 8.0 后仍支持静态注册的系统广播(部分),可在官方文档查询完整列表,常见的有:

  • ACTION_BOOT_COMPLETED(开机完成)
  • ACTION_LOCKED_BOOT_COMPLETED(锁屏状态下开机完成)
  • ACTION_PACKAGE_ADDED(应用安装)
  • ACTION_PACKAGE_REMOVED(应用卸载)
  • ACTION_BATTERY_LOW(电量低)

七、广播常见坑点与避坑指南(实战经验总结)

7.1 坑点 1:静态广播注册后无法接收

  • 现象:在 Manifest 中注册了静态广播,但始终接收不到广播;
  • 原因:
    1. Android 8.0 后,该广播不属于系统例外广播,静态注册被拦截;
    2. 广播 Action 拼写错误,或未添加category android:name="android.intent.category.DEFAULT"
    3. 接收器设置了android:exported="false",无法接收系统或跨应用广播;
  • 解决方案:
    1. 非系统核心广播,改用动态注册;
    2. 检查 Action 拼写和 IntentFilter 配置,确保正确;
    3. 接收系统或跨应用广播时,设置android:exported="true"

7.2 坑点 2:Android 8.0 后自定义广播接收不到

  • 现象:应用在 Android 8.0 以下正常接收广播,升级到 8.0 + 后失效;
  • 原因:Android 8.0 + 限制后台应用接收隐式广播,未指定接收方包名;
  • 解决方案:
    1. 发送广播时通过setPackage()指定接收方包名;
    2. 改用动态注册或本地广播。

7.3 坑点 3:广播onReceive()中做耗时操作导致 ANR

  • 现象:接收广播后应用无响应,弹出 ANR 提示;
  • 原因:onReceive()运行在主线程,执行时间超过 10 秒;
  • 解决方案:
    1. 耗时操作(如网络请求、文件读写)移到 Service 或子线程中执行;
    2. 如需在广播中启动 Service,Android 8.0 + 需用startForegroundService()
    // 广播中启动Service处理耗时操作
    @Override
    public void onReceive(Context context, Intent intent) {
        Intent serviceIntent = new Intent(context, MyService.class);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(serviceIntent);
        } else {
            context.startService(serviceIntent);
        }
    }
    

7.4 坑点 4:动态广播未注销导致内存泄漏

  • 现象:应用退出后,通过 LeakCanary 检测到内存泄漏;
  • 原因:动态注册广播后,未在组件销毁(onDestroy())时注销,导致接收器持有组件引用;
  • 解决方案:
    1. 在 Activity 的onDestroy()、Service 的onDestroy()中强制注销广播;
    2. 避免在 Application 中动态注册广播(除非手动管理生命周期)。

7.5 坑点 5:跨应用广播接收不到,权限配置错误

  • 现象:应用 A 发送跨应用广播,应用 B 注册后无法接收;
  • 原因:
    1. 应用 B 未声明接收权限,或应用 A 发送时指定了权限但应用 B 未声明;
    2. 应用 B 的接收器android:exported="false",禁止接收跨应用广播;
  • 解决方案:
    1. 发送方指定权限时,接收方需在 Manifest 中声明该权限;
    2. 接收方设置android:exported="true"
    <!-- 接收方声明权限 -->
    <uses-permission android:name="com.example.broadcast.PERMISSION_RECEIVE" />
    

7.6 坑点 6:广播传递大数据导致崩溃

  • 现象:通过广播传递大型对象(如高清图片、大体积 JSON 数据)时,应用崩溃;
  • 原因:Intent 传递数据的大小有限制(通常不超过 1MB),超过限制会抛出TransactionTooLargeException
  • 解决方案:
    1. 避免通过广播传递大数据,改用文件存储、数据库或 ContentProvider;
    2. 如需传递对象,仅传递关键信息(如文件路径、ID),而非完整数据。

八、总结:BroadcastReceiver 核心知识点图谱

到这里,BroadcastReceiver 的核心内容已经全部讲完,我们用一张图谱梳理重点:

plaintext

BroadcastReceiver核心知识点
├── 基础认知:跨组件/跨应用消息中转站、3个核心特性、典型场景
├── 分类维度:
│   - 发送方式:有序广播(优先级、中断、数据修改)、无序广播(异步、无顺序)
│   - 注册方式:静态广播(Manifest注册、系统事件)、动态广播(代码注册、灵活可控)
│   - 传播范围:全局广播(跨应用)、本地广播(应用内、安全高效)
├── 生命周期:注册→onReceive()→销毁(极短、主线程执行)
├── 进阶用法:跨应用广播、系统广播监听、PendingIntent发送延迟广播
├── 适配方案:Android 8.0+广播限制、静态转动态、指定接收方包名
├── 避坑指南:ANR、内存泄漏、广播失效、权限错误、大数据传递崩溃

其实广播的学习关键是 “分清场景、选对类型、避开限制”—— 应用内通信优先用本地广播,跨应用通信用全局广播并指定包名,系统事件监听根据需求选静态或动态注册,Android 8.0 + 需重点适配后台限制。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值