LocalBroadcastManager 的源码分析及使用方法梳理
文章目录
- 1. 注册方法:registerReceiver (锁mReceivers)
- 2. 注销广播:unregisterReceiver(锁mReceivers)
- 3. 发送广播:sendBroadcast(锁mReceivers)
背景
在 Android 系统中, BroadcastReceiver 的设计初衷就是从全局考虑的,可以方便应用程序和系统、应用程序之间、应用程序内的通信,所以对单个应用程序而言 BroadcastReceiver 是存在安全性问题的,相应问题及解决如下:
- 当应用程序发送某个广播时系统会将发送的Intent与系统中所有注册的 BroadcastReceiver 的 IntentFilter 进行匹配,若匹配成功则执行相应的 onReceive 函数。可以通过类似 sendBroadcast(Intent, String) 的接口在发送广播时指定接收者必须具备的 permission 。或通过 Intent.setPackage 设置广播仅对某个程序有效。
- 当应用程序注册了某个广播时,即便设置了 IntentFilter 还是会接收到来自其他应用程序的广播进行匹配判断。对于动态注册的广播可以通过类似 registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler)的接口指定发送者必须具备的 permission ,对于静态注册的广播可以通过 android:exported="false" 属性表示接收者对外部应用程序不可用,即不接受来自外部的广播。
最开始在 Android v4 兼容包提供了 android.support.v4.content.LocalBroadcastManager 工具类,
现在需要我们手动引入,添加依赖如下:
arduino
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
LocalBroadcastManager 的优点
LocalBroadcastManager 可以帮助大家在自己的进程内进行局部广播发送与注册,使用它比直接通过sendBroadcast(Intent) 发送系统全局广播有以下几点好处:
- 因广播数据在本应用范围内传播,我们不用担心隐私数据泄露的问题;
- 不用担心别的应用伪造广播,造成安全隐患;
- 相比在系统内发送全局广播,它更高效,因为本地广播无需通过 IPC 和其他进程交互。
LocalBroadcastManager 源码分析
源码不多,270 行左右。我们选取一两个角度分析一二。
分析要点: 锁的运用 + 数据结构
单例模式
类的延迟加载的单例模式保证类对象的唯一性。
less
@NonNull
public static LocalBroadcastManager getInstance(@NonNull Context context) {
synchronized(mLock) {
if (mInstance == null) {
mInstance = new LocalBroadcastManager(context.getApplicationContext());
}
return mInstance;
}
}
- 这里的 mLock 对象是一个空 Object 对象,只是单纯的在这里用来锁一下。
- 同进程所有线程共享一个 LocalBroadcastManager 实例。而 LocalBroadcastManager 初始化时持有 ApplicationContext,显然其生命周期和整个进程相同。
注意:其他方法基本都涉及到对 mReceivers 的读写改查,因此需要使用同一把锁,形成互斥,但是获取单列和这些方法并不存在数据的交集,因此不需要互斥,所以使用不同的锁。此处和锁定类的效果是一样的。
构造方法
java
private LocalBroadcastManager(Context context) {
mAppContext = context;
// 构造Handler,在主线程回调
mHandler = new Handler(context.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_EXEC_PENDING_BROADCASTS:
// 执行队列中的广播
executePendingBroadcasts();
break;
default:
super.handleMessage(msg);
}
}
};
}
在 LocalBroadcastManager 构造函数中创建了一个 Handler。
可见 LocalBroadcastManager 的本质上是通过 Handler 机制发送和接收消息的。
在创建 Handler的时候,用了 context.getMainLooper() , 说明这个 Handler 是在 Android 主线程中(UI 线程)创建的,广播接收器的接收消息的时候会在 Android 主线程,所以我们决不能在广播接收器里面做耗时操作,以免阻塞 UI 从而导致出现 ANR。
重要方法
1. 注册方法:registerReceiver (锁mReceivers)
注册一个匹配指定 IntentFilter 时触发的 BroadcastReceiver。
less
public void registerReceiver(@NonNull BroadcastReceiver receiver,
@NonNull IntentFilter filter) {
// 先获取同步锁
synchronized (mReceivers) {
// 用传入的变量构造ReceiverRecord
ReceiverRecord entry = new ReceiverRecord(filter, receiver);
// 从mReceivers查找该receiver是否已经存在记录
ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
// 记录为空创建新的filters
if (filters == null) {
filters = new ArrayList<>(1);
mReceivers.put(receiver, filters);
}
// key 为 receiver,value 为 ReceiverRecord
filters.add(entry);
// 按照Action记录对应的ReceiverRecord
for (int i=0; i<filter.countActions(); i++) {
// 从filter逐个获取Action
String action = filter.getAction(i);
ArrayList<ReceiverRecord> entries = mActions.get(action);
// 这个Action没有记录过则创建新记录
if (entries == null) {
entries = new ArrayList<ReceiverRecord>(1);
mActions.put(action, entries);
}
// key为Action,value为ReceiverRecord
entries.add(entry);
}
}
}
2. 注销广播:unregisterReceiver(锁mReceivers)
ini
public void unregisterReceiver(@NonNull BroadcastReceiver receiver) {
synchronized (mReceivers) {
final ArrayList<ReceiverRecord> filters = mReceivers.remove(receiver);
// 如果该 Receiver没 有注册过,结束方法的执行
if (filters == null) {
return;
}
for (int i=filters.size()-1; i>=0; i--) {
final ReceiverRecord filter = filters.get(i);
filter.dead = true;
for (int j=0; j<filter.filter.countActions(); j++) {
final String action = filter.filter.getAction(j);
final ArrayList<ReceiverRecord> receivers = mActions.get(action);
if (receivers != null) {
for (int k=receivers.size()-1; k>=0; k--) {
final ReceiverRecord rec = receivers.get(k);
if (rec.receiver == receiver) {
// 标记该接收器已经失效
rec.dead = true;
// 从列表移除接收器
receivers.remove(k);
}
}
// actions没有接收器,就直接把整个actions记录移除
if (receivers.size() <= 0) {
mActions.remove(action);
}
}
}
}
}
}
3. 发送广播:sendBroadcast(锁mReceivers)
ini
public boolean sendBroadcast(@NonNull Intent intent) {
synchronized (mReceivers) {
// 分析Intent
final String action = intent.getAction();
final String type = intent.resolveTypeIfNeeded(
mAppContext.getContentResolver());
final Uri data = intent.getData();
final String scheme = intent.getScheme();
final Set<String> categories = intent.getCategories();
// 根据发送的action找出已经注册的接收者记录
ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
if (entries != null) {
ArrayList<ReceiverRecord> receivers = null;
for (int i=0; i<entries.size(); i++) {
ReceiverRecord receiver = entries.get(i);
// 同一个ReceiverRecord多次注册相同条件的IntentFilter不会重复通知
if (receiver.broadcasting) {
continue;
}
// 检查此ReceiverRecord是否满足接收事件的条件
int match = receiver.filter.match(action, type, scheme, data,
categories, "LocalBroadcastManager");
if (match >= 0) {
if (receivers == null) {
receivers = new ArrayList<ReceiverRecord>();
}
// 加入到待通知列表
receivers.add(receiver);
receiver.broadcasting = true;
}
}
if (receivers != null) {
for (int i=0; i<receivers.size(); i++) {
receivers.get(i).broadcasting = false;
}
mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
// 向消息队列放入消息,表示有广播可以分发
if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
}
return true;
}
}
}
return false;
}
通过 Intent 发送广播。先获取线程锁 mReceivers,所以可以多线程操作。广播正在主线程分发的时候也会获取该锁,所以不存在线程安全问题。
通过对这个方法的分析我们可以得知:
LocalBroadcastManager 直接翻译过来为本地广播管理器,本质上就是通过普通的回调实现的。
java
private void executePendingBroadcasts() {
// 这里是死循环,直到广播处理完成后退出
while (true) {
final BroadcastRecord[] brs;
// 上线程锁
synchronized (mReceivers) {
// 获取待处理广播的数量
final int N = mPendingBroadcasts.size();
if (N <= 0) {
return;
}
// 创建与待处理广播数量相同的数组
brs = new BroadcastRecord[N];
// 把所有待处理的广播赋值到新创建的数组中
mPendingBroadcasts.toArray(brs);
// 清除待处理广播的列表
mPendingBroadcasts.clear();
}
// 遍历刚创建数组的广播事件
for (int i=0; i<brs.length; i++) {
// 逐个取出广播
final BroadcastRecord br = brs[i];
// 获取广播接收者的数量
final int nbr = br.receivers.size();
for (int j=0; j<nbr; j++) {
final ReceiverRecord rec = br.receivers.get(j);
// 把广播记录派发给所有事件接收者
if (!rec.dead) {
rec.receiver.onReceive(mAppContext, br.intent);
}
}
}
}
}
整个派发过程都在主线程上进行,如果接收器处理逻辑耗时,会阻塞主线程。
其他学习的知识点:
- 消息的处理
scss
if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
}
- HashMap 和 ArrayList 数据的结构的选择与使用
LocalBroadcastManager 使用方法:
和正常注册广播的使用方法类似。
在引入工具类后,创建广播接收器:
typescript
private BroadcastReceiver onNotice= new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// intent can contain anydata
Log.d("test","Broadcast received");
}
};
注册接收者:
scss
protected void onResume() {
super.onResume();
IntentFilter myFilter= new IntentFilter(MyIntentService.ACTION);
LocalBroadcastManager.getInstance(this).registerReceiver(onNotice, myFilter);
}
取消注册接收者:
scss
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(onNotice);
}
注意事项
- LocalBroadcastManager 注册广播只能通过代码注册的方式。传统的广播可以通过代码和 xml 两种方式注册。
- LocalBroadcastManager注册广播后,一定要记得取消监听,这一步可以解决内存泄漏的问题。
- LocalBroadcastManager注册的广播,在发送广播的时候务必使用LocalBroadcastManager.sendBroadcast(intent),否则会接收不到广播。
传统的发送广播的方法为:context.sendBroadcast( intent );
LocalBroadcastManager 弃用原因
LocalBroadcastManager
是应用级事件总线,在您的应用中使用了层违规行为;任何组件都可以监听来自其他任何组件的事件。- 它继承了系统
BroadcastManager
不必要的用例限制;开发者必须使用Intent
,即使对象只存在且始终存在于一个进程中。由于同一原因,它未遵循功能级BroadcastManager
。
这些问题同时出现,会对开发者造成困扰。
替换
- 您可以将
LocalBroadcastManager
替换为可观察模式的其他实现。合适的选项可能是LiveData
或响应式流,具体取决于您的用例。
小结 :
LocalBroadcastManager 的使用在 项目中如果已经使用,可以深入了解一下源码,学习一下设计思路并运用到项目中。
LocalBroadcastManager 的使用很简单,不过,随着 LiveData的出现,替换使用已经是必然趋势,后期会再介绍一下 liveData 在项目中的运用。