EventBus源碼解析(三)-注冊

一、注冊主要流程

EventBus的注冊代碼如下:

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

register方法主要做了三件事:

  1. 獲取訂閱者類的class對象
  2. 根據class對象查找對應的訂閱者類的所有訂閱方法
  3. 執(zhí)行訂閱

二、SubscriberMethodFinder

查找訂閱方法時,調用了SubscriberMethodFinder類的findSubscriberMethods方法,并將訂閱者類的class對象作為實參傳遞進去。那么SubscriberMethodFinder是什么呢?

SubscriberMethodFinder類,顧名思義,它就是一個訂閱者方法查找器。在它的內部有一個很重要的成員變量METHOD_CACHE,即訂閱者方法緩存,它是線程安全的HashMap,鍵是訂閱者類的class對象,具備唯一性;值是訂閱方法(添加了@Subscribe注解的方法)集合。

private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();

查找對應的訂閱者類的訂閱方法時,執(zhí)行了如下邏輯:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        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;
        }
    }

這部分代碼邏輯很清晰,按步驟分別作了以下操作:

  1. 根據訂閱者class對象到METHOD_CACHE緩存中查找對應的訂閱方法集合
  2. 如果訂閱方法集合非空,直接返回;否則繼續(xù)向下執(zhí)行
  3. 如果不在編譯時使用索引技術,則跳轉findUsingReflection,使用反射查找
  4. 如果在編譯時使用索引技術,則跳轉findUsingInfo,使用索引查找
  5. 將查找到的集合存入METHOD_CACHE緩存

對于findUsingReflection和findUsingInfo,在這一章不去深究,我們的主線仍然是注冊。


三、SubscriberMethod

那么訂閱方法SubscriberMethod是什么呢?其實它對應的就是我們在代碼中使用@Subscribe注解標記的public方法。

public class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    final int priority;
    final boolean sticky;

    ...

}

回想一下,我們在使用EventBus時,訂閱方法的常規(guī)寫法,比如下面這種形式:

@Subscribe(threadMode = ThreadMode.MainThread,sticky=true)
public void subscribe(Event event) {
}

對應到SubscriberMethod 成員,是不是一路了然?method代表訂閱方法,threadMode代表訂閱方法的執(zhí)行線程,eventType代表事件類型,priority代表事件優(yōu)先級,sticky代表事件是否粘性。


四、訂閱

回到第一小節(jié),查找到訂閱方法集合之后,需要遍歷集合,對每個訂閱者和訂閱方法執(zhí)行subscribe訂閱操作。

subscribe的代碼邏輯比較多,但其實也很清晰,我們將該方法完整地(不省略任何代碼)拆分成3個小方法,依次分析。即:

//該代碼是偽代碼
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        subscribe1(subscriber,subscriberMethod);
        subscribe2(subscriber,subscriberMethod);
        subscribe3(subscriber,subscriberMethod);
}
4.1
private void subscribe1(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

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

分析這部分代碼之前,我們首先需要了解Subscription類和subscriptionsByEventType變量。

4.1.1 Subscription

Subscription的內部結構如下:

    final Object subscriber;
    final SubscriberMethod subscriberMethod;
    volatile boolean active;

該類和第二小節(jié)提到的METHOD_CACHE很相似,但又不同。METHOD_CACHE記錄的是訂閱者類中的所有訂閱方法,是一對多的關系;而Subscription記錄的是訂閱者類的某個具體的訂閱方法,是一對一的關系。該類中的active布爾值當注冊解除時被置位成false。我們可以把該類理解成訂閱信息。

4.1.2 subscriptionsByEventType

subscriptionsByEventType在該系列文章的第二篇中的結束語中有提及,但沒有深究。subscriptionsByEventType其實是一個依據事件類型來存儲訂閱者及訂閱方法信息的數據結構。subscriptionsByEventType在EventBus類中的定義是這樣的:

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

subscriptionsByEventType也是一個Map結構,鍵是一個事件類型的class對象,值則是一個線程安全的訂閱信息集合。也許會有讀者問:為什么鍵是一個“事件類型”的class對象,而不是其他呢?我們可以從subscribe1的如下代碼中推敲而出(更直觀地,也可以通過命名得知):

CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);

看到沒有,get方法里的實參是一個事件類型的class對象,由此得出以上結論。

4.1.3 subscribe1流程分析

分析完Subscription和subscriptionsByEventType后,我們梳理一下subscribe1的代碼邏輯:

  1. 將訂閱者與某個具體的訂閱方法包裝成一個新的訂閱信息對象newSubscription
  2. 根據事件類型,從subscriptionsByEventType中取出訂閱信息集合subscriptions
  3. 如果訂閱信息集合為null,說明是首次訂閱該類事件,直接創(chuàng)建一個空的訂閱信息集合,并存入subscriptionsByEventType
  4. 如果訂閱信息集合不是null,則說明此前已經有訂閱過該類事件的歷史,對訂閱信息集合進行contains判斷,如果該集合中存在newSubscription,則說明訂閱者重復訂閱了同一類事件,拋出異常
  5. 將訂閱信息newSubscription按照priority優(yōu)先級存入訂閱信息集合subscriptions中

