Android LocalBroadcast 发送广播更加方便快捷高效的方式

LocalBroadcast是一个应用内的局部广播,广播范围只限于应用内部,并且不属于四大组件,相对高效很多,可以用来实现事件总线,相比其他事件总线实现方案,先对简单,高效。
而且,其实现很简单,设计的很好;

优点

  • 广播范围只限于应用内部,不会出现数据泄漏和安全漏洞问题;
  • 相对于系统广播,更加高效便捷;
  • 相对于其他事件总线框架,实现简单,高效,不存在例如EventBus事件类代码区域划分问题;
  • 可以集中管理Action,使用系统Intent传递数据,学习成本低;
  • 虽然Intent传递数据,但是内部没有涉及到序列化,所以不同于四大组件的Intent实体需要实现序列化,并且大小也无限制;
  • 使用IntentFilter进行事件匹配,所以可以是多维度的,是根据Action, Type, Scheme, Uri, Categories进行匹配的;

缺点

  • 无法线程变换,事件处理默认在主线程;
  • 无法实现粘性事件;
  • 如果不定义Action常量,则需要用文档管理维护大量的Action;IntentFilter也相当于事件类,如果是多纬度的,那么就需要维护IntentFilter这个事件类了;
  • 查找订阅事件或者发送事件比较困难,只能全局搜索,如果定义Action常量,那么Action代码也会出现区域划分问题;

注意

  • 可以重复注册,并且重复注册后会收到多次回调;

使用

//1. 创建BroadcastReceiver
val broadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Log.i("shuxin.wei", "onReceive")
    }
}
//2. 在合适的位置注册监听
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("ACTION_TEST"))
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("ACTION_TEST"))
//3. 发布事件
LocalBroadcastManager.getInstance(this).sendBroadcastSync(Intent("ACTION_TEST"))
Log.i("shuxin.wei", "sendBroadcastSync")
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent("ACTION_TEST"))
Log.i("shuxin.wei", "sendBroadcast")
//4. 在合适的位置取消监听
if (broadcastReceiver != null) {
    LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
}
//日志输出
//可以看到同步广播是在onReceive执行完,才继续向下执行的;
//广播是可以重复注册的,并会重复收到;
shuxin.wei: onReceive
shuxin.wei: onReceive
shuxin.wei: sendBroadcastSync
shuxin.wei: sendBroadcast
shuxin.wei: onReceive
shuxin.wei: onReceive

源码分析

1. 数据结构

//保存接收者与IntentFilter,并记录广播状态
//这个实体,既可以用作代替BroadcastReceiver,也可以用作代替IntentFilter
//在注册时利用其保存了BroadcastReceiver与多个IntentFilter对应关系
//也利用其保存了Action与多个BroadcastReceiver的对应关系
private static final class ReceiverRecord {
    final IntentFilter filter;
    final BroadcastReceiver receiver;
    boolean broadcasting;
    boolean dead;
    ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {
        filter = _filter;
        receiver = _receiver;
    }
}
//保存广播和Intent,在分发广播时,将Intent传递到接收者的receiver方法中
//receiver.onReceive(mAppContext, br.intent)
private static final class BroadcastRecord {
    final Intent intent;
    final ArrayList<ReceiverRecord> receivers;
    BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) {
        intent = _intent;
        receivers = _receivers;
    }
}
//记录BroadcastReceiver和注册的所有IntentFilter
//例如在多个位置对相同的BroadcastReceiver注册多个IntentFilter
HashMap<BroadcastReceiver, ArrayList<ReceiverRecord>> mReceivers = new HashMap<>();
//记录Action和对应所有的BroadcastReceiver
//不同的BroadcastReceiver可以注册相同的Action
HashMap<String, ArrayList<ReceiverRecord>> mActions = new HashMap<>();
//记录所有要分发的Intent和对应的所有的BroadcastReceiver
//不同BroadcastReceiver可以对应相同的Action,所以,分发时一个Intent可以对应多个BroadcastReceiver
ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();

2. 创建LocalBroadcastManager实例

  1. LocalBroadcastManager是相对线程安全的单例;
  2. 使用ApplicationContext,不会出现内存泄漏;
  3. Handler使用MainLooper,回调只能在主线程;
