EventBus 核心要點解析

bitbean 8年前發布 | 8K 次閱讀 EventBus Android開發 移動開發

關注EventBus核心要點

###使用流程

register(object)

eventBus.post(event)

舉個簡單例子

基類Activity

public class CommonActivity extends AppCompatActivity {
    protected EMEventBus eventBus;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        eventBus = EMEventBus.getDefault();
        eventBus.register(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        eventBus.unregister(this);
    }

    //EventBus至少有一個onEvent方法
    public void onEvent(String defaultEvent) {

    }

}

登錄頁面只訂閱一個LoginEvent

public class LoginActivity extends CommonActivity {

    public void onEventMainThread(LoginEvent event) {

    }
}

當在另一個頁面 eventBus.post(new LoginEvent()) 的時候發生了什么

  • #register

當LoginActivity頁面register的時候

subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());

先找出所有LoginActivity中訂閱的Event

{
   LoginActivity.onEventMainThread(me.ele.crowdsource.event.LoginEvent)
   CommonActivity.onEvent(java.lang.String)
}

然后for循環生成EventBus中第一重要的映射

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

為什么說這個映射重要,因為他對應著一個Event->List訂閱這個Event的對象,有了這個映射,當你要post(event)的時候,就可以輕松找到所有響應的訂閱者。注意,這個list的類型是 CopyOnWriteArrayList

  CopyOnWriteArrayList是ArrayList 的一個線程安全的變體,其中所有可變操作(add、set等等)都是通過對底層數組進行一次新的復制來實現的。
CopyOnWriteArrayList適合使用在讀操作遠遠大于寫操作的場景里,比如緩存。發生修改時候做copy,新老版本分離,保證讀的高性能,適用于以讀為主的情況。

接著生成了EventBus中第二重要的映射

private final Map

這里EventBus代碼中一個奇怪的習慣,就是把一個list先添加到map,然后再給這個list add數據,這個思路有異常人,比如下面這樣

List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
       if (subscribedEvents == null) {
           subscribedEvents = new ArrayList<Class<?>>();
           typesBySubscriber.put(subscriber, subscribedEvents);
       }
 subscribedEvents.add(eventType);

生成了subscriptionsByEventType和typesBySubscriber 這倆個map,register的任務就結束了。

  • #post

試想如果你來實現EventBus,當Post一個event的時候,怎么搞?很簡單是根據subscriptionsByEventType找到所有訂閱的方法,然后執行。再看EventBus如何實現的,哈哈,也就是這個思路。只是多了個查找Event所有的父類和實現的接口過程.

/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */
   private List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
       synchronized (eventTypesCache) {
           List<Class<?>> eventTypes = eventTypesCache.get(eventClass);
           if (eventTypes == null) {
               eventTypes = new ArrayList<Class<?>>();
               Class<?> clazz = eventClass;
               while (clazz != null) {
                   eventTypes.add(clazz);
                   addInterfaces(eventTypes, clazz.getInterfaces());
                   clazz = clazz.getSuperclass();
               }
               eventTypesCache.put(eventClass, eventTypes);
           }
           return eventTypes;
       }
   }

尋找訂閱的核心實現在這里

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
       CopyOnWriteArrayList<Subscription> subscriptions;
       synchronized (this) {
           subscriptions = subscriptionsByEventType.get(eventClass);
       }
       if (subscriptions != null && !subscriptions.isEmpty()) {
           for (Subscription subscription : subscriptions) {
             ···
            postToSubscription(subscription, event, postingState.isMainThread);
              ···
       }
       return false;
   }

當真正去響應各個訂閱方法的時候,出現了EventBus的核心實現-用

private final HandlerPoster mainThreadPoster;
private final BackgroundPoster backgroundPoster;
private final AsyncPoster asyncPoster;

三個隊列來處理所有的方法。關于這里隊列的介紹之前 總結過一次

有興趣的可以看這里,類似的實現handle中也有,自己的項目中也可以用。

postToSubscription 函數中會判斷訂閱者的 ThreadMode,從而決定在什么 Mode 下執行事件響應函數。ThreadMode 共有四類:

  • PostThread:默認的 ThreadMode,表示在執行 Post 操作的線程直接調用訂閱者的事件響應方法,不論該線程是否為主線程(UI 線程)。當該線程為主線程時,響應方法中不能有耗時操作,否則有卡主線程的風險。適用場景:對于是否在主線程執行無要求,但若 Post 線程為主線程,不能耗時的操作;
  • MainThread:在主線程中執行響應方法。如果發布線程就是主線程,則直接調用訂閱者的事件響應方法,否則通過主線程的 Handler 發送消息在主線程中處理——調用訂閱者的事件響應函數。顯然,MainThread類的方法也不能有耗時操作,以避免卡主線程。適用場景:必須在主線程執行的操作;
  • BackgroundThread:在后臺線程中執行響應方法。如果發布線程不是主線程,則直接調用訂閱者的事件響應函數,否則啟動唯一的后臺線程去處理。由于后臺線程是唯一的,當事件超過一個的時候,它們會被放在隊列中依次執行,因此該類響應方法雖然沒有PostThread類和MainThread類方法對性能敏感,但最好不要有重度耗時的操作或太頻繁的輕度耗時操作,以造成其他操作等待。適用場景:操作輕微耗時且不會過于頻繁,即一般的耗時操作都可以放在這里;
  • Async:不論發布線程是否為主線程,都使用一個空閑線程來處理。和BackgroundThread不同的是,Async類的所有線程是相互獨立的,因此不會出現卡線程的問題。適用場景:長耗時操作,例如網絡訪問。

post方法中關于線程的處理也耐人尋味,尤其是這個currentPostingThreadState

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};

每次post可能在不同的線程,這個時候使用ThreadLocal使得各線程能夠保持各自獨立的一個對象,從源碼可以看出

public T get() { 
       Thread t = Thread.currentThread(); 
       ThreadLocalMap map = getMap(t); 
       if (map != null) { 
           ThreadLocalMap.Entry e = map.getEntry(this); 
           if (e != null) 
               return (T)e.value; 
       } 
       return setInitialValue(); 
   }

這說明ThreadLocal確實只有一個變量,但是它內部包含一個map,針對每個thread保留一個entry,如果對應的thread不存在則會調用initialValue。

再回到EventBus

final static class PostingThreadState {
     final List<Object> eventQueue = new ArrayList<Object>();
     boolean isPosting;
     boolean isMainThread;
     Subscription subscription;
     Object event;
     boolean canceled;
 }

每個線程維護一個eventQueue,每次post就循環的去發送

PostingThreadState postingState = currentPostingThreadState.get();
  List<Object> eventQueue = postingState.eventQueue;
  eventQueue.add(event);
while (!eventQueue.isEmpty()) {
       postSingleEvent(eventQueue.remove(0), postingState);
     }
  • #unregister

unregister思路很簡單,就是清除typesBySubscriber里面這個Activity的記錄和subscriptionsByEventType中對應的記錄,但是這里有個刪除list中元素的技巧很有趣。因為之前兆軒剛在上面踩過坑,看EventBus如何刪除subscriptionsByEventType中一個event對應的訂閱list中某個記錄。

private void unubscribeByEventType(Object subscriber, Class<?> eventType) {
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

重點就是i–和size–,當命中的時候通過–讓游標回退一位,size也減一,這樣就可以讓剩下的元素也正常循環完成。

到此為止,EventBus我認為比較有意思的地方就都列出來了,還是學到了蠻多的東西。

 

來自: http://xujinyang.github.io/2016/05/24/EventBus-核心要點解析/

 

 本文由用戶 bitbean 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!