閱讀622 返回首頁    go 技術社區[雲棲]


EventBus用法及源碼解析

EventBus用法及源碼解析

  • 目錄介紹
  • 1.EventBus簡介
  • 1.1 EventBus的三要素
  • 1.2 EventBus的四種ThreadMode(線程模型)
  • 1.3 EventBus怎麼調用
  • 2.EventBus使用
  • 2.1 最簡單的使用
  • 3.EventBus注冊源碼解析
  • 3.1 EventBus.getDefault()獲取對象
  • 3.2 register(this)注冊源碼解析
  • 3.2.1 首先看register(this)源碼
  • 3.2.2 接下來看findSubscriberMethods(subscriberClass)裏麵的源碼
  • 3.2.3 接下來看findUsingInfo(subscriberClass)源碼
  • 3.3 查找完所有的訂閱方法後便開始對所有的訂閱方法進行注冊
  • 3.3.1 subscribe(subscriber, subscriberMethod);
  • 3.3.2 訂閱者的注冊過程
  • 3.3.3 流程圖
  • 4.EventBus事件分發解析
  • 4.1 從post方法入手
  • 4.2 什麼是PostingThreadState?
  • 4.3 PostingThreadState怎麼獲得?
  • 4.4 來看看postSingleEvent方法裏做了什麼
  • 4.5 接下來看看postSingleEventForEventType方法
  • 4.6 接下來看看postToSubscription方法
  • 4.7 整個流程圖
  • 4.8 總結一下整個事件分發的過程
  • 5.EventBus取消注冊解析
  • 5.1 unregister(this)方法入手
  • 5.2 再來看看unsubscribeByEventType(subscriber, eventType)
  • 5.3 取消注冊流程圖
  • 5.4 總結一下取消注冊的過程
  • 6.總結一下EventBus的工作原理
  • 6.1 訂閱邏輯
  • 6.2 事件發送邏輯
  • 6.3 取消邏輯
  • 6.4 利與弊
  • 7.其他介紹
  • 7.1 參考文檔
  • 7.2 其他

0.本人寫的綜合案例

1.EventBus簡介

  • 1.1 EventBus的三要素
    • Event:事件 可以是任意類型的對象。
    • Subscriber:事件訂閱者 在EventBus3.0之前,消息處理的方法隻能限定於onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,他們分別代表四種線程模型。 在EventBus3.0之後,事件處理的方法可以隨便取名,但是需要添加一個注解@Subscribe,並且要指定線程模型(默認為POSTING),四種線程模型下麵會講到。
    • Publisher:事件發布者 可以在任意線程任意位置發送事件,直接調用EventBus的post(Object)方法。可以自己實例化EventBus對象,但一般使用EventBus.getDefault()就好了,根據post函數參數的類型,會自動調用訂閱相應類型事件的函數。
  • 1.2 EventBus的四種ThreadMode(線程模型)
    • POSTING(默認): 如果使用事件處理函數指定了線程模型為POSTING,那麼該事件在哪個線程發布出來的,事件處理函數就會在這個線程中運行,也就是說發布事件和接收事件在同一個線程。在線程模型為POSTING的事件處理函數中盡量避免執行耗時操作,因為它會阻塞事件的傳遞,甚至有可能會引起ANR。
    • MAIN: 事件的處理會在UI線程中執行。事件處理時間不能太長,長了會ANR的。
    • BACKGROUND: 如果事件是在UI線程中發布出來的,那麼該事件處理函數就會在新的線程中運行,如果事件本來就是子線程中發布出來的,那麼該事件處理函數直接在發布事件的線程中執行。在此事件處理函數中禁止進行UI更新操作。
    • ASYNC: 無論事件在哪個線程發布,該事件處理函數都會在新建的子線程中執行,同樣,此事件處理函數中禁止進行UI更新操作。
  • 1.3 EventBus怎麼調用** 代碼如下: EventBus.getDefault().post(param);
    • 調用原理簡單理解為:
    • 一句話,你也可以叫發布,隻要把這個param發布出去,EventBus會在它內部存儲的方法中,進行掃描,找到參數匹配的,就使用反射進行調用。
    • 撇開專業術語:其實EventBus就是在內部存儲了一堆onEvent開頭的方法,然後post的時候,根據post傳入的參數,去找到匹配的方法,反射調用之。
    • 它內部使用了Map進行存儲,鍵就是參數的Class類型。知道是這個類型,那麼你覺得根據post傳入的參數進行查找還是個事麼?

