源碼探索系列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); //註銷
      
    3. 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、本地廣播

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章