在 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
<receiver android:name=".ModeChangeReceiver" android:exported="true">
<intent-filter>
<action android:name="custom.start.schoolfinance.MODE_CHANGE"/>
</intent-filter>
</receiver>
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+ 对静态注册隐式广播限制很大,跨应用通信优先用 显式广播
- 动态注册灵活,但依赖进程常驻
- 静态注册 + 显式广播是唤醒未启动应用的唯一通用方式
- 系统广播可以作为触发器,把消息中转给目标应用