自定義訊息訂閱框架NotificationBus
1.概述
看過我之前關於EventBus 講解的文章 ofollow,noindex">《EventBus原理與原始碼解析》 ,可以瞭解到EventBus是針對Android優化的釋出-訂閱事件匯流排,簡化了Android元件間的通訊。有了EventBus已經很方便我們平時日常開發中的元件通訊了,但在仔細研讀其原始碼之後,我也在之前的文章中提到了兩個疑問:
- 其訊息給人感覺是一種亂跳的感覺,因為其採用註解的方式,這點感覺對業務邏輯梳理並不一定佔有優勢,就拿Android Studio來說,居然會提示該方法無處使用。
- 採用反射方法invokeSubscriber來消費事件,效率如何。
基於以上的兩個疑問,結合我目前做的專案,大概簡單寫了個通EventBus 具備相同功能框架 NotificationBus" target="_blank" rel="nofollow,noindex">NotificationCenter 。以下是關於NotificationBus的一些基本講解。
2.基本使用
NotificationBus 的基本使用和EventBus使用相差無幾,均需要註冊,釋出,反註冊這幾個步驟。其基本流程圖如下:

NotificationCenter.png
2.1基本結構
從下圖我們可以看出整個框架程式碼結構非常精簡,同EventBus一樣也是基於觀察者模式來設計的。

NotificationCenter—Structure.png
2.2 使用方法
- 專案引用NotificationBus,在專案的build檔案中新增
compile project(path: ':notificatonlibrary')
- 註冊:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); NotificationCenter.defaultCenter().subscriber(TOP_KEY,subscriber); NotificationCenter.defaultCenter().subscriber(EventSubscriber.class,eventSubscriber); initView(); }
- 反註冊:
@Override protected void onDestroy() { super.onDestroy(); NotificationCenter.defaultCenter().unsubscribe(TOP_KEY,subscriber); NotificationCenter.defaultCenter().unsubscribe(EventSubscriber.class,eventSubscriber); }
- 釋出訊息
public void onClick(View v) { int viewId = v.getId(); if (viewId==R.id.bt_send){ NotificationCenter.defaultCenter().publish("top_key",null); EventSubscriber eventSubscriber = new EventSubscriber(); NotificationCenter.defaultCenter().publish(eventSubscriber); } }
可以看到我們輸出的log如下

