Android-Broadcast

Android-BroadcastReceiver

BroadcastReceiver 本质上通过设置过滤来有选择地响应某些事件,例如网络状态的变化、锁屏的变化等等,其本身没有用户界面,但可以启动 Activity 或 Service,或使用 Notification 通知用户。

1
2
3
4
5
6
public class DemoReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 接收广播后的响应
}
}

1. Broadcast的分类

1.1 静态和动态

静态和动态仅针对接收器 BroadcastReceiver,由注册方式决定。

1.1.1 静态Receiver

在 Manifest 中静态声明注册的 BroadcastReceiver 就称为静态 Receiver。静态 Receiver 会随系统的启动而保持活跃,即使 App 未运行,只要接收到指定广播就会触发响应。

1
2
3
4
5
6
7
8
<receiver
android:name=".DemoReceiver"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="ACTION_DEMO">
</intent-filter>
</receiver>

1.1.2 动态Receiver

通过代码动态注册和销毁的 BroadcastReceiver 就称为动态 Receiver。动态 Receiver 只有在生命周期内才能接收广播。

1
2
3
4
5
DemoReceiver demoReceiver = new DemoReceiver();
// 动态注册
registerReceiver(demoReceiver, new IntentFilter("ACTION_DEMO"));
// 销毁广播接收器
unregisterReceiver(demoReceiver);

registerReceiver()unregisterReceiver() 都是 Context 中的方法,但需要注意的是,Register 和 Unregister 必须由同一个 Context 对象调用,例如:

  • AActivity 注册的 Receiver,必须由 AActivity 反注册。
  • AActivity 注册的 Receiver,由其他 Activity 或 Application 反注册会抛出异常。
  • 同理,Application 注册的 Receiver,由其他 Activity 反注册也会抛出异常。

注册和反注册一定要确保成对调用,如果 Activity 注册 Receiver 后未主动销毁,会导致 Activity 退出后无法回收导致内存泄漏。

动态注册的 Receiver 只能接收「隐式」广播

1.2 全局和本地

全局和本地同时针对 Broadcast 和 BroadcastReceiver,由 Broadcast 发送方式和 Receiver 的注册方式决定。

1.2.1 全局广播

全局 Broadcast 可以被任意全局 Receiver 接收,即使 App 并未启动。

  • 静态注册的 Receiver,如果 exported=true 则为全局 Receiver,否则为本地 Receiver。
  • 通过 Context#registerReceiver() 注册的是全局 Receiver。
  • Context#sendBroadcast() 发送的是全局广播。

全局 Receiver 通常应该设置 permission 属性,可以视为「密钥」,如果 permission 有值,则仅当广播的 permission 属性匹配成功时才会响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 静态 Receiver 设置 Permission:
<receiver
android:name=".DemoReceiver"
android:exported="true"
android:enabled="true"
android:permission="PERMISSION_STR">
<intent-filter>
<action android:name="ACTION_DEMO">
</intent-filter>
</receiver>

// 动态 Receiver 设置 Permission:
context.reisterReceiver(receiver, intentFilter, permission, schedulerHandler);

1.2.2 本地广播

本地 Broadcast 通过 LocalBroadcastManager 发送,只能在应用程序内部传播。本地 Receiver 通过 LocalBroadcastManager 注册和反注册,也只能接收应用内部的 Broadcast。

1
2
3
4
5
6
// 使用 Context 获取 LocalBroadcastManager 实例对象
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
// 注册本地 Receiver
localBroadcastManager.registerReceiver(new DemoReceiver(), new IntentFilter("ACTION_DEMO"));
// 发送本地 Broadcast
localBroadcastManager.sendBroadcast(new Intent("ACTION_DEMO"));

本地 Broadcast 无法指定 permission 属性,本地注册的 Receiver 也无法过滤 permission 属性。

1.3 标准和有序

标准广播和有序广播仅针对 Broadcast,由发送方式决定。

1.3.1 标准Broadcast

标准 Broadcast 可以被所有类型符合的 Receiver 接收,且不能被拦截和修改。

(1)发送「全局」标准 Broadcast:

1
2
3
4
// 不指定 Permission 的全局 Broadcast:
context.sendBroadcast(new Intent("ACTION_DEMO"));
// 指定 Permission 的全局 Broadcast:
context.sendBroadcast(new Intent("ACTION_DEMO"), "PERMISSION_STR");

(2)发送「本地」标准 Broadcast:

1
localBroadcastManager.sendBroadcast(new Intent("ACTION_DEMO"));

1.3.2 有序Broadcast

只有全局 Broadcast 可以有序发送,按照 Receiver 的优先级(范围:-1000 ~ 1000)从高到低逐级传播,Receiver 可以修改广播数据、也可以终止广播事件。

当 Priority 属性相等时,动态 Receiver 比静态 Receiver 优先。

(1)设置静态 Receiver 的优先级:

1
2
3
4
5
6
7
<receiver
android:name=".DemoReceiver">
<intent-filter
android:priority="100">
<action android:name="ACTION_DEMO">
</intent-filter>
</receiver>

(2)设置动态 Receiver 的优先级:

1
2
3
IntentFilter intentFilter = new IntentFilter("ACTION_DEMO");
intentFilter.setPriority(100);
context.registerReceiver(new DemoReceiver(), intentFilter);

(3)发送「全局」有序 Broadcast(必须指定 permission 属性):

1
context.sendOrderedBroadcast(new Intent("ACTION_DEMO"), "PERMISSION_STR");

本地 Broadcast 无法设置有序。

