30、BroadcastReceiver调试:广播注册、有序广播、粘性广播、超时问题
广播这玩意儿,在Android系统里就像个传话筒。但传话筒用不好,要么消息丢了,要么卡死主线程。今天咱们就聊聊广播调试的那些坑。
广播注册的两种姿势
广播注册分静态和动态,我习惯叫它们「清单注册」和「代码注册」。区别很明显:
| 注册方式 | 生命周期 | 调试要点 |
|---|---|---|
| 静态注册(AndroidManifest.xml) | 应用安装后一直存在 | 注意Android 8.0后大部分隐式广播被限制 |
| 动态注册(registerReceiver) | 跟随组件生命周期 | 记得在onPause/onDestroy中unregister |
我在项目中遇到过一个问题:动态注册的广播接收器在Activity销毁后还在接收广播。说白了就是忘了在onDestroy里调用unregisterReceiver。结果呢?内存泄漏,还可能导致空指针崩溃。
有序广播的调试
有序广播(Ordered Broadcast)是按优先级顺序传递的。每个接收者可以截断广播,也可以修改结果数据。
调试有序广播时,我一般会关注三点:
- 优先级设置:通过intent-filter的android:priority属性设置,数值越大优先级越高
- 截断广播:调用abortBroadcast()可以阻止后续接收者
- 结果传递:使用setResultExtras/getResultExtras传递数据
举个例子,你想想看:
// 发送有序广播
Intent intent = new Intent("com.example.MY_ORDERED_BROADCAST");
sendOrderedBroadcast(intent, null);
// 高优先级接收者
public class HighPriorityReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Bundle extras = new Bundle();
extras.putString("data", "来自高优先级");
setResultExtras(extras);
// 如果不想让低优先级收到,可以截断
// abortBroadcast();
}
}
调试时有个技巧:在adb shell里用dumpsys activity broadcasts可以查看当前系统中注册的所有广播接收器及其优先级。嗯,这个命令我经常用。
粘性广播的坑
粘性广播(Sticky Broadcast)已经被标记为deprecated了。为什么?因为它会导致状态不一致。
粘性广播的特点是:发送后广播会一直存在,新注册的接收器能立即收到最后一条粘性广播。听起来很方便对吧?但问题来了:
- 权限控制困难,任何应用都能读取粘性广播数据
- 调试时很难复现问题,因为广播状态是持久的
- Android 5.0之后已经不建议使用
广播超时问题
广播超时是ANR的常见原因之一。系统给广播接收器的执行时间默认是10秒(前台广播)或60秒(后台广播)。
为什么会超时?说白了就是你在onReceive里做了耗时操作。记住:onReceive运行在主线程。
我曾经排查过一个线上ANR问题,日志显示:
Broadcast of Intent { act=com.example.ACTION_SYNC flg=0x10 }
took 15000ms, timeout = 10000ms
一看就知道,有人在onReceive里直接做了网络请求。正确的做法是:
public class SafeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 只做轻量操作
Intent serviceIntent = new Intent(context, MyService.class);
context.startService(serviceIntent);
// 或者使用goAsync
final PendingResult pendingResult = goAsync();
new Thread(() -> {
// 耗时操作
pendingResult.finish();
}).start();
}
}
调试工具与技巧
我常用的广播调试三板斧:
- adb shell dumpsys activity broadcasts:查看所有注册的广播接收器
- adb logcat -s "BroadcastQueue:B":过滤广播相关的日志
- 设置-开发者选项-显示所有ANR:开启后能捕获广播超时
另外,在Android Studio的Profiler里也能看到广播的发送和接收情况。嗯,这个功能很多人不知道。
知识体系总览
下面这张图总结了广播调试的核心要点:
避坑指南
最后分享几个我踩过的坑:
- 我曾经在onReceive里调用了ContentResolver.query,结果数据库操作太慢导致ANR。后来改成用AsyncQueryHandler或者直接丢给Service处理。
- 我曾经调试有序广播时发现低优先级接收器收不到广播,查了半天才发现是高优先级调用了abortBroadcast()。嗯,这个坑很多人会踩。
- 我曾经在Android 8.0设备上发现静态注册的广播不生效,后来才知道大部分隐式广播已经被限制了。解决办法是改用动态注册或者JobScheduler。
广播调试其实不难,关键是要理解它的生命周期和超时机制。记住一句话:onReceive不是做苦力的地方。