源码探索系列5---关于Broadcast、LocalBroadcastManager 、EventBus的比较和源码解析

1. 比较

  • **性能 对比 : EventBus不差 **

    EventBus ~ LocalBroadCast  > Bradocast 
    
  • 运行的线程环境: EventBus完胜!

    LocalBroadcastManager 所有调用都是在主线程,
    EventBus 可以定义在调用线程、主线程、后台线程、异步。

  • 代码量比较: EventBus完胜!

  1. Bradocast

             @Override
             public void onStart() {
                 super.onStart();
                 // 注册广播接收
                 IntentFilter filter = new IntentFilter();
                 filter.addAction(Constants.USER_UPDATE);
                 receiveBroadCast = new ReceiveBroadCast();
                 registerReceiver(receiveBroadCast, filter);
             }
    
           // 发送
            Intent intent = new Intent();
            intent.setAction(Constants.BP_UPDATE);
             sendBroadcast(intent);
    
             class ReceiveBroadCast extends BroadcastReceiver {
                 @Override
                 public void onReceive(Context context, Intent intent) {
                     String action = intent.getAction();
           
                     if ( !TextUtils.isEmpty(action) && action.equals(Constants.USER_UPDATE)) {
                         Bundle bundle = intent.getExtras();
                         if (null != bundle) {
                             ...do Something 
                             }
                         }
                     }
         
                 }
             }        
             
             @Override
             public void onStop() {
                 super.onStop();
                              
                 if (receiveBroadCast != null) {
                     unregisterReceiver(receiveBroadCast);//注销
                 }
             }
    
  2. EventBus

         EventBus.getDefault().register(this);//注册
          
         EventBus.getInstance().post(ourEvnet());         //发送
    
         @Subscribe
         public void onEvent(TokenInvalidEvent event) {
            .....处理接收到的消息
         }
          
    
         EventBus.getDefault().unregister(this); //注销
    
    1. LocalBroadcastManager

       //注册
       LocalBroadcastReceiver localReceiver = new LocalBroadcastReceiver();
       LocalBroadcastManager.getInstance(context).registerReceiver(localReceiver, new IntentFilter(ACTION_LOCAL_SEND));  
       
       //发送
       LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(ACTION_LOCAL_SEND));
      
       //接收广播
       public class LocalBroadcastReceiver extends BroadcastReceiver {
        
           @Override
           public void onReceive(Context context, Intent intent) {
               localMsg.setText(intent.getStringExtra(MSG_KEY));
           }
       }    
       
       //取消
       LocalBroadcastManager.getInstance(context).unregisterReceiver(localReceiver);
      

好啦,我们可以看到,如果追求代码行数,正常人都会选择用EventBus,简单好用!
EventBus < BroadCast ~ LocalBroadCast 。

  • 信息量
  1. 系统广播:
    使用广播另外的一个点是,许多系统级的事件都是使用广播来进行的,
    像常用的屏幕开闭、网络状态变化、短信发送接收的状态等等。
    对于这些事情,没办法,系统指定了的,就只能用广播来干了。
    但广播的另外一个问题就是,他也是性能和空间消耗最大的,
    如果你不需要接收系统的消息和跨进程的,那用这个就显得有点浪费了。
  2. 接收方数目
    EventBus 还有一些额外功能,比如说定义多个接收方接收的顺序,LocalBroadcastManager对此不支持,
    但全局 Broadcast 是支持的 。
  • 灵活性
    EventBus的调度灵活,不依赖于 Context,使用时无需像广播一样关注 Context 的注入与传递。
    父类对于通知的监听和处理可以继承给子类,这对于简化代码至关重要
    通知的优先级,能够保证 Subscriber 关注最重要的通知;
    粘滞事件(sticky events)能够保证通知不会因 Subscriber 的不在场而忽略。
    可继承、优先级、粘滞,是 EventBus 比之于广播、观察者等方式最大的优点,它们使得创建结构良好组织紧密的通知系统成为可能。

综上,请根据自己需要做选择,已经说的明了,我们来看下源码上的事吧

LocalBroadcastManager 源码解析

API:23

获取单例

首先,我们从getInstance()开始说起,整个类就两百多行,算不复杂的了。

private static LocalBroadcastManager mInstance;

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

我们看到些有意思的内容,他的内部使用了Handler ,还记得前面比较说的工作环境吗?我们的本地广播是在主线程的,因为他内部用Handler的时候用的是MainLooper,在handleMessage()中会调用接收器对广播的消息进行处理。

另外我们看到了他的单例是这样些的,这种的效率是偏低的,但实际我们的程序一般没那么高的效率要求,这种也没太大问题,要改的话,可以改成DCL模式,或者静态内部类的形式。

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

注册广播

private final HashMap<BroadcastReceiver, ArrayList<IntentFilter>> mReceivers
            = new HashMap<BroadcastReceiver, ArrayList<IntentFilter>>();
private final HashMap<String, ArrayList<ReceiverRecord>> mActions
            = new HashMap<String, ArrayList<ReceiverRecord>>();
            
