广播概览
BroadcastReceiver(使用级)
BroadcastReceiver内部机制
一、概述
- Android 应用与 Android 系统和其他 Android 应用之间可以相互收发广播消。
- 应用可以注册接收特定的广播。
广播发出后,系统会自动将广播传送给同意接收这种广播的应用。广播消息本身会被封装在一个 Intent 对象中,该对象的操作字符串会标识所发生的事件(例如android.intent.action.AIRPLANE_MODE)。该 Intent 可能还包含绑定到其 extra 字段中的附加信息。例如,飞行模式 intent 包含布尔值 extra 来指示是否已开启飞行模。有关系统广播操作的完整列表,请参阅 Android SDK 中的 BROADCAST_ACTIONS.TXT 文件。 - 系统软件包管理器会在应用安装时注册接收器。
然后,该接收器会成为应用的一个独立入口点,这意味着如果应用当前未运行,系统可以启动应用并发送广播。
1.1 使用场景
- 接收系统发送出的重要的广播(网络变化,开机,充电)
- app之间相互通信,相互拉活的手段
- app内部组件间通信的手段
1.2 分类
1.2.1 广播类型
- 系统广播、非系统广播
- 全局广播、本地广播
- 无序广播、有序广播、粘性广播(废弃)
1.2.2 注册广播类型
静态注册
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>
动态注册
IntentFilter filter = new IntentFilter();
filter.addAction(OrderBroadcast.ACTION_ORDER);
MyBroadcastReceiver broadcast = new MyBroadcastReceiver();
registerReceiver(broadcast, filter);
LocalBroadcastManager.getInstance(this).registerReceiver(reveive, filter);
静态、动态注册区别
动态注册的广播
|---优势:可以自由的控制注册和取消,有很大的灵活性。
|---劣势:只有在注册之后才能起作用,在Activity的onDestroy后如果未被注销,会报异常
所以动态注册的广播存活时间最长也就约等于Activity的生命周期长度
静态注册的广播
|---优势:不受程序是否启动的约束,随时使用
|---劣势:优势同样也是劣势,无法取消,什么时候都能用
1.2.3 发送广播方式
显式发送
显式,即直接指定需要打开的组件对应的类
Intent intent = new Intent(this, OrderBroadcast.class);
intent.putExtra(OrderBroadcast.EXTRA_INDEX, 1000);
sendBroadcast(intent);
//可用在其它App向指定App发送广播
Intent intent = new Intent();
ComponentName compName = new ComponentName("com.varmin.vdemo", "com.varmin.vdemo.old.service.OrderBroadcast");
intent.setComponent(compName);
intent.putExtra(OrderBroadcast.EXTRA_INDEX, 1000);
sendBroadcast(intent);
隐式发送
隐式,不明确指定启动哪个组件,而是设置Action、Data、Category,让系统来根据来筛选出合适的组件。
Intent intentBr = new Intent();
intentBr.setAction(OrderBroadcast.ACTION_MANIFEST);
intentBr.putExtra(OrderBroadcast.EXTRA_INDEX, 1000);
sendBroadcast(intentBr);
发送广播:
- sendBroadcast(Intent)
该方法会按随机的顺序向所有接收器发送广播,这称为常规广播。
这种方法效率更高,但也意味着接收器无法从其他接收器读取结果,无法传递从广播中收到的数据,也无法中止广播。
- sendOrderedBroadcast(Intent, String)
该方法一次向一个接收器发送广播。
当接收器逐个顺序执行时,接收器可以向下传递结果,也可以完全中止广播,使其不再传递给其他接收器。
接收器的运行顺序可以通过匹配的 intent-filter 的 android:priority 属性来控制;具有相同优先级的接收器将按随机顺序运行。
- LocalBroadcastManager.sendBroadcast(Intent)
该方法会将广播发送给与发送器位于同一应用中的接收器。如果您不需要跨应用发送广播,请使用本地广播。
这种实现方法的效率更高(无需进行进程间通信),而且无需担心其他应用在收发您的广播时带来的任何安全问题。
二、使用
以有序广播为例
//注册有序广播(正常情况下应该是不同的接收器,此处为试验使用了同一个)
for (int i = 0; i < 10; i++) {
IntentFilter filter = new IntentFilter();
filter.addAction(OrderBroadcast.ACTION_ORDER);
filter.setPriority(i);
OrderBroadcast broadcast = new OrderBroadcast();
registerReceiver(broadcast, filter);
orderBrList.add(broadcast);
}
//发送广播
Intent intent = new Intent();
intent.setAction(OrderBroadcast.ACTION_ORDER);
intent.putExtra(OrderBroadcast.EXTRA_INDEX, 100);
sendOrderedBroadcast(intent, null);
//接收有序广播
public class OrderBroadcast extends BroadcastReceiver {
...
@Override
public void onReceive(Context context, Intent intent) {
//有序广播
if (TextUtils.equals(intent.getAction(), ACTION_ORDER)) {
//接收上一优先级数据,使用getResultExtras方法接收
index = getResultExtras(true).getInt(EXTRA_INDEX, -1);
//无数据说明是有序广播的第一个广播,正常接收
if(index == -1) index = intent.getExtras().getInt(EXTRA_INDEX, -1);
//abortBroadcast();中断广播
Bundle bundle = new Bundle();
bundle.putInt(EXTRA_INDEX, index+1);
//向下一优先级传递数据
setResultExtras(bundle);
}else{//普通广播
index = intent.getExtras().getInt(EXTRA_INDEX, -1000);
Toast.makeText(context, "remote:"+index, Toast.LENGTH_SHORT).show();
}
}
}
//注销广播
@Override
protected void onDestroy() {
super.onDestroy();
for (OrderBroadcast orderBroadcast : orderBrList) {
unregisterReceiver(orderBroadcast);
}
}
关于有序广播
- 第一个广播接收器使用intent.getExtras()接收数据
后面的广播接收器使用getResultExtras(true)接收数据 - 是否是有序广播:isOrderedBroadcast()
中断广播:abortBroadcast()
向下级传递数据:setResultExtras(bundle),如果不是有序广播,调用会报错
IntentFilter中setPriority设置优先级- 优先级对无序广播也生效
- 无需广播,先注册先生效
三、onRevice
对进程状态的影响
BroadcastReceiver 的状态(无论它是否在运行)会影响其所在进程的状态,而其所在进程的状态又会影响它被系统终结的可能性。例如,当进程执行接收器(即当前在运行其 onReceive() 方法中的代码)时,它被认为是前台进程。除非遇到极大的内存压力,否则系统会保持该进程运行。
但是,一旦从 onReceive() 返回代码,BroadcastReceiver 就不再活跃,系统会将其进程视为低优先级进程,并可能会将其终止,以便将资源提供给其他更重要的进程使用。
因此,不应从广播接收器启动长时间运行的后台线程。onReceive() 完成后,系统可以随时终止进程来回收内存,在此过程中,也会终止进程中运行的派生线程。
如果您希望在后台线程中多花一点时间来处理广播,要避免这种情况,应该调用 goAsync()或者使用 JobScheduler 从接收器调度 JobService,这样系统就会知道该进程将继续活跃地工作。
以下代码段展示使用 goAsync() 来标记它在 onReceive() 完成后需要更多时间才能完成。
如果希望在 onReceive() 中完成的工作很长,足以导致界面线程丢帧 (>16ms),则这种做法非常有用,这使它尤其适用于后台线程。
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
PendingResult pendingResult = goAsync();
Task asyncTask = new Task(pendingResult, intent);
asyncTask.execute();
}
private class Task extends AsyncTask<String, Integer, String> {
...
}
}
生命周期
- 只有一个onReceiver, 相当的短暂,方法完成后广播接受者 就销毁
- 不要做耗时操作,否则出现ANR
- 也不要开启子线程进行耗时操作,因为生命周期结束后 整个进程就成低优先级进程了,容易被系统杀掉
- 耗时较长的工作最好放在后台服务、作业调度中执行
四、安全注意事项和最佳做法
- 优先使用则可以使用LocalBroadcastManager
如果不需要向应用以外的组件发送广播,使用LocalBroadcastManager 效率更高(无需进行进程间通信)。本地广播可在应用中作为通用的发布/订阅事件总线,而不会产生任何系统级广播开销。 - 优先使用上下文注册而不是清单声明
如果有许多应用在其清单中注册接收相同的广播,可能会导致系统启动大量应用,从而对设备性能和用户体验造成严重影响。有时,Android 系统本身会强制使用上下文注册的接收器。例如,CONNECTIVITY_ACTION 广播只会传送给上下文注册的接收器。 - 通过权限限制广播
- 当注册接收器时,任何应用都可以向你应用的接收器发送潜在的恶意广播。
可以通过以下三种方式限制你的应用可以接收的广播:- 在注册广播接收器时指定权限
- 对于清单声明的接收器,可以在清单中将 android:exported 属性设置为“false”,这样接收器就不会接收来自应用外部的广播。
- 使用 LocalBroadcastManager 注册广播
- 勿使用隐式 intent 广播敏感信息
可以通过以下三种方式控制哪些应用可以接收广播:- 可以在发送广播时指定权限
- 可以在发送广播时使用 setPackage(String) 指定软件包
- 可以使用 LocalBroadcastManager 发送本地广播
- onRecive返回后即有可能被系统回收,如需做耗时操作,尽量使用goAsync()开启后台任务或JobScheduler
五、LocalBroadcastManager
- 发送的广播将只在自身App内传播,因此不必担心隐私数据泄漏
- 其它App无法对你的App发送该广播,因为你的App根本就不可能接收到非自身应用发送的该广播,因此你不必担心有安全漏洞可以利用
- 比系统的全局广播更加高效
- 它内部是通过Handler实现的,它的sendBroadcast方法含义并非和我们平时所用的一样,是通过handler发送一个Message实现的
- 所以相比于系统广播通过Binder实现肯定是更高效了
- 同时使用Handler来实现,别的应用无法向我们的应用发送该广播,而我们应用内发送的广播也不会离开我们的应用。
六、常见问题
Android 8.0部分静态广播失效
受 Android 8.0(API 级别 26)后台执行限制的影响,以 API 级别 26 或更高级别为目标的应用无法再在其清单中注册用于隐式广播的广播接收器。
- 静态注册,隐式广播,失效
- 静态注册,显式广播,本地或其它App,有效
不过,有几种广播目前不受这些限制的约束。无论应用以哪个 API 级别为目标,都可以继续为以下广播注册监听器: 隐式广播例外情况
在广播中更新UI
- 广播是在主线程中只能,所以能更新UI
- 但是,广播使用Binder进程间通信机制实现,比较耗费性能,有可能会影响更新准时率
- 所以,能更新UI,但尽量少用