LocalBroadcastManager 的源码分析及使用方法梳理

LocalBroadcastManager 的源码分析及使用方法梳理

文章目录

背景

在 Android 系统中, BroadcastReceiver 的设计初衷就是从全局考虑的,可以方便应用程序和系统、应用程序之间、应用程序内的通信,所以对单个应用程序而言 BroadcastReceiver 是存在安全性问题的,相应问题及解决如下:

  1. 当应用程序发送某个广播时系统会将发送的Intent与系统中所有注册的 BroadcastReceiver 的 IntentFilter 进行匹配,若匹配成功则执行相应的 onReceive 函数。可以通过类似 sendBroadcast(Intent, String) 的接口在发送广播时指定接收者必须具备的 permission 。或通过 Intent.setPackage 设置广播仅对某个程序有效。
  2. 当应用程序注册了某个广播时,即便设置了 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);
                }
            }
        }
    }
}

整个派发过程都在主线程上进行,如果接收器处理逻辑耗时,会阻塞主线程。

其他学习的知识点:

  1. 消息的处理
scss 复制代码
if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
                        mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
  }
  1. 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 在项目中的运用。

相关推荐
呆呆小雅37 分钟前
C# 结构体
android·java·c#
ᥬ 小月亮3 小时前
Layui表格的分页下拉框新增“全部”选项
android·javascript·layui
sunly_12 小时前
Flutter:启动屏逻辑处理02:启动页
android·javascript·flutter
Sgq丶13 小时前
Android Studio 配置 proto
android·ide·android studio
_小马快跑_17 小时前
ConstraintLayout 中的ImageFilterView探索:处理图片圆角、亮度、饱和度、图片重叠等
android
IT-sec17 小时前
jquery-picture-cut 任意文件上传(CVE-2018-9208)
android·前端·javascript·安全·web安全·网络安全·jquery
xiaoduyyy18 小时前
【Android】RecyclerView回收复用机制
android
林北芒大果18 小时前
【Flutter】搭建Flutter开发环境,安卓开发
android·flutter
m0_7482302120 小时前
MySQL 数据库连接池爆满问题排查与解决
android·数据库·mysql
SunshineBrother20 小时前
Flutter求职、面试20+面试官总结:Dart篇
android·前端·flutter