log.png
基本使用就是這麼簡單,具體可以參照 Demo 。
3. 原始碼解讀
3.1註冊,從上述使用的過程我們可以看到,存在兩種註冊方式
NotificationCenter.defaultCenter().subscriber(TOP_KEY,subscriber);
和 NotificationCenter.defaultCenter().subscriber(EventSubscriber.class,eventSubscriber);
兩種方式。首先先看defaultCenter 這個方法
public static NotificationCenter defaultCenter() { if (_instanceCenter == null) { _instanceCenter = new NotificationCenter(); } return _instanceCenter ; }
也是一個單例模式,我們來看看構造方法NotificationCenter
private NotificationCenter() { Looper looper = Looper.getMainLooper(); mHandler = new Handler(looper); }
會建立一個mHandler物件,這個是訊息傳送的根本。
從其中呼叫可以看,採用了物件作為第二個引數,訊息傳遞過來,當然是在subscriber物件中去處理業務邏輯。這就解決了個人對EventBus的註解疑惑,畢竟這樣做至少從程式碼層面可以看出無未被使用的程式碼,而註解的話unused方法Android Studio一直提示,強迫症不喜歡,哈哈哈。下面以此看兩種註冊方式
/** * 訂閱訊息 * * @param topic主題key * @param subscriber 回撥介面 * @return */ public boolean subscriber(String topic, TopicSubscriber subscriber) { if (TextUtils.isEmpty(topic)) { throw new IllegalArgumentException( "Topic must not be null or empty"); } if (subscriber == null) { throw new IllegalArgumentException( "Event subscriber must not be null"); } return subscribe(topic, subscribersByTopic, new WeakReference<TopicSubscriber>(subscriber)); }
public boolean subscriber(Class eventClass, Subscriber subscriber) { if (eventClass == null) { throw new IllegalArgumentException("Event class must not be null"); } if (subscriber == null) { throw new IllegalArgumentException( "Event subscriber must not be null"); } return subscribe(eventClass, subscribersByClass, new WeakReference<Subscriber>(subscriber)); }
可以看到兩個傳入引數基本無差別,topic型別的忽略。以class傳入引數的方法我們通過上文可以看到是 NotificationCenter.defaultCenter().subscriber(EventSubscriber.class,eventSubscriber);
再看EventSubscriber類
/** * FileName: EventSubscriber * Author: owen.zhan * Date: 2018/10/26 16:52 */ public class EventSubscriber{ }
其實其根本沒做任何特殊處理,就是一個簡單的java 類。而eventSubscriber物件則是
Subscriber<EventSubscriber> eventSubscriber = new Subscriber<EventSubscriber>() { @Override public void onEvent(EventSubscriber event) { NLog.d(NotificationCenter.TAG,"onEvent===="); } };
由此可見訊息返回後均在onEvent方法中去處理業務邏輯。
下面繼續回過頭來看註冊,從上面註冊的程式碼我們可知,最終均是採用方法subscribe ,只是會區分topic還是class型別去註冊:
採用Topic來註冊
protected boolean subscribe(final Object classTopicOrPatternWrapper, final Map<Object, Object> subscriberMap, final Object subscriber) { if (classTopicOrPatternWrapper == null) { throw new IllegalArgumentException("Can't subscribe to null."); } if (subscriber == null) { throw new IllegalArgumentException( "Can't subscribe null subscriber to " + classTopicOrPatternWrapper); } boolean alreadyExists = false; Object realSubscriber = subscriber; boolean isWeakRef = subscriber instanceof WeakReference; if (isWeakRef) { realSubscriber = ((WeakReference) subscriber).get(); } boolean isWeakProxySubscriber = false; //1 if (subscriber instanceof ProxySubscriber) { ProxySubscriber proxySubscriber = (ProxySubscriber) subscriber; isWeakProxySubscriber = proxySubscriber.getReferenceStrength() == ReferenceStrength.WEAK; if (isWeakProxySubscriber) { realSubscriber = ((ProxySubscriber) subscriber) .getProxiedSubscriber(); } } if (isWeakRef && isWeakProxySubscriber) { throw new IllegalArgumentException( "ProxySubscribers should always be subscribed strongly."); } if (realSubscriber == null) { return false; } synchronized (listenerLock) {//2 //判斷該top是否已經被註冊 List currentSubscribers = (List) subscriberMap .get(classTopicOrPatternWrapper); if (currentSubscribers == null) {//3 currentSubscribers = new ArrayList(); subscriberMap.put(classTopicOrPatternWrapper, currentSubscribers); } else { for (Iterator iterator = currentSubscribers.iterator(); iterator .hasNext(); ) { Object currentSubscriber = iterator.next(); Object realCurrentSubscriber = getRealSubscriberAndCleanStaleSubscriberIfNecessary( iterator, currentSubscriber); if (realSubscriber.equals(realCurrentSubscriber)) { iterator.remove(); alreadyExists = true; } } } currentSubscribers.add(subscriber); return !alreadyExists; } }
- 判斷是topic註冊還是其他型別註冊。
- 判斷該物件是否已經被註冊,前面部分程式碼作出基本判斷。
private Map subscribersByTopic = new HashMap();//根據topic 來註冊的訊息 private Map subscribersByClass = new HashMap();//根據物件來註冊的訊息
3.2 傳送訊息
從上文我們可知訊息最終是採用publish方法發出去。
/** * 傳送訊息 * * @param topicName * @param eventObj */ public void publish(String topicName, Object eventObj) { mHandler.post(new PublishRunnable(null, topicName, eventObj, getSubscribersToTopic(topicName))); }
/** * topic 為class * @param event */ public void publish(Object event) { if (event == null) { throw new IllegalArgumentException("Cannot publish null event."); } mHandler.post(new PublishRunnable(event, null, null, getSubscribers(event.getClass()))); }
可以看出最終均是通過handler來處理一個PublishRunnable,我們下面來看看該類:
class PublishRunnable implements Runnable { Object theEvent; String theTopic; Object theEventObject; List theSubscribers; public PublishRunnable(final Object event, final String topic, final Object eventObj, final List subscribers) { this.theEvent = event; this.theTopic = topic; this.theEventObject = eventObj; this.theSubscribers = subscribers; } @Override public void run() { publish(theEvent, theTopic, theEventObject, theSubscribers); } }
繼續往下檢視:publish方法:
protected void publish(final Object event, final String topic, final Object eventObj, final List subscribers) { if (event == null && topic == null) { throw new IllegalArgumentException( "Can't publish to null topic/event."); } if (subscribers == null || subscribers.isEmpty()) { return; } for (int i = 0; i < subscribers.size(); i++) { Object eh = subscribers.get(i); //區分是主題型別還是物件型別 if (event != null) { Subscriber eventSubscriber = (Subscriber) eh; try { //1 eventSubscriber.onEvent(event); } catch (Throwable e) { } } else { //2 TopicSubscriber eventTopicSubscriber = (TopicSubscriber) eh; try { eventTopicSubscriber.onEvent(topic, eventObj); } catch (Throwable e) { } } } }
可以看到該方法是用來遍歷前面提到的集合,然後再判斷註冊的型別,最終到達訊息傳遞的功能。
- class 註冊方式來處理。
- topic 註冊方式來處理。
3.3 反註冊
/** * * @param cl * @param subscriber * @return */ public boolean unsubscribe(Class cl, Subscriber subscriber) { return unsubscribe(cl, subscribersByClass, subscriber); }
/** * 反向註冊 * * @param topic * @param subscriber * @return */ public boolean unsubscribe(String topic, TopicSubscriber subscriber) { return unsubscribe(topic, subscribersByTopic, subscriber); }
可以看到最終均是走向unubscribe方法,那麼我們來看看這個方法
protected boolean unsubscribe(Object o, Map subscriberMap, Object subscriber) { if (o == null) { throw new IllegalArgumentException("Can't unsubscribe to null."); } if (subscriber == null) { throw new IllegalArgumentException( "Can't unsubscribe null subscriber to " + o); } synchronized (listenerLock) { return removeFromSetResolveWeakReferences(subscriberMap, o, subscriber); } }
可以發現是走向了removeFromSetResolveWeakReferences 方法,繼續往下檢視
private boolean removeFromSetResolveWeakReferences(Map map, Object key, Object toRemove) { List subscribers = (List) map.get(key); if (subscribers == null) { return false; } if (subscribers.remove(toRemove)) { if (toRemove instanceof WeakReference) { // decWeakRefPlusProxySubscriberCount(); } if (toRemove instanceof ProxySubscriber) { ((ProxySubscriber) toRemove).proxyUnsubscribed(); // decWeakRefPlusProxySubscriberCount(); } return true; } // search for WeakReferences and ProxySubscribers for (Iterator iter = subscribers.iterator(); iter.hasNext(); ) { Object existingSubscriber = iter.next(); if (existingSubscriber instanceof ProxySubscriber) { ProxySubscriber proxy = (ProxySubscriber) existingSubscriber; existingSubscriber = proxy.getProxiedSubscriber(); if (existingSubscriber == toRemove) { removeProxySubscriber(proxy, iter); return true; } } if (existingSubscriber instanceof WeakReference) { WeakReference wr = (WeakReference) existingSubscriber; Object realRef = wr.get(); if (realRef == null) { // clean up a garbage collected reference iter.remove(); // decWeakRefPlusProxySubscriberCount(); return true; } else if (realRef == toRemove) { iter.remove(); // decWeakRefPlusProxySubscriberCount(); return true; } else if (realRef instanceof ProxySubscriber) { ProxySubscriber proxy = (ProxySubscriber) realRef; existingSubscriber = proxy.getProxiedSubscriber(); if (existingSubscriber == toRemove) { removeProxySubscriber(proxy, iter); return true; } } } } return false; }
整體功能就是判斷型別,依次清理上述說的兩個集合subscribersByTopic和subscribersByClass。
整NotificationCenter 基本使用和邏輯結構基本如此,詳細的可以參照 Demo 去研究和拓展。
4 . 總結
NotificationBus 是根據專案需求,結合EventBus來寫的一個訊息訂閱框架,歡迎各位大神來多多補充和交流。關於其中個人覺得的優缺點作出以下幾點簡單說明,歡迎拍磚。
優點:
- 整體採用map來管理,最終是遍歷集合查詢,相對EventBus採用物件繫結,然後最終採用反射來呼叫,效能方面會更好一點。
- 沒有用註解,個人覺得使用起來業務邏輯清晰。
- 比EventBus更輕量化。
缺點:
1 . 沒有EventBus會區分執行緒來註冊和釋出訊息,這點不夠全面。
以上是根據EventBus 的原始碼研究和結合專案需求來寫的,中間會存在很多問題,希望各位大神多多指教。