public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
    synchronized (mReceivers) {
        ReceiverRecord entry = new ReceiverRecord(filter, receiver);
        ArrayList<IntentFilter> filters = mReceivers.get(receiver);
        if (filters == null) {
            filters = new ArrayList<IntentFilter>(1);
            mReceivers.put(receiver, filters);
        }
        filters.add(filter);
        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);
        }
    }
}

我们看到他把广播信息存储下来,我猜应该就是用于后面有收到广播的时候,去查看是否有匹配的消息,然后调用接收去执行的把。 同时mReceivers 是接收器和IntentFilter的对应表,主要作用是方便在unregisterReceiver(…)取消注册。
mActions 以Action为 key,注册这个Action的BroadcastReceiver链表为 value。我猜是为了方便在广播发送后快速得到可以接收它的BroadcastReceiver。
有这种猜测是因为EventBus的源码里面也有类似的做法,在有程序发送Post消息时候,可以查找然后执行。

注销

就让我们来看下他取消注册的时候是怎么干的,来验证下我们前面的猜想把

 public void unregisterReceiver(BroadcastReceiver receiver) {
    synchronized (mReceivers) {
        ArrayList<IntentFilter> filters = mReceivers.remove(receiver);
        if (filters == null) {
            return;
        }
        for (int i=0; i<filters.size(); i++) {
            IntentFilter filter = filters.get(i);
            for (int j=0; j<filter.countActions(); j++) {                     
                String action = filter.getAction(j);
                ArrayList<ReceiverRecord> receivers = mActions.get(action);
                //这几句说明我们猜对了
                if (receivers != null) {
                    for (int k=0; k<receivers.size(); k++) {
                        if (receivers.get(k).receiver == receiver) {
                            receivers.remove(k);
                            k--;
                        }
                    }
                    if (receivers.size() <= 0) {
                        mActions.remove(action);
                    }
                }
            }
        }
    }
}

发送广播消息

为了方便阅读,把一些debug的内容给砍掉了,来让我们看下我们前面的猜想,以及消息是如何传递到目标Receiver吧

 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 (receiver.broadcasting) { 
                    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;
}

开头我们就看到,他取出Action对应的ReceiverRecord列表,遍历每个ReceiverRecord是否 匹配,是的话则保存到receivers中去,接着发送MSG_EXEC_PENDING_BROADCASTS消息,通过 Handler 去处理。
对于匹配,我们看到好长一行,match(action, type, scheme, data,categories, "LocalBroadcastManager") ,对于匹配规则,估计对于一些人还是很模糊,欢迎百度查阅下。

我们继续来看下这个消息对应的函数执行了什么

private void executePendingBroadcasts() {
    while (true) {
        BroadcastRecord[] brs = null;
        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++) {
            BroadcastRecord br = brs[i];
            for (int j=0; j<br.receivers.size(); j++) {
                br.receivers.get(j).receiver.onReceive(mAppContext, br.intent);
            }
        }
    }
}

我们看到他对mReceiver加了synchronized,防止后面还有人发送广播时候再修改它,导致bug。
最后再做遍历,去调用各个receive的onReceive方法。这样整个广播流程基本就跑完了

另外他还提供了个同步的方法来发送广播,这个有意思。

* Like {@link #sendBroadcast(Intent)}, but if there are any receivers for
* the Intent this function will block and immediately dispatch them before
* returning.
 public void sendBroadcastSync(Intent intent) {
        if (sendBroadcast(intent)) {
            executePendingBroadcasts();
        }
    }

一些观点:

对于这个本地广播LocalBroadcastManager,我们就有个大致的理解了,怎么说也把人家给看了遍了,
他的底层实现是基于Handler来做应用内的通信,自然安全性更好,相比与基于Binder的系统广播来说,效率更高。
另外比较坑的是
另外比较坑的是
另外比较坑的是
这个类要我们写的接收是BroadcastReceiver,但我们阅读完可以知道,完全没必要啊,自己定义一个接口都行啦 ,直接就是匹配然后就执行。这也很好解释他是没有优先级等概念的原因,这个就是"轻量级"的系统广播嘛。

而且,像上面那也不是使用静态内部类的做法不是很好,虽然在这个上下文情况看是不会有大问题。
关于Handler的正确姿势,欢迎查看这篇文章源码探索系列---Handler与HandlerLeak的那些事

最后想说的

虽然比较了这么多,但实际想说的是,本地广播EventBus其实对于很多人写的那项目来说是差不多,只是写起来啰嗦点,像我这种追求简洁和效率点的(其实就是懒),应该都会用EventBus,最少从我看的很多项目里面有看到他的影子,就这样_

后记

不知道为啥,有个习惯就是有个后记,哈哈,虽然上面写的就是最后想说了。
想在还缺了EventBus的源码解析,虽然这库看了两次吧,不过也没记录过,因为网上分析这个太多而且写得太好了。虽然这个本地广播也有大把人写过,不过自己没看过,就顺便写在这里吧。
接下来有空再去看下系统广播是怎么实现,再 贴出来。

参考来源:

LocalBroadcastManager

Android 应用内全局通知那种方案更好?观察者、eventbus、本地广播

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

推荐阅读更多精彩内容