如果 Receiver 需要消费掉有序 Broadcast,阻止其继续向更低优先级的 Receiver 传递,可以通过 BroadcastReceiver#abortBroadcast() 方法中断后续传递:

1
2
3
4
5
6
7
@Override
public void onReceive(Context context, Intent intent) {
Log.d("DemoReceiver", "--- onReceive ---");

// 该方法调用仅在收到的是有序 Broadcast 时有效。
abortBroadcast();
}

如果 Receiver 想向后续优先级更低的 Receiver 传递一些信息,可以通过 BroadcastReceiver#setResultExtras(Bundle) 存入,由下一个 Receiver 通过 BroadcastReceiver#getResultExtras(boolean) 取出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class HighPriorityReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 存入当前高优先级 Receiver 的数据:
setResultExtras(new Bundle());
}
}

public class LowPriorityReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 取出上一个 Receiver 存入的数据
// - 入参为 true 表示如果没有数据,会自动构造一个空 Map。
// - 入参为 false 表示如果没有数据,会返回 null。
Bundle lastData = getResultExtras(true);
}
}

2. Broadcast的限制

2.1 耗时任务

BroadcastReceiver 的 onReceive() 默认运行在主进程的主线程,因此前台卡顿超过 10 秒、后台卡顿超过 60 秒就会抛出 ANR。但也不应该在 onReceive() 中开启子线程,因为 BroadcastReceiver 可能在 App 未启动的时候触发并启动进程,如果开启子线程,onReceive() 方法立即返回,则主线程及主进程可能会被退出,导致其子线程被中断,这种情况是不可预料的,因此不应该在 BroadcastReceiver 中通过子线程的方式处理耗时任务。

BroadcastReceiver 通常作为一个通知者,在接收到特定的消息后,如果需要执行的耗时任务与 UI 有关,可以通过启动 Activity 的方式由 Activity 处理,如果与 UI 无关则可以通过启动 Service 的方式由 Service 处理,此时 App 进程被正常启动,Activity 或 Service 都将按照各自的生命周期执行,因此任务的处理是可预料的。

2.2 显式和隐式广播

全局 BroadcastReceiver 在收到符合条件的 Broadcast 后,如果原本 App 未启动则会先启动 Application,但不会启动 Activity,此时 App 在最近任务不可见,且用户不可感知。所以高 API 对隐式广播做了限制,禁止通过隐式 Broadcast 唤起静态 Receiver,避免 App 偷偷互相拉起的情况。

  • 隐式广播:广播 Intent 仅指定 ActionName 属性。
  • 显式广播:广播 Intent 需要指定 ComponentName 属性,包含 Receiver 所在 App 的包名、以及 Receiver 的全路径。

(1)App 内部发送的本地隐式 Broadcast 可以被其 App 内的任意 Receiver 接收。

因为本地广播仅在当前 App 内传播,所以任意 Receiver 都可以接收来自其同一个 App 内部发送的「本地」隐式广播:

1
2
// 通过隐式 Intent 发送本地 Broadcast,该 App 内所有对应 ActionName 的 Receiver 都可以收到:
localBroadcastManager.sendBroadcast(new Intent("ACTION_DEMO"));

(2)动态注册的 Receiver 只能接收隐式 Broadcast。

在 Manifest 中注册的四大组件,App 安装时系统就能明确获取到对应的信息,因此通常跨 App 调用组件时,都需要采用显式调用的方式,系统会根据显示调用的信息搜索已经静态注册的组件。但四大组件中只有 BroadcastReceiver 可以动态注册,而动态注册的 Receiver 是不会被记录在 Manifest 中的,如果向其发送显式广播,系统将无法从已注册的组件信息中搜索到对应的 Receiver,因此动态 Receiver 只能接收隐式 Broadcast。

(3)外部 App 发送的隐式 Broadcast 可以被动态 Receiver 接收。

因为 Google 在高 API 版本对隐式 Broadcast 唤起的限制主要时避免 App 偷偷互相拉起,因此对于已经启动的 App,其 Receiver 被唤起并不会影响用户体验。而动态 Receiver 一定是在 App 运行时注册的,因此动态 Receiver 可以接收来自其他 App 发送的隐式广播。

(4)外部 App 发送的隐式 Broadcast 无法被静态 Receiver 接收。

由于静态 Receiver 在 App 未启动时也响应对符合条件的 Broadcast,并且会静默启动 Application,因此静态 Receiver 被禁止接收来自外部 App 发送的全局隐式 Broadcast。向其他 App 的 Receiver 发送全局 Broadcast 时,Receiver 必须指定 exported="true",且发送方必须采用显式 Intent 的方式,指定 Receiver 所在 App 的包名和 Receiver 全路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DemoReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("DemoReceiver", "--- onReceive ---");
}
}

// 接收方 App 必须设置 exported=true,permission 可选。
<manifest package="priv.luis">
<receiver
android:name=".DemoReceiver"
android:exported="true"
/>
</manifest>

// 发送方必须指定 Receiver 所在 App 的包名以及 Receiver 的全路径:
Intent intent = new Intent("ACTION_DEMO");
intent.setComponent(new ComponentName("priv.luis", "priv.luis.DemoReceiver"));
content.sendBroadcast(intent);

查看日志即可发现,如果 Receiver 所在 App 原本未启动,则发送广播后会先启动 Application,然后触发 onReceive()

1
2
3
D/OemNetd: setPidForPackage: packageName=priv.luis, pid=25234, pid=10408
I/ActivityManager: Start proc 25234:priv.luis/u0a408 for broadcast {priv.luis/priv.luis.DemoReceiver} caller=priv.luis.demosender
D/DemoReceiver: --- onReceive ---