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

Posted 小羊子说

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 工具类,

现在需要我们手动引入,添加依赖如下:

 implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'

LocalBroadcastManager 的优点

LocalBroadcastManager 可以帮助大家在自己的进程内进行局部广播发送与注册,使用它比直接通过sendBroadcast(Intent) 发送系统全局广播有以下几点好处:

  • 因广播数据在本应用范围内传播,我们不用担心隐私数据泄露的问题;

  • 不用担心别的应用伪造广播,造成安全隐患;

  • 相比在系统内发送全局广播,它更高效,因为本地广播无需通过 IPC 和其他进程交互。

LocalBroadcastManager 源码分析

源码不多,270 行左右。我们选取一两个角度分析一二。

分析要点: 锁的运用 + 数据结构

单例模式

类的延迟加载的单例模式保证类对象的唯一性。

@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 的读写改查,因此需要使用同一把锁,形成互斥,但是获取单列和这些方法并不存在数据的交集,因此不需要互斥,所以使用不同的锁。此处和锁定类的效果是一样的。

构造方法

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

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)

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)

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 直接翻译过来为本地广播管理器,本质上就是通过普通的回调实现的。

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. 消息的处理
 if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) 
                        mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
  
  1. HashMap 和 ArrayList 数据的结构的选择与使用

LocalBroadcastManager 使用方法:

和正常注册广播的使用方法类似。

在引入工具类后,创建广播接收器:

private BroadcastReceiver onNotice= new BroadcastReceiver() 
    @Override
    public void onReceive(Context context, Intent intent) 
        // intent can contain anydata
        Log.d("test","Broadcast received")
;

注册接收者:

protected void onResume() 
        super.onResume();
        IntentFilter myFilter= new IntentFilter(MyIntentService.ACTION);
        LocalBroadcastManager.getInstance(this).registerReceiver(onNotice, myFilter);
    

取消注册接收者:

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

参考文档:

官方文档:Localbroadcastmanager
通过线程提升性能

以上是关于LocalBroadcastManager 的源码分析及使用方法梳理的主要内容,如果未能解决你的问题,请参考以下文章

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

Android 之LocalBroadcastManager原理简析

Android 之LocalBroadcastManager原理简析

学习笔记 Android LocalBroadcastManager的使用

学习笔记 Android LocalBroadcastManager的使用

Android 应用内广播 LocalBroadcastManager