public static LocalBroadcastManager getInstance(Context context) {
    synchronized (mLock) {
        if (mInstance == null) {
            mInstance = new LocalBroadcastManager(context.getApplicationContext());
        }
        return mInstance;
    }
}
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);
            }
        }
    };
}

2. 注册监听

  1. 同步方法,线程安全
  2. 用ReceiverRecord保存BroadcastReceiver和IntentFilter
  3. 从HashMap中获取BroadcastReceiver对应的IntentFilter集合
  4. 如果没有注册过BroadcastReceiver,则创建新的filters集合,添加到HashMap中,并将ReceiverRecord保存到filters集合;
  5. 开始处理Action,遍历要注册的IntentFilter的Action;
  6. 根据Action从HashMap中取到对应的BroadcastReceiver集合;
  7. 如果没有注册这个Action,则创建新的BroadcastReceiver集合,添加到HashMap中,并将ReceiverRecord保存到BroadcastReceiver集合;
    总结:同一个BroadcastReceiver可以注册多个相同或不同IntentFilter;
    相同Action可以对应多个相同或不同BroadcastReceiver;
public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
    synchronized (mReceivers) {
        ReceiverRecord entry = new ReceiverRecord(filter, receiver);
        ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
        if (filters == null) {
            filters = new ArrayList<>(1);
            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);
                mActions.put(action, entries);
            }
            entries.add(entry);
        }
    }
}

3. 取消注册

  1. 同步方法,线程安全;
  2. 根据BroadcastReceiver移除对应的所有的IntentFilter;
  3. 如果IntentFilter集合为空,那么说明已经被unregister过,Action的移除也处理过了,直接结束;
  4. 如果IntentFilter集合不为空,则还需要处理Action,遍历所有的IntentFilter;
  5. 标记ReceiverRecord为dead;继续遍历IntentFilter中的Action;
  6. 根据Action取出对应的ReceiverRecord集合,遍历ReceiverRecord集合,移除匹配的BroadcastReceiver;
  7. 如果Action取出对应的ReceiverRecord集合为空了,那么不再需要这个Action,移除掉,释放内存;
    思考:如果mActions里面用HashMap<String,HashMap<BroadcastReceiver, ArrayList<ReceiverRecord>>>保存Action与ReceiverRecord对应关系,那么移除时,可以直接根据BroadcastReceiver移除ReceiverRecord,理论上效率应该会更高一些,虽然性能差距可能不大
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);
                    }
                }
            }
        }
    }
}

4. 发送广播

  1. 相对主线程,可以是异步发送的,加了锁,线程安全;
  2. 解析Intent,根据Action取出所有对应的ReceiverRecord;
  3. 如果ReceiverRecord集合不为空,则进行事件处理,否则,没有对应的事件,直接retrun false;
  4. 遍历ReceiverRecord集合,判断当前事件是否已经添加到等待处理的广播集合中;
    broadcasting的作用是??????在注册时ReceiverRecord都是直接new出来的,所以不会出现重复,那么broadcasting的作用是什么呢?没搞懂!!!
  5. 进行IntentFilter匹配,匹配成功,添加到待分发的ReceiverRecord集合中;
  6. 将Intent和ReceiverRecord集合包装添加到待分发的mPendingBroadcasts中;
  7. 如果mHandler里没有等待执行的任务,则启动一个任务;
  8. 任务启动后会回调到mHandler中,执行executePendingBroadcasts()方法;
  9. 将mPendingBroadcasts复制一份,然后调用receiver.onReceive(mAppContext, br.intent),清空mPendingBroadcasts,完成事件分发;
    分发中的死循环是处理异步任务可能随时见添加新的事件,确保一次执行能将所有任务处理完;
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();
        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);
                if (debug) Log.v(TAG, "Matching against filter " + receiver.filter);
                if (receiver.broadcasting) {
                    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 (debug) Log.v(TAG, "  Filter matched!  match=0x" +
                            Integer.toHexString(match));
                    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;
}

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

同步广播

sendBroadcast方法是同步的,一定是执行完了receiver.onReceive(mAppContext, br.intent)回调才会继续向下执行,否则阻塞于while循环中;

public void sendBroadcastSync(Intent intent) {
    if (sendBroadcast(intent)) {
        executePendingBroadcasts();
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容