一、注冊(cè)主要流程
EventBus的注冊(cè)代碼如下:
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方法主要做了三件事:
- 獲取訂閱者類(lèi)的class對(duì)象
- 根據(jù)class對(duì)象查找對(duì)應(yīng)的訂閱者類(lèi)的所有訂閱方法
- 執(zhí)行訂閱
二耕姊、SubscriberMethodFinder
查找訂閱方法時(shí),調(diào)用了SubscriberMethodFinder類(lèi)的findSubscriberMethods方法讼稚,并將訂閱者類(lèi)的class對(duì)象作為實(shí)參傳遞進(jìn)去肯夏。那么SubscriberMethodFinder是什么呢殿漠?
SubscriberMethodFinder類(lèi),顧名思義完沪,它就是一個(gè)訂閱者方法查找器。在它的內(nèi)部有一個(gè)很重要的成員變量METHOD_CACHE嵌戈,即訂閱者方法緩存覆积,它是線程安全的HashMap,鍵是訂閱者類(lèi)的class對(duì)象咕别,具備唯一性技健;值是訂閱方法(添加了@Subscribe注解的方法)集合。
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
查找對(duì)應(yīng)的訂閱者類(lèi)的訂閱方法時(shí)惰拱,執(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;
}
}
這部分代碼邏輯很清晰雌贱,按步驟分別作了以下操作:
- 根據(jù)訂閱者class對(duì)象到METHOD_CACHE緩存中查找對(duì)應(yīng)的訂閱方法集合
- 如果訂閱方法集合非空,直接返回偿短;否則繼續(xù)向下執(zhí)行
- 如果不在編譯時(shí)使用索引技術(shù)欣孤,則跳轉(zhuǎn)findUsingReflection,使用反射查找
- 如果在編譯時(shí)使用索引技術(shù)昔逗,則跳轉(zhuǎn)findUsingInfo降传,使用索引查找
- 將查找到的集合存入METHOD_CACHE緩存
對(duì)于findUsingReflection和findUsingInfo,在這一章不去深究勾怒,我們的主線仍然是注冊(cè)婆排。
三、SubscriberMethod
那么訂閱方法SubscriberMethod是什么呢笔链?其實(shí)它對(duì)應(yīng)的就是我們?cè)诖a中使用@Subscribe注解標(biāo)記的public方法段只。
public class SubscriberMethod {
final Method method;
final ThreadMode threadMode;
final Class<?> eventType;
final int priority;
final boolean sticky;
...
}
回想一下,我們?cè)谑褂肊ventBus時(shí)鉴扫,訂閱方法的常規(guī)寫(xiě)法赞枕,比如下面這種形式:
@Subscribe(threadMode = ThreadMode.MainThread,sticky=true)
public void subscribe(Event event) {
}
對(duì)應(yīng)到SubscriberMethod 成員,是不是一路了然?method代表訂閱方法炕婶,threadMode代表訂閱方法的執(zhí)行線程姐赡,eventType代表事件類(lèi)型,priority代表事件優(yōu)先級(jí)柠掂,sticky代表事件是否粘性项滑。
四、訂閱
回到第一小節(jié)陪踩,查找到訂閱方法集合之后杖们,需要遍歷集合,對(duì)每個(gè)訂閱者和訂閱方法執(zhí)行subscribe訂閱操作肩狂。
subscribe的代碼邏輯比較多摘完,但其實(shí)也很清晰,我們將該方法完整地(不省略任何代碼)拆分成3個(gè)小方法傻谁,依次分析孝治。即:
//該代碼是偽代碼
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類(lèi)和subscriptionsByEventType變量审磁。
4.1.1 Subscription
Subscription的內(nèi)部結(jié)構(gòu)如下:
final Object subscriber;
final SubscriberMethod subscriberMethod;
volatile boolean active;
該類(lèi)和第二小節(jié)提到的METHOD_CACHE很相似谈飒,但又不同。METHOD_CACHE記錄的是訂閱者類(lèi)中的所有訂閱方法态蒂,是一對(duì)多的關(guān)系杭措;而Subscription記錄的是訂閱者類(lèi)的某個(gè)具體的訂閱方法,是一對(duì)一的關(guān)系钾恢。該類(lèi)中的active布爾值當(dāng)注冊(cè)解除時(shí)被置位成false手素。我們可以把該類(lèi)理解成訂閱信息。
4.1.2 subscriptionsByEventType
subscriptionsByEventType在該系列文章的第二篇中的結(jié)束語(yǔ)中有提及瘩蚪,但沒(méi)有深究泉懦。subscriptionsByEventType其實(shí)是一個(gè)依據(jù)事件類(lèi)型來(lái)存儲(chǔ)訂閱者及訂閱方法信息的數(shù)據(jù)結(jié)構(gòu)。subscriptionsByEventType在EventBus類(lèi)中的定義是這樣的:
Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
subscriptionsByEventType也是一個(gè)Map結(jié)構(gòu)疹瘦,鍵是一個(gè)事件類(lèi)型的class對(duì)象崩哩,值則是一個(gè)線程安全的訂閱信息集合。也許會(huì)有讀者問(wèn):為什么鍵是一個(gè)“事件類(lèi)型”的class對(duì)象,而不是其他呢?我們可以從subscribe1的如下代碼中推敲而出(更直觀地恨课,也可以通過(guò)命名得知):
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
看到?jīng)]有,get方法里的實(shí)參是一個(gè)事件類(lèi)型的class對(duì)象吴超,由此得出以上結(jié)論。
4.1.3 subscribe1流程分析
分析完Subscription和subscriptionsByEventType后鸯乃,我們梳理一下subscribe1的代碼邏輯:
- 將訂閱者與某個(gè)具體的訂閱方法包裝成一個(gè)新的訂閱信息對(duì)象newSubscription
- 根據(jù)事件類(lèi)型,從subscriptionsByEventType中取出訂閱信息集合subscriptions
- 如果訂閱信息集合為null,說(shuō)明是首次訂閱該類(lèi)事件缨睡,直接創(chuàng)建一個(gè)空的訂閱信息集合鸟悴,并存入subscriptionsByEventType
- 如果訂閱信息集合不是null,則說(shuō)明此前已經(jīng)有訂閱過(guò)該類(lèi)事件的歷史奖年,對(duì)訂閱信息集合進(jìn)行contains判斷细诸,如果該集合中存在newSubscription,則說(shuō)明訂閱者重復(fù)訂閱了同一類(lèi)事件陋守,拋出異常
- 將訂閱信息newSubscription按照priority優(yōu)先級(jí)存入訂閱信息集合subscriptions中
也許會(huì)有讀者有疑問(wèn)震贵,newSubscription明明每次都是被重新new出來(lái)的,對(duì)象地址勢(shì)必都不同水评,這樣在執(zhí)行subscriptions.contains(newSubscription)不就一定是返回false嗎猩系?這樣不就永遠(yuǎn)也不會(huì)拋出這個(gè)異常了?但在實(shí)際使用中中燥,的確是有很多讀者都遇到過(guò)這個(gè)異常信息寇甸。
EventBus之所以敢這樣寫(xiě),是因?yàn)樗貙?xiě)了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也是該系列第二章中結(jié)束語(yǔ)時(shí)提到的一個(gè)關(guān)鍵變量绽淘,它在EventBus中的定義如下:
private final Map<Object, List<Class<?>>> typesBySubscriber;
typesBySubscriber也是一個(gè)Map結(jié)構(gòu),鍵是訂閱者類(lèi)的class對(duì)象闹伪,值是該訂閱類(lèi)已經(jīng)訂閱的事件類(lèi)型的集合沪铭。
知道了這個(gè)變量的作用以后,subscribe2的邏輯理解起來(lái)就很簡(jiǎn)單了祭往。
- 根據(jù)訂閱者伦意,從typesBySubscriber集合中取出已經(jīng)訂閱的事件類(lèi)型集合subscribedEvents
- 如果subscribedEvents 為null,說(shuō)明還沒(méi)有訂閱任何事件硼补。創(chuàng)建一個(gè)空的事件類(lèi)型集合驮肉,并存入typesBySubscriber中
- 將事件類(lèi)型的class對(duì)象加入事件類(lèi)型集合中
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主要是針對(duì)sticky事件的前置處理。這里同樣要先了解一下stickyEvents變量已骇。它也是一個(gè)Map結(jié)構(gòu)离钝,鍵是事件類(lèi)型的class對(duì)象,值是具體的粘性事件褪储。
private final Map<Class<?>, Object> stickyEvents;
subscribe3執(zhí)行邏輯梳理如下:
- 如果事件類(lèi)型是可繼承的(默認(rèn)配置是可繼承)卵渴,則遍歷事件本身和其超類(lèi),執(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在內(nèi)部調(diào)用了postToSubscription方法碘橘,該方法內(nèi)部根據(jù)不同的threadMode互订,執(zhí)行了不同的操作
- 如果線程模式是POSTING,則直接通過(guò)反射invoke調(diào)用執(zhí)行訂閱方法
- 如果線程模式是MAIN痘拆,且當(dāng)前訂閱方法就處于主線程仰禽,則執(zhí)行同1操作,否則通過(guò)mainThreadPoster(HandlerPoster類(lèi)型)切換到主線程來(lái)執(zhí)行訂閱方法
- 如果線程模式是MAIN_ORDERED纺蛆,執(zhí)行與2相反的操作
- 如果線程模式是BACKGROUND吐葵,且訂閱方法定義在主線程,則通過(guò)backgroundPoster異步執(zhí)行訂閱方法桥氏,否則通過(guò)invoke直接執(zhí)行訂閱方法
- 如果線程模式是ASYNC温峭,則通過(guò)asyncPoster異步執(zhí)行訂閱方法
從以上分析可知,當(dāng)遇到粘性事件時(shí)识颊,訂閱者一旦向EventBus注冊(cè)诚镰,EventBus就會(huì)馬上直接或間接地處理粘性事件所在的訂閱方法。具體的處理則是交由mainThreadPoster祥款、backgroundPoster清笨、asyncPoster等幾個(gè)Poster實(shí)現(xiàn),這些Poster我們將在后續(xù)章節(jié)去詳細(xì)分析刃跛,這里暫且略過(guò)抠艾。
4.4 小結(jié)
綜上,subscribe方法主要執(zhí)行了如下幾個(gè)操作:
- 根據(jù)事件類(lèi)型桨昙,按照priority優(yōu)先級(jí)將訂閱信息newSubscription存入訂閱信息集合subscriptions中
- 根據(jù)訂閱者類(lèi)的class對(duì)象检号,存儲(chǔ)訂閱者訂閱的事件類(lèi)型
- 執(zhí)行粘性事件對(duì)應(yīng)的訂閱方法
五、結(jié)束語(yǔ)
本章分析了EventBus的訂閱者注冊(cè)流程蛙酪,通過(guò)流程分析齐苛,我們可以知道,當(dāng)同一個(gè)訂閱類(lèi)中桂塞,有同方法名且事件類(lèi)型相同的訂閱方法時(shí)凹蜂,EventBus會(huì)拋出以下異常:
"Subscriber " + subscriber.getClass() + " already registered to event " + eventType
對(duì)于粘性事件,EventBus在完成訂閱者的注冊(cè)的最后阁危,會(huì)直接或間接地執(zhí)行其對(duì)應(yīng)的訂閱方法玛痊。這也就是為什么粘性事件會(huì)得以執(zhí)行的原因。