Android之LocalBroadcastManager源码解析

转载请标明出处:【顾林海的博客】

个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持!
在这里插入图片描述


前言

广播想必大家都不陌生,日常开发中同一个APP中的多个进程之间需要进行传输信息,或是不同APP之间的消息通信,都可以使用广播来实现,学习广播时,我们知道广播有普通广播和系统广播,通过自定义广播接受者BroadcastReceiver,并复写onReceive方法,内部通过Binder机制向AMS(Activity Manager Service)进行注册,广播发送者通过Binder机制向AMS发送广播,AMS内部会寻找符合相应条件的BroadcastReceiver,将广播发送到BroadcastReceiver相应的消息循环队列中,之后从消息循环队列拿到这个广播,再通过回调BroadcastReceiver的onReceive方法,整个广播的使用还是很简单,如果在不考虑安全、性能的情况下,完全可以使用以上方式就可以实现,但在真实环境下还是推荐使用本地广播进行应用内的信息传输,考虑安全、性能方面原因,这也是本篇之所以介绍本地广播的原因,在正式开始前,


为什么本地广播安全和性能更高?

使用LocalBroadcastManager发送的广播只会在当前APP内进行传播,数据传递也只会在当前APP内进行传输,因此数据并不会泄露出去,同时其他APP发送的广播,自身的APP并不会接受到,因此我们并不会担心出现安全漏洞,同时LocalBroadcastManager内部是通过Handler来实现的,性能上要比全局广播更高。


源码解析

LocalBroadcastManager的核心方法就是注册和解注册,因此分析源码的时候主要介绍这两个方法实现的逻辑,使用LocalBroadcastManager前我们需要获取的它的实例,可以通过静态方法getInstance方法获取。

    public static LocalBroadcastManager getInstance(Context context) {
        synchronized (mLock) {
            if (mInstance == null) {
                mInstance = new LocalBroadcastManager(context.getApplicationContext());
            }
            return mInstance;
        }
    }

这段代码想必大家一点都不陌生,典型的单例模式,注意这里传入的Context最终在实例化LocalBroadcastManager时传入的是Application的上下文,为什么这样做?如果我们传入的是Activity的上下文,由于LocalBroadcastManager
是个单例类,它的整个生命周期和应用的生命周期一样长,如果这个单例类持有了Activity的context,那么在整个应用程序的生命周期它都不能正常被回收,当我们退出Activity时,因为静态单例(在应用程序的整个生命周期中存在)会继续持有这个Activity的引用,导致这个Activity对象无法被回收释放,最终造成了内存泄露。

    private LocalBroadcastManager(Context context) {
        mAppContext = context;
        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,Handler传入的是context.getMainLooper,也就是传入了主线程的Looper,到这里我们知道了LocalBroadcastManager的广播是在主线程中处理的,最终回调的BroadcastReceiver的onReceive方法也是在主线程处理,因此在onReceive方法中是不能做耗时操作的。

LocalBroadcastManager实例获取完后就可以调用它的registerReceiver方法实现广播的注册,发送广播是在自身APP内进行发送的,因此广播的注册者所注册的过滤信息和广播之间的映射关系需要保存下来,ReceiverRecord是LocalBroadcastManager的静态内部类,内部就保存着注册者所注册的广播以及广播所接受的意图过滤器IntentFilter,可以通过它的addAction方法添加多个广播过滤信息,registerReceiver方法如下:

    private final HashMap<BroadcastReceiver, ArrayList<ReceiverRecord>> mReceivers = new HashMap<>();
    private final HashMap<String, ArrayList<ReceiverRecord>> mActions = new HashMap<>();

    public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        synchronized (mReceivers) {
            ReceiverRecord entry = new ReceiverRecord(filter, receiver);
            //获取该广播的订阅信息列表
            ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
            if (filters == null) {
                //该广播的订阅信息列表为空,创建新的列表,并将该广播的订阅信息列表通过mReceivers存储。
                filters = new ArrayList<>(1);
                //
                mReceivers.put(receiver, filters);
            }
            //保存相关信息
            filters.add(entry);
            //遍历该订阅信息列表
            for (int i = 0; i < filter.countActions(); i++) {
                String action = filter.getAction(i);
                //获取订阅信息对应的ReceiverRecord对象的集合
                ArrayList<ReceiverRecord> entries = mActions.get(action);
                if (entries == null) {
                    entries = new ArrayList<ReceiverRecord>(1);
                    mActions.put(action, entries);
                }
                entries.add(entry);
            }
        }
    }

在registerReceiver方法中,通过ReceiverRecord保存广播和IntentFilter的映射关系,如果同一个广播被多个订阅者注册呢?LocalBroadcastManager内部提供了集合mReceivers,以BroadcastReceiver为key,以广播的注册者的订阅信息和广播之间的映射关系列表为Value,总的来说mReceivers用于保存同一个广播与多个订阅者的关系之间的映射。以上方法中,一开始就从集合mReceivers中通过广播接收器获取ReceiverRecord列表,接着会将广播接收器和广播过滤信息包装成ReceiverRecord对象添加到ReceiverRecord列表中。