也許會有讀者有疑問,newSubscription明明每次都是被重新new出來的,對象地址勢必都不同,這樣在執(zhí)行subscriptions.contains(newSubscription)不就一定是返回false嗎?這樣不就永遠也不會拋出這個異常了?但在實際使用中,的確是有很多讀者都遇到過這個異常信息。

EventBus之所以敢這樣寫,是因為它重寫了Subscription的equals和hashCode方法,有興趣的讀者可以自行翻閱。

4.2
private void subscribe2(Object subscriber, SubscriberMethod subscriberMethod) {
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
}

依照慣例,我們依然需要了解一下typesBySubscriber變量的作用。typesBySubscriber也是該系列第二章中結束語時提到的一個關鍵變量,它在EventBus中的定義如下:

private final Map<Object, List<Class<?>>> typesBySubscriber;

typesBySubscriber也是一個Map結構,鍵是訂閱者類的class對象,值是該訂閱類已經訂閱的事件類型的集合。

知道了這個變量的作用以后,subscribe2的邏輯理解起來就很簡單了。

  1. 根據訂閱者,從typesBySubscriber集合中取出已經訂閱的事件類型集合subscribedEvents
  2. 如果subscribedEvents 為null,說明還沒有訂閱任何事件。創(chuàng)建一個空的事件類型集合,并存入typesBySubscriber中
  3. 將事件類型的class對象加入事件類型集合中
4.3
private void subscribe3(Object subscriber, SubscriberMethod subscriberMethod) {
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);
            }
        }
}

subscribe3主要是針對sticky事件的前置處理。這里同樣要先了解一下stickyEvents變量。它也是一個Map結構,鍵是事件類型的class對象,值是具體的粘性事件。

private final Map<Class<?>, Object> stickyEvents;

subscribe3執(zhí)行邏輯梳理如下:

  1. 如果事件類型是可繼承的(默認配置是可繼承),則遍歷事件本身和其超類,執(zhí)行checkPostStickyEventToSubscription
  2. 如果是不可繼承的,則直接執(zhí)行checkPostStickyEventToSubscription

那么,checkPostStickyEventToSubscription做了什么操作呢?

    private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            postToSubscription(newSubscription, stickyEvent, isMainThread());
        }
    }


private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(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);
        }
    }

可以看到,checkPostStickyEventToSubscription在內部調用了postToSubscription方法,該方法內部根據不同的threadMode,執(zhí)行了不同的操作

  1. 如果線程模式是POSTING,則直接通過反射invoke調用執(zhí)行訂閱方法
  2. 如果線程模式是MAIN,且當前訂閱方法就處于主線程,則執(zhí)行同1操作,否則通過mainThreadPoster(HandlerPoster類型)切換到主線程來執(zhí)行訂閱方法
  3. 如果線程模式是MAIN_ORDERED,執(zhí)行與2相反的操作
  4. 如果線程模式是BACKGROUND,且訂閱方法定義在主線程,則通過backgroundPoster異步執(zhí)行訂閱方法,否則通過invoke直接執(zhí)行訂閱方法
  5. 如果線程模式是ASYNC,則通過asyncPoster異步執(zhí)行訂閱方法

從以上分析可知,當遇到粘性事件時,訂閱者一旦向EventBus注冊,EventBus就會馬上直接或間接地處理粘性事件所在的訂閱方法。具體的處理則是交由mainThreadPoster、backgroundPoster、asyncPoster等幾個Poster實現,這些Poster我們將在后續(xù)章節(jié)去詳細分析,這里暫且略過。

4.4 小結

綜上,subscribe方法主要執(zhí)行了如下幾個操作:

  1. 根據事件類型,按照priority優(yōu)先級將訂閱信息newSubscription存入訂閱信息集合subscriptions中
  2. 根據訂閱者類的class對象,存儲訂閱者訂閱的事件類型
  3. 執(zhí)行粘性事件對應的訂閱方法

五、結束語

本章分析了EventBus的訂閱者注冊流程,通過流程分析,我們可以知道,當同一個訂閱類中,有同方法名且事件類型相同的訂閱方法時,EventBus會拋出以下異常:

"Subscriber " + subscriber.getClass() + " already registered to event " + eventType

對于粘性事件,EventBus在完成訂閱者的注冊的最后,會直接或間接地執(zhí)行其對應的訂閱方法。這也就是為什么粘性事件會得以執(zhí)行的原因。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容