介绍
BroadcastReceiver 即广播组件,是 Android 的四大组件之一。用于监听和接收广播消息,并做出响应。有以下一些应用:
- 不同组件之间的通信(应用内或不同应用之间)。
- 多线程之间通信。
- 与系统在特定情况下(例如,电话呼入时、网络可用时)的通信。
原理
Android 中的广播机制使用了观察者设计模式:基于消息的发布、订阅事件模型。因此,广播的发送者和接收者解耦,使得系统方便集成,更容易扩展。
模型中有三个角色:
- 消息订阅者(广播接收者)
- 消息发布者(广播发送者)
- 消息中心(
ActivityManagerService
)
整个模型过程如下:
- 广播接收者通过 Binder 机制在 AMS 中注册订阅广播。
- 广播发送者通过 Binder 机制向 AMS 发送广播。
- AMS 根据广播发送者要求(IntentFilter、Permission),在已注册列表中寻找适合的接收者。
- AMS 将广播发送到合适的广播接收者相应的消息循环队列中。
-
广播接收者通过消息循环拿到广播,并回调
onReceive()
方法。注:广播发送者和接收者的执行是异步的,发送者不会关心有无接收者接收,也不确定接收者何时才能接收到。
使用
步骤1:自定义广播接收器
继承 BroadcastReceiver 基类,并复写抽象方法 onReceive()
。默认情况下,广播接收器运行在主线程,因此 onReceive()
方法不能执行耗时操作,否则阻塞主线程导致 ANR 问题。
一个 BroadcastReceiver 对象只有在被调用 onReceive()
时才有效,当从该方法返回后 BroadcastReceiver 对象就结束了生命周期。在 onReceive()
方法里,不建议使用线程来执行耗时操作,因为当得到其他异步操作所返回的结果时,BroadcastReceiver 可能已经结束生命周期了。如果确实需要的话,可以用调用 goAsync()
方法,然后再新开一个线程去执行,但是仍不建议执行超过 10 秒的任务。对于耗时的操作,最好使用 startService()
来完成。
// 继承 BroadcastReceiver 基类
public class TestBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "TestBroadcastReceiver";
// 复写 onReceive() 方法,接收到广播后,自动调用该方法
@Override
public void onReceive(Context context, Intent intent) {
final PendingResult pendingResult = goAsync();
Task asyncTask = new Task(pendingResult, intent);
asyncTask.execute();
}
private static class Task extends AsyncTask<String, Integer, String> {
private final PendingResult pendingResult;
private final Intent intent;
private Task(PendingResult pendingResult, Intent intent) {
this.pendingResult = pendingResult;
this.intent = intent;
}
@Override
protected String doInBackground(String... strings) {
StringBuilder sb = new StringBuilder();
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
String log = sb.toString();
Log.d(TAG, log);
return log;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
// Must call finish() so the BroadcastReceiver can be recycled.
pendingResult.finish();
}
}
}
步骤2:注册广播接收器
广播接收器的注册方式分为两种:静态注册、动态注册。
静态注册
静态注册会时刻监听广播,在系统常驻,不受到任何组件生命周期的影响,但是也有耗电、占内存等缺点。
广播接收器的静态注册由 PMS 负责,在应用安装时,PMS 负责按照一定的目录顺序,扫描手机中所有安装的应用,并将应用清单文件中有关注册广播的信息解析存储起来。
所以,静态注册方法是在清单文件 AndroidManifest 里通过 <receiver>
元素标签声明:
<receiver android:name=".TestBroadcastReceiver"
android:permission="android.permission.SEND_SMS">
<intent-filter>
<action android:name="android.intent.action.AIRPLANE_MODE"/>
</intent-filter>
</receiver>
注:由于系统是在应用安装时根据清单文件中的声明注册接收器,所以静态注册的广播接收器,即使应用不在运行,也可以接收到广播。
但是 Android 3.1(API 12)开始系统在 Intent 与广播相关的 Flag 中增加了两个参数,用来标识是否包含停止运行的包。系统不管什么广播类型,都默认增加值为
FLAG_EXCLUDE_STOPPED_PACKAGES
的 Flag,表示不包含停止运行的包。系统广播是系统直接发出,无法更改此 Flag 值,导致即使是静态注册的广播接收器,假如其所在的进程已退出,同样无法收到广播。
而自定义广播,可以通过修改此 Flag 为
FLAG_INCLUDE_STOPPED_PACKAGES
,使得静态注册的广播接收器,在应用停止运行时也可以接收到广播,并会启动应用进程。
动态注册
动态注册是通过 AMS 负责的,注册方式比较灵活,可以跟随组件的生命周期变化,可以在特定时间段内监听广播。使用方法是调用 registerReceiver()
方法注册广播接收器的监听,调用 unregisterReceiver()
方法注销:
TestBroadcastReceiver testBroadcastReceiver = null;
@Override
protected void onResume() {
super.onResume();
// 1. 实例化 BroadcastReceiver 子类
testBroadcastReceiver = new TestBroadcastReceiver();
// 2. 实例化 IntentFilter,设置接收广播的类型
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
// 3. 动态注册:调用 Context的registerReceiver() 方法
registerReceiver(testBroadcastReceiver, intentFilter);
}
@Override
protected void onPause() {
super.onPause();
// 注销在 onResume() 中注册的广播
unregisterReceiver(testBroadcastReceiver);
}
注:因为动态广播注册后不注销会导致内存泄漏,所以最好在 Activity 生命周期中几个成对出现的方法里注册和注销。推荐在 Activity 中的
onResume()
中注册,onPause
中注销,因为这样可以在 Activity 不在前台显示时,停止监听,减少运行的开销。
注意:如果应用针对 Android 的最新版本,则需注意系统广播行为的多处修改:
Android 7.0(API 24)或更高版本,系统不再发送
ACTION_NEW_PICTURE
和ACTION_NEW_VIDEO
广播,并且只有动态注册的广播接收器能接收到CONNECTIVITY_ACTION
广播。Android 8.0(API 26)或更高版本,系统对静态注册的接收器施加了额外限制,无法为大多数隐式广播声明接收器,但可以使用动态注册接收器。
Android 9(API 28)或更高版本,
NETWORK_STATE_CHANGED_ACTION
广播不会收到有关用户位置或个人身份数据的信息。自 Wi-Fi 的系统广播不包含 SSID,BSSID,连接信息或扫描结果。
步骤3:广播发送者向 AMS 发送广播
广播发送者通过 sendBroadcast()
方法向 AMS 发送包装了广播的 Intent 对象。广播一般主要分为以下五类:
-
Normal Broadcast
普通广播 -
System Broadcast
系统广播 -
Ordered Broadcast
有序广播 Sticky Broadcast
粘性广播-
Local Broadcast
本地广播
普通广播(Normal Broadcast)
开发者自定义 Intent 的全局广播。若发送广播需要相应的权限,则广播接收器也需要相应的权限。发送广播方式如下:
Intent intent = new Intent();
// 对应 BroadcastReceiver 中 IntentFilter 定义的 Action
intent.setAction(TEST_BROADCAST_ACTION);
sendBroadcast(intent);
系统广播(System Broadcast)
Android 系统中定义了很多内置的广播,涉及到手机的一些基本操作(例如,开机、网络状态变化、拍照等),都会发送相应的系统广播。
每个广播都有特定的 IntentFilter,使用系统广播时,只需要在注册广播接收器处声明相关的 IntentFilter 条件即可,并不需要手动发送广播,系统会在执行相关操作时自动进行系统广播。
有序广播(Ordered Broadcast)
广播接收器按照一定顺序规则接收广播,先接收的广播接收器可以对广播进行修改或截断,再被其他的广播接收器接收,或终止广播。顺序规则如下:
- 按照 priority 属性值从大到小排序。
- priority 属性值相同时,动态注册的广播接收器优先于静态注册。
- 静态注册相同优先级的接收器,先扫描的优先级高。
-
动态注册相同优先级的接收器,先注册的优先级高。
注:优先级对无序广播同样有效,对于无序广播:动态注册优先级高于静态注册(无视优先级)。同一种注册方式 priority 属性大的优先级高。同一 priority 属性值下,静态注册先扫描的优先级高,动态注册先注册的优先级高。
发送有序广播与普通广播类似,但使用的是 sendOrderedBroadcast()
方法发送广播,在 onReceive()
中通过 setResultExtras()
给下一优先级的接收器传递数据,通过 getResultExtras()
取出上一优先级接收器传递来的数据,通过 abortBroadcast()
截断广播的发送。
通过 sendOrderedBroadcast()
方法可以指定最终广播接收器 ResultReceiver。如果比它优先级高的接收器不终止广播,则最终接受器会接收两次广播:第一次,按照标准的优先级接收,第二次,接收最终的广播。如果比它优先级高的接收器终止广播,那么它只是接收一次最终的广播。
粘性广播(Sticky Broadcast)
在Android 5.0(API 21)或更高版已失效。
本地广播(Local Broadcast)
由于 Android 中的广播可以跨应用直接通信,所以可能造成以下两个问题:
- 其他应用发出与当前应用 IntentFilter 相匹配的广播,导致当前应用不断接收和处理一些无用甚至是恶意的广播。
- 其他应用注册与当前应用一致的 IntentFilter 用于接收广播,导致可以截取当前应用广播的具体信息,存在安全性问题。
使用本地广播可以有效解决上面的问题,提高安全性和效率。使用本地广播的方式有两种:
将全局广播设置为本地广播
- 注册广播接收器时将 exported 属性设置为 false,只接收使当前应用发出的广播。
- 在广播发送和接收时,增设相应的自定义权限 permission,用于权限验证。
- 在 Android 4.0 及更高版本,发送广播时通过 Intent 的
setPackage()
方法指定包名,此广播只会发送到指定包中匹配的广播接收器。
使用 LocalBroadcastManager 类
使用方式与全局广播类似,区别是要使用 LocalBroadcastManager 的单例来调用 registerReceiver()
和 unregisterReceiver
来注册和注销广播接收器,调用 sendBroadcast()
来发送广播。
// 注册应用内广播接收器
testBroadcastReceiver = new TestBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
LocalBroadcastManager.getInstance(this).registerReceiver(testBroadcastReceiver, intentFilter);
// 注销应用内广播接收器
LocalBroadcastManager.getInstance(this).unregisterReceiver(testBroadcastReceiver);
// 发送应用内广播
Intent intent = new Intent();
intent.setAction(TEST_BROADCAST_ACTION);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
其他
对于不同注册方式的广播接收器,在回调 onReceive(Context context, Intent intent)
中的 context 返回值是不一样的:
- 静态注册:context 返回的是 ReceiverRestrictedContext。
- 动态注册的全局广播:context 返回的是 Activity Context。
- 动态注册的本地广播:context 返回的是 Activity Context。
- 动态注册的本地广播(LocalBroadcastManager):context 返回的是 Application Context。