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 在项目中的运用。

相关推荐
没有了遇见42 分钟前
Android 之Google Play bundletool 校验 AAB包
android·google
yuanhello1 小时前
【Android】Android的键值对存储方案对比
android·java·android studio
Ditglu.1 小时前
CentOS7 MySQL5.7 主从复制最终版搭建流程(避坑完整版)
android·adb
恋猫de小郭1 小时前
Android Studio Otter 2 Feature 发布,最值得更新的 Android Studio
android·前端·flutter
走在路上的菜鸟1 小时前
Android学Dart学习笔记第十二节 函数
android·笔记·学习·flutter
没有了遇见1 小时前
Android + Google Play:老项目适配实战指南
android·google
怀君2 小时前
Uniapp——开发Android插件教程
android·uni-app
Lei活在当下2 小时前
【Perfetto从入门到精通】1. 初识 Perfetto
android·性能优化·架构
用户41659673693552 小时前
深度解析 Android 权限机制:从清单注册到 Android 14 适配实战
android
Nerve4 小时前
GalleryPicker:一个基于 Android 官方 Photo Picker API 封装的现代图片/视频选择库
android