自定義消息訂閱框架NotificationBus

1.概述

看過(guò)我之前關(guān)于EventBus 講解的文章《EventBus原理與源碼解析》,可以了解到EventBus是針對(duì)Android優(yōu)化的發(fā)布-訂閱事件總線,簡(jiǎn)化了Android組件間的通信。有了EventBus已經(jīng)很方便我們平時(shí)日常開(kāi)發(fā)中的組件通信了,但在仔細(xì)研讀其源碼之后,我也在之前的文章中提到了兩個(gè)疑問(wèn):

  1. 其消息給人感覺(jué)是一種亂跳的感覺(jué),因?yàn)槠洳捎米⒔獾姆绞?,這點(diǎn)感覺(jué)對(duì)業(yè)務(wù)邏輯梳理并不一定占有優(yōu)勢(shì),就拿Android Studio來(lái)說(shuō),居然會(huì)提示該方法無(wú)處使用。
  2. 采用反射方法invokeSubscriber來(lái)消費(fèi)事件,效率如何。
    基于以上的兩個(gè)疑問(wèn),結(jié)合我目前做的項(xiàng)目,大概簡(jiǎn)單寫(xiě)了個(gè)同EventBus 具備相同功能框架 NotificationCenter。以下是關(guān)于NotificationBus的一些基本講解。

2.基本使用

NotificationBus的基本使用和EventBus使用相差無(wú)幾,均需要注冊(cè),發(fā)布,反注冊(cè)這幾個(gè)步驟。其基本流程圖如下:

NotificationCenter.png

2.1基本結(jié)構(gòu)

從下圖我們可以看出整個(gè)框架代碼結(jié)構(gòu)非常精簡(jiǎn),同EventBus一樣也是基于觀察者模式來(lái)設(shè)計(jì)的。


NotificationCenter—Structure.png

2.2 使用方法

  1. 項(xiàng)目引用NotificationBus,在項(xiàng)目的build文件中添加
    compile project(path: ':notificatonlibrary')
  2. 注冊(cè):
 @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();
    }
  1. 反注冊(cè):
@Override
    protected void onDestroy() {
        super.onDestroy();
        NotificationCenter.defaultCenter().unsubscribe(TOP_KEY,subscriber);
        NotificationCenter.defaultCenter().unsubscribe(EventSubscriber.class,eventSubscriber);
    }
  1. 發(fā)布消息
 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

基本使用就是這么簡(jiǎn)單,具體可以參照Demo。

3. 源碼解讀

3.1注冊(cè),從上述使用的過(guò)程我們可以看到,存在兩種注冊(cè)方式

NotificationCenter.defaultCenter().subscriber(TOP_KEY,subscriber);NotificationCenter.defaultCenter().subscriber(EventSubscriber.class,eventSubscriber);兩種方式。首先先看defaultCenter 這個(gè)方法

public static NotificationCenter defaultCenter() {
        if (_instanceCenter == null) {
            _instanceCenter = new NotificationCenter();
        }
        return _instanceCenter ;
    }

也是一個(gè)單例模式,我們來(lái)看看構(gòu)造方法NotificationCenter

private NotificationCenter() {
        Looper looper = Looper.getMainLooper();
        mHandler = new Handler(looper);
    }

會(huì)創(chuàng)建一個(gè)mHandler對(duì)象,這個(gè)是消息發(fā)送的根本。
從其中調(diào)用可以看,采用了對(duì)象作為第二個(gè)參數(shù),消息傳遞過(guò)來(lái),當(dāng)然是在subscriber對(duì)象中去處理業(yè)務(wù)邏輯。這就解決了個(gè)人對(duì)EventBus的注解疑惑,畢竟這樣做至少?gòu)拇a層面可以看出無(wú)未被使用的代碼,而注解的話unused方法Android Studio一直提示,強(qiáng)迫癥不喜歡,哈哈哈。下面以此看兩種注冊(cè)方式

 /**
     * 訂閱消息
     *
     * @param topic      主題key
     * @param subscriber 回調(diào)接口
     * @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));
    }

可以看到兩個(gè)傳入?yún)?shù)基本無(wú)差別,topic類型的忽略。以class傳入?yún)?shù)的方法我們通過(guò)上文可以看到是NotificationCenter.defaultCenter().subscriber(EventSubscriber.class,eventSubscriber);再看EventSubscriber類

/**
* FileName: EventSubscriber
* Author: owen.zhan
* Date: 2018/10/26 16:52
*/
public class EventSubscriber  {

}