2.EventBus使用

  • 2.1 最簡單的使用
  • 2.1.1 自定義一個事件類 public class MessageEvent { }
  • 2.1.2 在需要訂閱事件的地方注冊事件

    EventBus.getDefault().register(this);
    
  • 2.1.3 發送事件

    EventBus.getDefault().post(messageEvent);
    
  • 2.1.4 處理事件

    • 3.0之後, 消息處理的方法可以隨便取名 問題: (threadMode = ThreadMode.MAIN)是做什麼用的?? 需要添加一個注解@Subscribe,並且要指定線程模型 如果沒有添加,那就是默認為POSTING @Subscribe(threadMode = ThreadMode.MAIN) public void Hhhh(MessageEvent messageEvent) { ... }
  • 2.1.5 取消事件訂閱

    EventBus.getDefault().unregister(this);
    

3.EventBus注冊源碼解析

  • 3.1 EventBus.getDefault()獲取對象
  • a.先看源碼:

    public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
    }
    
  • b.分析
    單例模式, 使用了雙重判斷的方式,防止並發的問題,還能極大的提高效率

  • 3.2 register(this)注冊源碼解析

  • 3.2.1.首先看register(this)源碼

    public void register(Object subscriber) {
    //首先獲取訂閱者的類對象
    Class<?> subscriberClass = subscriber.getClass();
    //用 subscriberMethodFinder 提供的方法,找到在 subscriber 這個類裏麵訂閱的內容。
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        //
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
    }
    
  • 3.2.2.接下來看findSubscriberMethods(subscriberClass)裏麵的源碼

    • 該方法的作用其實就是從訂閱類中獲取所有的訂閱方法信息
    • findSubscriberMethods找出一個SubscriberMethod的集合,也就是傳進來的訂閱者所有的訂閱的方法,接下來遍曆訂閱者的訂閱方法來完成訂閱者的訂閱操作。對於SubscriberMethod(訂閱方法)類中,主要就是用保存訂閱方法的Method對象、線程模式、事件類型、優先級、是否是粘性事件等屬性。
    • 源碼分析:首先從緩存中查找,如果找到了就立馬返回。如果緩存中沒有的話,則根據 ignoreGeneratedIndex 選擇如何查找訂閱方法,ignoreGeneratedIndex屬性表示是否忽略注解器生成的MyEventBusIndex。最後,找到訂閱方法後,放入緩存,以免下次繼續查找。ignoreGeneratedIndex 默認就是false,可以通過EventBusBuilder來設置它的值。我們在項目中經常通過EventBus單例模式來獲取默認的EventBus對象,也就是ignoreGeneratedIndex為false的情況,這種情況調用了findUsingInfo方法 List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { //首先從緩存中讀取 List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } //是否忽略注解器生成的MyEventBusIndex類 if (ignoreGeneratedIndex) { //利用反射來獲取訂閱類中的訂閱方法信息 subscriberMethods = findUsingReflection(subscriberClass); } else { //從注解器生成的MyEventBusIndex類中獲得訂閱類的訂閱方法信息 subscriberMethods = findUsingInfo(subscriberClass); } //在獲得subscriberMethods以後,如果訂閱者中不存在@Subscribe注解並且為public的訂閱方法,則會拋出異常。 if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { //保存進緩存 METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } } //METHOD_CACHE,是一個map集合,鍵是class類型 Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
  • 3.2.3 接下來看findUsingInfo(subscriberClass)源碼

    • 通過getSubscriberInfo方法來獲取訂閱者信息。在我們開始查找訂閱方法的時候並沒有忽略注解器為我們生成的索引MyEventBusIndex,如果我們通過EventBusBuilder配置了MyEventBusIndex,便會獲取到subscriberInfo,調用subscriberInfo的getSubscriberMethods方法便可以得到訂閱方法相關的信息,這個時候就不在需要通過注解進行獲取訂閱方法。如果沒有配置MyEventBusIndex,便會執行findUsingReflectionInSingleClass方法,將訂閱方法保存到findState中。最後再通過getMethodsAndRelease方法對findState做回收處理並反回訂閱方法的List集合。 private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { //獲取訂閱者信息,沒有配置MyEventBusIndex返回null findState.subscriberInfo = getSubscriberInfo(findState); if (findState.subscriberInfo != null) { SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); for (SubscriberMethod subscriberMethod : array) { if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); } } } else { //通過反射來查找訂閱方法 findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }
  • 3.3 查找完所有的訂閱方法後便開始對所有的訂閱方法進行注冊

  • 3.3.1 回到
    register(this)這個方法
    Image.png

  • 3.3.2 訂閱者的注冊過程

    • 訂閱的代碼主要就做了兩件事,第一件事是將我們的訂閱方法和訂閱者封裝到subscriptionsByEventType和typesBySubscriber中,subscriptionsByEventType是我們投遞訂閱事件的時候,就是根據我們的EventType找到我們的訂閱事件,從而去分發事件,處理事件的;typesBySubscriber在調用unregister(this)的時候,根據訂閱者找到EventType,又根據EventType找到訂閱事件,從而對訂閱者進行解綁。第二件事,如果是粘性事件的話,就立馬投遞、執行。 private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { //獲取訂閱方法的參數類型 Class<?> eventType = subscriberMethod.eventType; //根據訂閱者和訂閱方法構造一個訂閱事件 Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //獲取當前訂閱事件中Subscription的List集合 CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //該事件對應的Subscription的List集合不存在,則重新創建並保存在subscriptionsByEventType中 if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else { //訂閱者已經注冊則拋出EventBusException if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } //遍曆訂閱事件,找到比subscriptions中訂閱事件小的位置,然後插進去 int size = subscriptions.size(); for (int i = 0; i <= size; i++) { if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { subscriptions.add(i, newSubscription); break; } } //通過訂閱者獲取該訂閱者所訂閱事件的集合 List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } //將當前的訂閱事件添加到subscribedEvents中 subscribedEvents.add(eventType); if (subscriberMethod.sticky) { if (eventInheritance) { //粘性事件的處理 Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet(); for (Map.Entry<Class<?>, Object> entry : entries) { Class<?> candidateEventType = entry.getKey(); if (eventType.isAssignableFrom(candidateEventType)) { Object stickyEvent = entry.getValue(); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } }
  • 3.3.3 流程圖
    Image.png

4.EventBus事件分發解析

  • 4.1 從post方法入手
    首先從PostingThreadState對象中取出事件隊列,然後再將當前的事件插入到事件隊列當中。最後將隊列中的事件依次交由postSingleEvent方法進行處理,並移除該事件。

    /* Posts the given event to the event bus. /
    public void post(Object event) {
    //獲取當前線程的postingState
    PostingThreadState postingState = currentPostingThreadState.get();
    //取得當前線程的事件隊列
    List<Object> eventQueue = postingState.eventQueue;
    //將該事件添加到當前的事件隊列中等待分發
    eventQueue.add(event);
    if (!postingState.isPosting) {
        //判斷是否是在主線程post
        postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            while (!eventQueue.isEmpty()) {
                //分發事件
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
    }
    
  • 4.2 什麼是PostingThreadState?
    PostingThreadState中包含了當前線程的事件隊列,就是當前線程所有分發的事件都保存在eventQueue事件隊列中以及訂閱者訂閱事件等信息,有了這些信息我們就可以從事件隊列中取出事件分發給對應的訂閱者

    final static class PostingThreadState {
    final List<Object> eventQueue = new ArrayList<Object>();//當前線程的事件隊列
    boolean isPosting;//是否有事件正在分發
    boolean isMainThread;//post的線程是否是主線程
    Subscription subscription;//訂閱者
    Object event;//訂閱事件
    boolean canceled;//是否取消
    }
    
  • 4.3 PostingThreadState怎麼獲得?

    • ThreadLocal 是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,而這段數據是不會與其他線程共享的。
    • 可以看出currentPostingThreadState的實現是一個包含了PostingThreadState的ThreadLocal對象,這樣可以保證取到的都是自己線程對應的數據。
    • 我們有了PostingThreadState獲取到了當前線程的事件隊列,接下來就是事件分發,我們來看postSingleEvent(eventQueue.remove(0), postingState); private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() { @Override protected PostingThreadState initialValue() { return new PostingThreadState(); } };
  • 4.4 來看看postSingleEvent方法裏做了什麼
    eventInheritance表示是否向上查找事件的父類,它的默認值為true,可以通過在EventBusBuilder中來進行配置。當eventInheritance為true時,則通過lookupAllEventTypes找到所有的父類事件並存在List中,然後通過postSingleEventForEventType方法對事件逐一處理,接下來看看postSingleEventForEventType方法

    事件分發
    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    //得到事件類型
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    
    //是否觸發訂閱了該事件(eventClass)的父類,以及接口的類的響應方法.
    if (eventInheritance) {
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
    }
    
  • 4.5 接下來看看postSingleEventForEventType方法
    同步取出該事件對應的Subscription集合並遍曆該集合將事件event和對應Subscription傳遞給postingState並調用postToSubscription方法對事件進行處理,接下來看看postToSubscription方法:

    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) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                //對事件進行處理
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
    }
    
  • 4.6 接下來看看postToSubscription方法

    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING://默認的 ThreadMode,表示在執行 Post 操作的線程直接調用訂閱者的事件響應方法,
        //不論該線程是否為主線程(UI 線程)。
            invokeSubscriber(subscription, event);
            break;
        case MAIN://在主線程中執行響應方法。
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case BACKGROUND://在後台線程中執行響應方法。
            if (isMainThread) {
                backgroundPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC://不論發布線程是否為主線程,都使用一個空閑線程來處理。
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
    }
    
  • 4.7 整個流程圖
    511825-20160330182550879-762466444.png

  • 4.8 總結一下整個事件分發的過程

    • 首先獲取當前線程的PostingThreadState對象從而獲取到當前線程的事件隊列
    • 通過事件類型獲取到所有訂閱者集合
    • 通過反射執行訂閱者中的訂閱方法

5.EventBus取消注冊解析

  • 5.1 unregister(this)方法入手

    /* Unregisters the given subscriber from all event classes. /
    public synchronized void unregister(Object subscriber) {
    //獲取訂閱者的所有訂閱的事件類型
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            //從事件類型的訂閱者集合中移除訂閱者
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
    }
    
  • 5.2 再來看看unsubscribeByEventType(subscriber, eventType)

    private void unsubscribeByEventType(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--;
            }
        }
    }
    }
    
  • 5.3 取消注冊流程圖
    Image.png

  • 5.4 總結一下取消注冊的過程

    • 1、首先獲取訂閱者的所有訂閱事件
    • 2、遍曆訂閱事件
    • 3、根據訂閱事件獲取所有的訂閱了該事件的訂閱者集合
    • 4、將該訂閱者移除
    • 5、將步驟1中的集合中的訂閱者移除

6.總結一下EventBus的工作原理

  • 6.1 訂閱邏輯
    • 1、首先用register()方法注冊一個訂閱者
    • 2、獲取該訂閱者的所有訂閱的方法
    • 3、根據該訂閱者的所有訂閱的事件類型,將訂閱者存入到每個以 事件類型為key 以所有訂閱者為values的map集合中
    • 4、然後將訂閱事件添加到以訂閱者為key 以訂閱者所有訂閱事件為values的map集合中
    • 5、如果是訂閱了粘滯事件的訂閱者,從粘滯事件緩存區獲取之前發送過的粘滯事件,響應這些粘滯事件。
  • 6.2 事件發送邏輯
    • 1、首先獲取當前線程的事件隊列
    • 2、將要發送的事件添加到事件隊列中
    • 3、根據發送事件類型獲取所有的訂閱者
    • 4、根據響應方法的執行模式,在相應線程通過反射執行訂閱者的訂閱方法
  • 6.3 取消邏輯
    • 1、首先通過unregister方法拿到要取消的訂閱者
    • 2、得到該訂閱者的所有訂閱事件類型
    • 3、遍曆事件類型,根據每個事件類型獲取到所有的訂閱者集合,並從集合中刪除該訂閱者
    • 4、將訂閱者從步驟2的集合中移除
  • 6.4 利與弊
    • EventBus好處比較明顯,它能夠解耦和,將業務和視圖分離,代碼實現比較容易。而且3.0後,我們可以通過apt預編譯找到訂閱者,避免了運行期間的反射處理解析,大大提高了效率。當然EventBus也會帶來一些隱患和弊端,如果濫用的話會導致邏輯的分散並造成維護起來的困難。另外大量采用EventBus代碼的可讀性也會變差。

7.其他介紹

最後更新:2017-11-15 13:34:01

  上一篇:go  獨家 | PHM數據競賽首個中國奪冠團隊經驗分享(常用模型&賽題詳解&PPT&視頻)
  下一篇:go  北大研討“天貓雙11”現象:新零售引擎,激發全球商業史上最大規模社會協同