一、注冊主要流程
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方法主要做了三件事:
- 獲取訂閱者類的class對象
- 根據class對象查找對應的訂閱者類的所有訂閱方法
- 執(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;
}
}
這部分代碼邏輯很清晰,按步驟分別作了以下操作:
- 根據訂閱者class對象到METHOD_CACHE緩存中查找對應的訂閱方法集合
- 如果訂閱方法集合非空,直接返回;否則繼續(xù)向下執(zhí)行
- 如果不在編譯時使用索引技術,則跳轉findUsingReflection,使用反射查找
- 如果在編譯時使用索引技術,則跳轉findUsingInfo,使用索引查找
- 將查找到的集合存入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的代碼邏輯:
- 將訂閱者與某個具體的訂閱方法包裝成一個新的訂閱信息對象newSubscription
- 根據事件類型,從subscriptionsByEventType中取出訂閱信息集合subscriptions
- 如果訂閱信息集合為null,說明是首次訂閱該類事件,直接創(chuàng)建一個空的訂閱信息集合,并存入subscriptionsByEventType
- 如果訂閱信息集合不是null,則說明此前已經有訂閱過該類事件的歷史,對訂閱信息集合進行contains判斷,如果該集合中存在newSubscription,則說明訂閱者重復訂閱了同一類事件,拋出異常
- 將訂閱信息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的邏輯理解起來就很簡單了。
- 根據訂閱者,從typesBySubscriber集合中取出已經訂閱的事件類型集合subscribedEvents
- 如果subscribedEvents 為null,說明還沒有訂閱任何事件。創(chuàng)建一個空的事件類型集合,并存入typesBySubscriber中
- 將事件類型的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í)行邏輯梳理如下:
- 如果事件類型是可繼承的(默認配置是可繼承),則遍歷事件本身和其超類,執(zhí)行checkPostStickyEventToSubscription
- 如果是不可繼承的,則直接執(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í)行了不同的操作
- 如果線程模式是POSTING,則直接通過反射invoke調用執(zhí)行訂閱方法
- 如果線程模式是MAIN,且當前訂閱方法就處于主線程,則執(zhí)行同1操作,否則通過mainThreadPoster(HandlerPoster類型)切換到主線程來執(zhí)行訂閱方法
- 如果線程模式是MAIN_ORDERED,執(zhí)行與2相反的操作
- 如果線程模式是BACKGROUND,且訂閱方法定義在主線程,則通過backgroundPoster異步執(zhí)行訂閱方法,否則通過invoke直接執(zhí)行訂閱方法
- 如果線程模式是ASYNC,則通過asyncPoster異步執(zhí)行訂閱方法
從以上分析可知,當遇到粘性事件時,訂閱者一旦向EventBus注冊,EventBus就會馬上直接或間接地處理粘性事件所在的訂閱方法。具體的處理則是交由mainThreadPoster、backgroundPoster、asyncPoster等幾個Poster實現,這些Poster我們將在后續(xù)章節(jié)去詳細分析,這里暫且略過。
4.4 小結
綜上,subscribe方法主要執(zhí)行了如下幾個操作:
- 根據事件類型,按照priority優(yōu)先級將訂閱信息newSubscription存入訂閱信息集合subscriptions中
- 根據訂閱者類的class對象,存儲訂閱者訂閱的事件類型
- 執(zhí)行粘性事件對應的訂閱方法
五、結束語
本章分析了EventBus的訂閱者注冊流程,通過流程分析,我們可以知道,當同一個訂閱類中,有同方法名且事件類型相同的訂閱方法時,EventBus會拋出以下異常:
"Subscriber " + subscriber.getClass() + " already registered to event " + eventType
對于粘性事件,EventBus在完成訂閱者的注冊的最后,會直接或間接地執(zhí)行其對應的訂閱方法。這也就是為什么粘性事件會得以執(zhí)行的原因。