其實(shí)其根本沒(méi)做任何特殊處理,就是一個(gè)簡(jiǎn)單的java 類。而eventSubscriber對(duì)象則是

Subscriber<EventSubscriber> eventSubscriber = new Subscriber<EventSubscriber>() {
       @Override
       public void onEvent(EventSubscriber event) {
           NLog.d(NotificationCenter.TAG,"onEvent====");
       }
   };

由此可見(jiàn)消息返回后均在onEvent方法中去處理業(yè)務(wù)邏輯。
下面繼續(xù)回過(guò)頭來(lái)看注冊(cè),從上面注冊(cè)的代碼我們可知,最終均是采用方法subscribe ,只是會(huì)區(qū)分topic還是class類型去注冊(cè):
采用Topic來(lái)注冊(cè)

 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是否已經(jīng)被注冊(cè)
            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;
        }
    }
  1. 判斷是topic注冊(cè)還是其他類型注冊(cè)。
  2. 判斷該對(duì)象是否已經(jīng)被注冊(cè),前面部分代碼作出基本判斷。
 private Map subscribersByTopic = new HashMap();//根據(jù)topic 來(lái)注冊(cè)的消息
 private Map subscribersByClass = new HashMap();//根據(jù)對(duì)象來(lái)注冊(cè)的消息

3.2 發(fā)送消息

從上文我們可知消息最終是采用publish方法發(fā)出去。

 /**
     * 發(fā)送消息
     *
     * @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())));
    }

可以看出最終均是通過(guò)handler來(lái)處理一個(gè)PublishRunnable,我們下面來(lái)看看該類:

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

繼續(xù)往下查看: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);
            //區(qū)分是主題類型還是對(duì)象類型
            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) {

                }
            }
        }
    }

可以看到該方法是用來(lái)遍歷前面提到的集合,然后再判斷注冊(cè)的類型,最終到達(dá)消息傳遞的功能。

  1. class 注冊(cè)方式來(lái)處理。
  2. topic 注冊(cè)方式來(lái)處理。

3.3 反注冊(cè)

/**
     * 
     * @param cl
     * @param subscriber
     * @return
     */
    public boolean unsubscribe(Class cl, Subscriber subscriber) {
        return unsubscribe(cl, subscribersByClass, subscriber);
    }
/**
     * 反向注冊(cè)
     *
     * @param topic
     * @param subscriber
     * @return
     */
    public boolean unsubscribe(String topic, TopicSubscriber subscriber) {
        return unsubscribe(topic, subscribersByTopic, subscriber);
    }

可以看到最終均是走向unubscribe方法,那么我們來(lái)看看這個(gè)方法

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

可以發(fā)現(xiàn)是走向了removeFromSetResolveWeakReferences 方法,繼續(xù)往下查看

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

整體功能就是判斷類型,依次清理上述說(shuō)的兩個(gè)集合subscribersByTopic和subscribersByClass。
整NotificationCenter 基本使用和邏輯結(jié)構(gòu)基本如此,詳細(xì)的可以參照Demo去研究和拓展。

4 . 總結(jié)

NotificationBus 是根據(jù)項(xiàng)目需求,結(jié)合EventBus來(lái)寫(xiě)的一個(gè)消息訂閱框架,歡迎各位大神來(lái)多多補(bǔ)充和交流。關(guān)于其中個(gè)人覺(jué)得的優(yōu)缺點(diǎn)作出以下幾點(diǎn)簡(jiǎn)單說(shuō)明,歡迎拍磚。
優(yōu)點(diǎn):

  1. 整體采用map來(lái)管理,最終是遍歷集合查找,相對(duì)EventBus采用對(duì)象綁定,然后最終采用反射來(lái)調(diào)用,性能方面會(huì)更好一點(diǎn)。
  2. 沒(méi)有用注解,個(gè)人覺(jué)得使用起來(lái)業(yè)務(wù)邏輯清晰。
  3. 比EventBus更輕量化。

缺點(diǎn):
1 . 沒(méi)有EventBus會(huì)區(qū)分線程來(lái)注冊(cè)和發(fā)布消息,這點(diǎn)不夠全面。
以上是根據(jù)EventBus 的源碼研究和結(jié)合項(xiàng)目需求來(lái)寫(xiě)的,中間會(huì)存在很多問(wèn)題,希望各位大神多多指教。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容