在 Android 开发中,广播(Broadcast) 是一种非常常用的组件间通信机制,既可以在应用内部解耦模块,也可以在多个应用之间传递消息。
但是随着 Android 版本的演进,特别是 8.0+(API 26) 之后,广播的使用方式有了很多限制和坑。
今天我们结合一个实际场景,来系统梳理一下 跨应用广播 的正确用法。
1. 广播的分类
Android 中的广播按发送/接收方式主要有两类:
分类
注册方式
特点
静态注册
Manifest 配置
进程未启动时也能接收(部分系统广播受限),App 会被系统唤醒
动态注册
代码中注册
只能在进程运行时接收,生命周期跟随注册者,灵活但需手动管理
2. Android 8.0+ 的限制
从 Android 8.0(API 26) 开始,大部分隐式广播(未指定包名/组件的广播)禁止静态注册。
比如:
java
复制代码
sendBroadcast(new Intent("custom.start.schoolfinance.MODE_CHANGE"));
在 8.0+ 中,Manifest 静态注册的自定义接收器将收不到此广播。
但以下情况不受限制:
显式广播(指定包名或组件名)
系统允许的部分广播(如 BOOT_COMPLETED、PACKAGE_ADDED 等)
应用内部广播(LocalBroadcast)
3. 显式广播的正确用法
显式广播就是明确指定接收方,例如指定包名:
java
复制代码
Intent intent = new Intent("custom.start.schoolfinance.MODE_CHANGE");
intent.setPackage("com.appB.package"); // 只发给 B 应用
sendBroadcast(intent);
优点:
不受 Android 8.0+ 静态注册限制
安全性高,不会被第三方应用接收
投递效率高,只发给目标应用
缺点:
只能发给指定的应用,广播范围受限
4. Application 中动态注册
如果只要求在应用运行时接收广播,可以在 Application 中动态注册:
java
复制代码
public class MyApplication extends Application {
private final BroadcastReceiver modeChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int mode = intent.getIntExtra("mode", -1);
Log.d("桌面APP", "收到模式变更广播: mode=" + mode);
}
};
@Override
public void onCreate() {
super.onCreate();
registerReceiver(modeChangeReceiver, new IntentFilter("custom.start.schoolfinance.MODE_CHANGE"));
}
}
优点
不受 Android 8.0 隐式广播限制
灵活,可在运行时按需注册
缺点
应用进程未启动时无法接收
5. 跨应用发送广播示例(A → B)
A 应用发送:
java
复制代码
Intent intent = new Intent("custom.start.schoolfinance.MODE_CHANGE");
intent.putExtra("mode", 1);
intent.setPackage("com.appB.package"); // 显式指定
sendBroadcast(intent);
B 应用接收(Application 中动态注册):
java
复制代码
@Override
public void onCreate() {
super.onCreate();
registerReceiver(modeChangeReceiver, new IntentFilter("custom.start.schoolfinance.MODE_CHANGE"));
}
📌 适用:B 已启动或后台常驻时实时接收
6. 让未启动的应用也能接收
如果希望 B 即使没启动也能收到广播,必须:
在 Manifest 中静态注册接收器
广播必须是显式广播
B 应用:
xml
复制代码
7. 系统广播中转给其他应用
有时 A 需要在收到系统广播(如 BOOT_COMPLETED)后,把信息转发给 B:
java
复制代码
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
Intent sendToB = new Intent("custom.start.schoolfinance.MODE_CHANGE");
sendToB.putExtra("mode", 1);
sendToB.setPackage("com.appB.package");
context.sendBroadcast(sendToB);
}
}
}
8. 选择方案的参考表
需求
推荐方案
进程未启动也能接收
静态注册 + 显式广播
应用已运行,实时接收
Application 中动态注册
只发给一个应用
显式广播(setPackage/setComponent)
多个应用都要接收
隐式广播(注意 8.0+ 限制)
9.为什么 setIntent() 必须调用
singleTask + FLAG_ACTIVITY_CLEAR_TOP 场景下的 Intent 参数传递流程图 ,为什么 setIntent() 必须调用?
css
复制代码
┌─────────────────────────┐
│ 启动 Activity A (首次) │
│ onCreate(Intent old) │
│ mIntent = oldIntent │
└────────────┬────────────┘
│
▼
用户停留在 A
│
▼
┌─────────────────────────┐
│ 再次启动 A │
│ Intent: machineMode=1 │
│ FLAG_ACTIVITY_CLEAR_TOP │
└────────────┬────────────┘
│
A 已经存在 → 不走 onCreate()
│
▼
┌─────────────────────────────┐
│ 调用 onNewIntent(newIntent) │
│ (machineMode=1) │
└─────────────────────────────┘
│
│ (系统并不会更新 mIntent)
▼
mIntent 还是旧的 → getIntent() 读不到新参数 ❌
│
▼
调用 setIntent(newIntent) ✔
│
▼
mIntent 更新 → getIntent() 拿到最新参数
总结
不调用 setIntent() :getIntent() 永远是第一次启动时的旧数据。
调用 setIntent() :getIntent() 会返回最新一次启动传进来的参数。
所以:
java
复制代码
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent); // 必须,保证 getIntent() 是最新
handleIntent(intent);
}
总结
Android 8.0+ 对静态注册隐式广播限制很大,跨应用通信优先用 显式广播
动态注册灵活,但依赖进程常驻
静态注册 + 显式广播是唤醒未启动应用的唯一通用方式
系统广播可以作为触发器,把消息中转给目标应用