我们在注册广播时,可以添加多个广播过滤信息,因此需要遍历广播过滤信息,再通过mActions集合保存广播过滤信息对应的ReceiverRecord对象列表,为什么需要mActions集合,有了集合mReceivers不是也能获取到广播吗,这是因为在注册多个不同广播情况下,如果设置的广播过滤器都一样,最后就只有一个广播响应,很明显这是错误的,因此需要mActions集合保存每个过滤信息所对应的广播接收器的列表,这样就可以根据过滤信息拿到注册的广播列表,从而遍历广播列表回调onReceive方法。

总的来说,registerReceiver方法的目的是通过Map集合mReceivers和mActions来完成内部广播处理的一个协作问题。

注册完广播后就可以通过sendBroadcast方法来发送广播了,有了Map集合mReceivers和mActions后处理广播就简单多了,
sendBroadcast方法如下:

private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();

public boolean sendBroadcast(Intent intent) {
    synchronized (mReceivers) {
        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();

        final boolean debug = DEBUG ||
                ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
        if (debug) Log.v(
                TAG, "Resolving type " + type + " scheme " + scheme
                        + " of intent " + intent);

        //拿到订阅信息对应的广播接收器列表
        ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
        if (entries != null) {
            if (debug) Log.v(TAG, "Action list: " + entries);

            ArrayList<ReceiverRecord> receivers = null;
            for (int i = 0; i < entries.size(); i++) {
                ReceiverRecord receiver = entries.get(i);
                if (debug) Log.v(TAG, "Matching against filter " + receiver.filter);

                if (receiver.broadcasting) {
                    //broadcasting默认false
                    if (debug) {
                        Log.v(TAG, "  Filter's target already added");
                    }
                    continue;
                }

                //进行规则匹配
                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;
                } else {
                    if (debug) {
                        String reason;
                        switch (match) {
                            case IntentFilter.NO_MATCH_ACTION:
                                reason = "action";
                                break;
                            case IntentFilter.NO_MATCH_CATEGORY:
                                reason = "category";
                                break;
                            case IntentFilter.NO_MATCH_DATA:
                                reason = "data";
                                break;
                            case IntentFilter.NO_MATCH_TYPE:
                                reason = "type";
                                break;
                            default:
                                reason = "unknown reason";
                                break;
                        }
                        Log.v(TAG, "  Filter did not match: " + reason);
                    }
                }
            }

            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;
}

sendBroadcast方法传入一个Intent对象,方法中会先从Intent中获取广播过滤信息,根据这个广播过滤信息从mActions集合中获取ReceiverRecord列表,遍历ReceiverRecord列表,根据IntentFilter中的match匹配规则方法,符合要求的会将ReceiverRecord添加到局部遍历receivers集合中,接着将receivers集合包装成BroadcastRecord对象并添加到mPendingBroadcasts集合中,mPendingBroadcasts集合主要就是存储待接收的广播对象。最后通过Handler发送消息处理待接收的广播对象。

    private LocalBroadcastManager(Context context) {
        mAppContext = context;
        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);
                }
            }
        };
    }

    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) {
                        //调用广播接收器的onReceive方法
                        rec.receiver.onReceive(mAppContext, br.intent);
                    }
                }
            }
        }
    }

在sendBroadcast方法中最后会通过Handler发送一个MSG_EXEC_PENDING_BROADCASTS的消息,Handler接收到这个消息会调用executePendingBroadcasts方法,在该方法中通过同步代码块将待接收的广播对象转换成数组,再通过遍历待接收的广播列表调用对应广播的onReceive方法。

当我们离开当前页面时,需要解除广播的注册,这里调用的是unregisterReceiver方法。

    public void unregisterReceiver(BroadcastReceiver receiver) {
        synchronized (mReceivers) {
            final ArrayList<ReceiverRecord> filters = mReceivers.remove(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);
                            }
                        }
                        if (receivers.size() <= 0) {
                            mActions.remove(action);
                        }
                    }
                }
            }
        }
    }

unregisterReceiver方法很简单,主要是进行数据的回收,从mReceivers集合中移除该广播,并遍历从mReceiver集合中移除的ReceiverRecord列表,获取该广播注册时的所有广播过滤信息,并从mActions集合中移除。

除了sendBroadcast方法外还有一个发送广播的方法是sendBroadcastSync方法,代码如下:

    public void sendBroadcastSync(Intent intent) {
        if (sendBroadcast(intent)) {
            //直接进行处理
            executePendingBroadcasts();
        }
    }

它跟sendBroadcast方法不同之处在于,sendBroadcast方法中会将待接受的广播交由Handler来处理,Handler发送消息后会将消息添加到消息队列中,之后交由该线程的Looper去循环遍历消息队列,再由Handler来处理,也就是说通过sendBroadcast方法来发送广播,广播接收器接收消息的时机是未知的,但通过sendBroadcastSync方法来发送广播时,先通过sendBroadcast方法保存待接收的广播到mPendingBroadcasts集合中,接着就直接执行了executePendingBroadcasts()方法来接收广播。


总结

LocalBroadcastManager发送的广播只在自身APP内传播,其他APP发送的广播并不会接收到,提高了数据的安全性并避免了安全漏洞的发生,同时由于LocalBroadcastManager内部是通过Handler来发送广播的,性能更加高效,内部协作中主要靠两个Map集合mActions和mReceivers,待接收的广播由List集合mPendingBroadcasts存储。

©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页