EventBus vs Otto vs Guava--自定義消息總線


同步發表於http://avenwu.net/ioc/2015/01/29/custom_eventbus

Fork on github https://github.com/avenwu/support

Android有廣播和Receiver可以處理消息的傳遞和響應,要進行消息-發布-訂閱,除此之外作為開發者現在也有其他類似的方案可以選擇,比如EventBus和Otto,都是比較熱門的三方庫。那么這些三方庫到底是怎么實現模塊之間的解耦,使得消息可以再不同的系統組件之間傳遞呢?

源碼剖析

由於是開源的,完全可以通過分析源代碼來了解這些個消息-發布-訂閱方案在Android內是怎么實現的,下面分別針對EventBus, Otto,Guava簡單分析。

EventBus v2.4.0

從github上獲取最新的EventBus代碼

git clone git@github.com:greenrobot/EventBus.git

直接找到EventBus這個類,從使用角度開始分析:

EventBus的使用可以參考項目wiki,簡單的來說就是EventBus#register(),EventBus#post(),實現onEventXXX
  • EventBus利用反射技術
  • register時遍歷訂閱者,通過反射獲取所有public void onEventXXX(XX)方法,構造對應的訂閱實例,方便后期post時invoke方法【SubscriberMethodFinder】
  • post后通過EventBus#postToSubscription分發至對應線程的事件控制器

Otto v1.3.6

Otto是大名鼎鼎的Square開發的

git clone git@github.com:square/otto.git
  • Otto利用反射和注解
  • register時遍歷訂閱者內的所有方法,根據Subscribe和Produce注解獲得所有目標方法,@Subscribe public void xxx(YYY);訂閱方法必須是public且只有一個參數
  • post后在EventHandler#handleEvent內invoke事件,這里沒有EventBus中的線程區分,默認是MainThread,也可以任何線程ANY,但是必須是一致的ThreadEnforcer

從代碼上來看EventBus和Otto非常像,不知道EventBus作者在設計編碼時是否參考了Otto得設計,Otto項目則明確表示其是基於Google的Guava而來,Guava是Google開發的一個工具類庫包含了非常多的實用工具類,其中就有一個EventBus模塊,但是這個EventBus是並沒有針對Android平台做線程方面的考量。

所以三者的是有關聯的:

  • Guava EventBus首先實現了一個基於發布訂閱的消息類庫,默認以注解來查找訂閱者
  • Otto借鑒Guava EventBus,針對Android平台做了修改,默認以注解來查找訂閱、生產者
  • EventBus和前兩個都很像,v2.4后基於反射的命名約定查找訂閱者,根據其自己的說法,效率上優於Otto,當然我們測試過,這也不是本文的重點。

由於源代碼也不少,所以只列舉了核心代碼對應的位置,感興趣的童鞋肯定會自己去研讀。

自定義一個EventBus

上面的庫當然不是為了研究而研究,現在理解了他們的核心思路后,我們其實已經可以着手自己寫一個簡單版的消息-發布-訂閱
現在先定義要實現的程度:

  • 基於UI線程的消息-發布-訂閱
  • 使用上合EventBus盡量保持一致,比如register,post,onEvent

思路設計

一個簡單的Bus大致上需要有幾個東西,Bus消息中心,負責綁定/解綁,發布/訂閱;Finder查找定義好的消息處理方法;PostHandler分發消息並處理.

bus_structure

Bus實現

這里的Bus做成單例,這樣無論在什么地方注冊,發布都是有這個消息中心來處理。
用一個Map來保存我們的訂閱關系,當消息到達時從map中取出該消息類型的所有訂閱方法,通過反射依次invoke。

public class Bus {

    static volatile Bus sInstance;

    Finder mFinder;

    Map<Class<?>, CopyOnWriteArrayList<Subscriber>> mSubscriberMap;

    PostHandler mPostHandler;

    private Bus() {
        mFinder = new NameBasedFinder();
        mSubscriberMap = new HashMap<>();
        mPostHandler = new PostHandler(Looper.getMainLooper(), this);
    }

    public static Bus getDefault() {
        if (sInstance == null) {
            synchronized (Bus.class) {
                if (sInstance == null) {
                    sInstance = new Bus();
                }
            }
        }
        return sInstance;
    }

    public void register(Object subscriber) {
        List<Method> methods = mFinder.findSubscriber(subscriber.getClass());
        if (methods == null || methods.size() < 1) {
            return;
        }
        CopyOnWriteArrayList<Subscriber> subscribers = mSubscriberMap.get(subscriber.getClass());
        if (subscribers == null) {
            subscribers = new CopyOnWriteArrayList<>();
            mSubscriberMap.put(methods.get(0).getParameterTypes()[0], subscribers);
        }
        for (Method method : methods) {
            Subscriber newSubscriber = new Subscriber(subscriber, method);
            subscribers.add(newSubscriber);
        }
    }

    public void unregister(Object subscriber) {
        CopyOnWriteArrayList<Subscriber> subscribers = mSubscriberMap.remove(subscriber.getClass());
        if (subscribers != null) {
            for (Subscriber s : subscribers) {
                s.mMethod = null;
                s.mSubscriber = null;
            }
        }
    }

    public void post(Object event) {
        //TODO post with handler
        mPostHandler.enqueue(event);
    }
}

Finder

查找訂閱方法即可以用注解,也可以用命名約定,這里先實現命名約定的方式。
為了處理方便這里和EventBus不完全一致,只做了方法名和參數的限制,但是最好實現的嚴謹些。

public class NameBasedFinder implements Finder {

    @Override
    public List<Method> findSubscriber(Class<?> subscriber) {
        List<Method> methods = new ArrayList<>();
        for (Method method : subscriber.getDeclaredMethods()) {
            if (method.getName().startsWith("onEvent") && method.getParameterTypes().length == 1) {
                methods.add(method);
                Log.d("findSubscriber", "add method:" + method.getName());
            }
        }
        return methods;
    }
}

PostHandler

分發消息肯定要用到Handler,EventBus中自己維護了一個隊列來來處理消息的入棧、出棧,我這里就世界用了Message來傳遞

public class PostHandler extends Handler {

    final Bus mBus;

    public PostHandler(Looper looper, Bus bus) {
        super(looper);
        mBus = bus;
    }

    @Override
    public void handleMessage(Message msg) {
        CopyOnWriteArrayList<Subscriber> subscribers = mBus.mSubscriberMap.get(msg.obj.getClass());
        for (Subscriber subscriber : subscribers) {
            subscriber.mMethod.setAccessible(true);
            try {
                subscriber.mMethod.invoke(subscriber.mSubscriber, msg.obj);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    void enqueue(Object event) {
        Message message = obtainMessage();
        message.obj = event;
        sendMessage(message);
    }
}

小結

基本上的代碼都在這里,實現一個Bus還是挺簡單的,當然如果吧各種情況都考慮進去就會變得復雜一些,比如支持多線程線程,也不可能想本文這樣區區數百行代碼就搞定。
感興趣的可以到這里獲取上面自定義bus的源代碼:https://github.com/avenwu/support/tree/master/support/src/main/java/net/avenwu/support


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM