Android LocalBroadcastManager 原理

概述

是什么

一个用于在「当前进程空间」内「注册」 Receiver 和「发送」 Intent 广播的辅助类。

解决什么问题

某个进程空间内多个模块间通信问题

使用方法

基本使用方法

        // 拿到 LocalBroadcastManager 单例
        final LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
        BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                // 接收到广播
                Log.d(TAG, "obj@" + System.identityHashCode(this) + " " +
                        "onReceive() called with: context = [" + context + "], " +
                        "intent = [" + intent + "]");
                // 反注册 Receiver
                manager.unregisterReceiver(this);
            }
        };
        IntentFilter filter = createFilter(ACTION_0);
        // 注册 Receiver
        manager.registerReceiver(broadcastReceiver, filter);
        // 发送广播
        manager.sendBroadcast(createIntent(ACTION_0));

进阶使用方法

在发送线程同步回调接收者 sendBroadcastSync

        final LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);

        final BroadcastReceiver receiver0 = createBroadcastReceiver();

        // 注册观察者
        manager.registerReceiver(receiver0, createFilter(ACTION_0));

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 在后台线程发送同步广播
                manager.sendBroadcastSync(createIntent(ACTION_0));
            }
        }).start();

输出 log 信息解读:
在「01-14 06:09:39.553」时间点,包名为 com.example.guangli.broadcastmanager 的应用进程「3512」 空间内后台线程 「3527」 中,地址为 「151810842」 的 Receiver 对象收到 onReceive 消息

01-14 06:09:39.553 3512-3527/com.example.guangli.broadcastmanager D/MainActivity: 
obj@151810842 onReceive() called with: context = [android.app.Application@57b1e4b],
 intent = [Intent { act=com.liguang.action0 }]

内部原理

抓住本质

  • 观察者模式
  • 订阅/发布模式
  • 生产者/消费者模式
  • 数组和映射表

注册流程

最终目的是记录调用者输入的 Receiver 对象

    public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        synchronized (mReceivers) {
            // 封装 receiver 感兴趣的 filter 到 ReceiverRecord 对象内部
            ReceiverRecord entry = new ReceiverRecord(filter, receiver);
            ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
            if (filters == null) {
                filters = new ArrayList<>(1);
               // 添加到 mReceivers 记录下来
                mReceivers.put(receiver, filters);
            }
            filters.add(entry);
            for (int i=0; i<filter.countActions(); i++) {
                String action = filter.getAction(i);
                ArrayList<ReceiverRecord> entries = mActions.get(action);
                if (entries == null) {
                    entries = new ArrayList<ReceiverRecord>(1);
                    // 建立 action 到 receiver 的映射,降低发送广播时查找复杂度
                    mActions.put(action, entries);
                }
                entries.add(entry);
            }
        }
    }

发送广播流程

生产者往容器中写入数据,最终目的找到对 Intent 感兴趣的 Receiver 对象,并通过 Handler 切换到主线程派发 Intent

    public boolean sendBroadcast(Intent intent) {
        synchronized (mReceivers) {
            ...
            // 找到对 action 感兴趣的 receiver
            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);
                    // 让 Filter 计算 match 数值
                    int match = receiver.filter.match(action, type, scheme, data,
                            categories, "LocalBroadcastManager");
                    if (match >= 0) {
                        if (receivers == null) {
                            receivers = new ArrayList<ReceiverRecord>();
                        }
                        receivers.add(receiver);
                    }
                }

                if (receivers != null) {
                    // 添加到待广播队列
                    mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
                    if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
                        // 切换到主线程消费待广播队列
                        mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
                    }
                    return true;
                }
            }
        }
        return false;
    }

消费者从容器中读取数据,并清空容器

    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) {
                        // 把 Intent 派发给 Receiver
                        rec.receiver.onReceive(mAppContext, br.intent);
                    }
                }
            }
        }
    }

反注册流程

目的是从 LocalBroadcastManager 内部移除对 receiver 的所有引用

    public void unregisterReceiver(BroadcastReceiver receiver) {
        synchronized (mReceivers) {
            // 从 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;
                                // 从 action => receivers 映射移除
                                receivers.remove(k);
                            }
                        }
                        if (receivers.size() <= 0) {
                            mActions.remove(action);
                        }
                    }
                }
            }
        }
    }

优缺点

优点

  • 从输出角度来看,广播数据不会离开当前进程空间,所以不必担心泄漏隐私数据
  • 从输入角度来看,其他进程空间也不能向我们发送广播,所以不必担心其他进程可以利用我们的安全漏洞
  • 高效性,数据不必在整个系统空间中经过两次 Binder 进程间通信

缺点

  • 不支持 Sticky 特性,某些需要后注册 Receiver 也可以收到信息的特殊需求无法实现
  • 不支持 order 特性,Intent 分发过程中无法支持某个 Receiver 已经吃掉该 Intent 的某些特性
  • 多线程并发性能不高,内部使用同一把锁对容器的读写进行保护,所有读写需要串行,没有 CopyOnWrite 特性

练习题

  • 删除所有接口内部实现,从零开始实现接口,锻炼写接口能力
  • 不用内部定义的新的类,自己设计数据结构,锻炼设计数据结构能力

总结

  • 使用方便,对外的接口只有 5 个
  • 数据结构简单,内部只有 ReceiverRecord 和 BroadcastRecord 新的类型,使用 HashMap 和 ArrayList 记录信息
  • 算法简单,基本是对 HashMap 和 ArrayList 的读写,只理解主要信息处理流程,没有过多其他逻辑,总共 300+ 行
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352