淺析EventBus 3.0實現思想
最近接觸了EventBus,也看了一些源碼分析的文章。在此就不再細述其代碼的實現細節,主要針對其的設計思想做一些記錄,也是自己思考的過程。同時本文盡量以較少的代碼來將其主要設計思想說的透徹明白,不會針對細節做過多深入。
基本的事件發布訂閱的實現
一般情況下,事件發布訂閱機制都是跟觀察者模式緊密相連。事件的發布中心都會維持著一組當前的觀察者(也可叫做訂閱者),這里稱之為事件總線,(觀察者的注冊/取消則對應著在這組數據中進行添加和刪除)。另外被觀察者(也可叫發布者)則通過發出事件,事件總線拿到該事件,則在觀察者列表中根據事件來查找相應的事件觀察者,緊接著執行觀察者的行為即可。對應一個簡單的事件總線圖如下:
EventBus 3.0的實現
1. EventBus的基本使用
- 發布事件:
EventBus.post(Object event);
- 訂閱事件:
@Subscribe public void subscribe(Object event){ }
- 訂閱事件的注冊以及取消注冊:
EventBus.register(Object); EventBus.unregister(Object);
由上可見,EventBus的使用,還是相當簡單的。其中添加了 @subscribe 注解的方法,則代表著真正的事件訂閱者,另外,添加了注解的方法,必須通過 register 和 unregister 來進行訂閱和取消訂閱,它倆相應的參數 Object 則指代的是 @subscribe 方法所在的類。 另可看出,事件訂閱參數 Object event ,即是我們所在事件總線中傳遞使用事件的參數。當我們要確定這個事件被接受到,則需要保持event的 class 是相同的。
2. EventBus的事件總線
首先需要明白的是在EventBus中,真正對應的訂閱對象是 SubscriberMethod ,其包含了相應的 Method 類,以及事件參數類型 Class<?> eventType ,其他就是線程,優先級,是否Sticky信息。
/** Used internally by EventBus and generated subscriber indexes. */ public class SubscriberMethod { final Method method; final ThreadMode threadMode; final Class<?> eventType; final int priority; final boolean sticky; }
緊接著,談及事件總線,一般都對應著一個集合,而EventBus中使用的是:
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
這里的Map集合采用的是一個HashMap集合,map的key對應就是之前 SubscriberMethod 中的 eventType , value則對應著一個線程安全的List,List中存放的是包含訂閱對象 Object 及相應訂閱方法 SubscriberMethod 的 Subscription 類:
final class Subscription { final Object subscriber; final SubscriberMethod subscriberMethod; }
現在,就很明了了,List數組用來維持所有當前的訂閱者,即對應著我們經常所說的事件總線中的集合。
3. EventBus的事件訂閱/消費
- EventBus中的訂閱與取消訂閱 –> register / unregister
1) 訂閱類信息的封裝: EventBus的 register 方法,則會將參數 Object類中 @subscribe 注解的所有方法,逐一封裝為 SubscriberMethod ,再與參數 subscriber 統一封裝為 Subscription ,這樣因注解方法可以多個,而通過訂閱類的register的方法,最后得到的將是一組訂閱者。
2) 訂閱者的添加: EventBus類會將這組訂閱者,根據不同的 eventType 參數,將放置在上文提到的 subscriptionsByEventType 結構中。這樣,一個事件中心的機制就完成了,注冊事件時,就在List中添加方法訂閱者;取消注冊事件,同時也是針對這個List中移除的訂閱類Object對象中相應的 subscription 。
- EventBus事件的消費/發布事件 這里通過EventBus的 post(Object event) 方法,進行事件的發出。緊接著EventBus的總線LIst中找出訂閱了這個event的方法 Subscription ,然后根據method指定的不同線程信息,將這個方法的調用,放置在相應線程中調用:
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
可以看出,訂閱者已經被封裝地非常完美,這樣,我們在使用不同的線程調度策略就很簡單了,隨意指定一個 ThreadMode 即可在指定線程中調用。唯一不完美的地方,就是這里的調用時通過 Method 類調用的,肯定沒有直接通過類調用來的直接,不過相對其帶我們的好處來說,這點性能影響可以忽略不計了。
4. 優化之EventBusIndex
到這里,會發現有個很大的疑問: SubscriberMethod 信息是怎么生成的?答案很肯定的嘛,就是加了注解 @subscribe 的方法嘛。我們可以通過在運行時通過采用反射的方法,獲取相應添加了注解的方法,再封裝成為 SubscriberMethod 。而對我們開發者來說,使用反射帶來的性能消耗,不由得我們不慎重一二的。 在3.0的版本,EventBus加入了apt處理的邏輯,有個 Subscriber Index 的介紹,主要是通過Apt在編譯期根據注解直接生成相應的信息,來避免在運行時通過反射來獲取。使用方法如下:
apt { arguments { eventBusIndex "com.example.myapp.MyEventBusIndex" } }
配置Index的調用如下:
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
之后,我們在執行了代碼的編譯之后,會生成一個包名為 com.example.myapp 的類 MyEventBusIndex 。這個類會實現一個如下的接口:
public interface SubscriberInfoIndex { SubscriberInfo getSubscriberInfo(Class<?> subscriberClass); }
從接口中可以看出,這個Index類只提供了一個方法 getSubsriberInfo ,這個方法需要我們傳入訂閱者所在的class類,然后獲得一個 SubscriberInfo 的類,其類結構如下:
public interface SubscriberInfo { Class<?> getSubscriberClass(); SubscriberMethod[] getSubscriberMethods(); SubscriberInfo getSuperSubscriberInfo(); boolean shouldCheckSuperclass(); }
從接口中暴露的方法,即可獲取所需的所有 SubscriberMethod 信息。而這只是一個獲取單個類的方法,而apt生成的EventBusIndex中,會將所有的這些class及SubscriberInfo保存在靜態變量hashMap結構,這樣就達到了避免運行期反射獲取生成訂閱方法的性能問題。而變成另外一個流程:訂閱類在注冊的時候,直接通過HashMap中的訂閱類,獲取到 SubscriberInfo ,進而獲取到所有的 SubscerberMethod ,并封裝為 Subscription ,被添加到事件總線中。這樣,在引入了apt之后,EventBus的性能問題就不需要我們擔心了。(PS:在EventBus作者的博客也提及到了這一點,使用apt了的EventBus在性能表現這方便,猶如打雞血一般。)
總結
EventBus將訂閱者巧妙地轉換為通過注解 @subscriber 定義的方法,方法的參數定義為事件所使用的數據類型,另外,可以指定訂閱者不同的線程及優先級,給我們開發者帶來最大的好處就是使用非常簡單方便。這就不得不提及一些不好的地方了,可以看到的是其在設計實現的過程中,引入了許多中間對象,若是我們對內存使用及性能非常敏感的話,則必須自己實現一個更加輕量級的事件總線了。
后續
因這是一篇簡單的介紹EventBus3.0設計思想實現的文章,但在查看其源碼的過程中,也發現了其他很有意思的技術實現細節及線程處理等內容,將在下一篇中做介紹,歡迎關注。
來自: http://alighters.com/blog/2016/05/22/eventbus3-dot-0-analyze/