EventBus源碼解析(三)-注冊(cè)

一、注冊(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方法主要做了三件事:

  1. 獲取訂閱者類(lèi)的class對(duì)象
  2. 根據(jù)class對(duì)象查找對(duì)應(yīng)的訂閱者類(lèi)的所有訂閱方法
  3. 執(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;
        }
    }

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

  1. 根據(jù)訂閱者class對(duì)象到METHOD_CACHE緩存中查找對(duì)應(yīng)的訂閱方法集合
  2. 如果訂閱方法集合非空,直接返回偿短;否則繼續(xù)向下執(zhí)行
  3. 如果不在編譯時(shí)使用索引技術(shù)欣孤,則跳轉(zhuǎn)findUsingReflection,使用反射查找
  4. 如果在編譯時(shí)使用索引技術(shù)昔逗,則跳轉(zhuǎn)findUsingInfo降传,使用索引查找
  5. 將查找到的集合存入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的代碼邏輯:

  1. 將訂閱者與某個(gè)具體的訂閱方法包裝成一個(gè)新的訂閱信息對(duì)象newSubscription
  2. 根據(jù)事件類(lèi)型,從subscriptionsByEventType中取出訂閱信息集合subscriptions
  3. 如果訂閱信息集合為null,說(shuō)明是首次訂閱該類(lèi)事件缨睡,直接創(chuàng)建一個(gè)空的訂閱信息集合鸟悴,并存入subscriptionsByEventType
  4. 如果訂閱信息集合不是null,則說(shuō)明此前已經(jīng)有訂閱過(guò)該類(lèi)事件的歷史奖年,對(duì)訂閱信息集合進(jìn)行contains判斷细诸,如果該集合中存在newSubscription,則說(shuō)明訂閱者重復(fù)訂閱了同一類(lèi)事件陋守,拋出異常
  5. 將訂閱信息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)單了祭往。

  1. 根據(jù)訂閱者伦意,從typesBySubscriber集合中取出已經(jīng)訂閱的事件類(lèi)型集合subscribedEvents
  2. 如果subscribedEvents 為null,說(shuō)明還沒(méi)有訂閱任何事件硼补。創(chuàng)建一個(gè)空的事件類(lèi)型集合驮肉,并存入typesBySubscriber中
  3. 將事件類(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í)行邏輯梳理如下:

  1. 如果事件類(lèi)型是可繼承的(默認(rèn)配置是可繼承)卵渴,則遍歷事件本身和其超類(lèi),執(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在內(nèi)部調(diào)用了postToSubscription方法碘橘,該方法內(nèi)部根據(jù)不同的threadMode互订,執(zhí)行了不同的操作

  1. 如果線程模式是POSTING,則直接通過(guò)反射invoke調(diào)用執(zhí)行訂閱方法
  2. 如果線程模式是MAIN痘拆,且當(dāng)前訂閱方法就處于主線程仰禽,則執(zhí)行同1操作,否則通過(guò)mainThreadPoster(HandlerPoster類(lèi)型)切換到主線程來(lái)執(zhí)行訂閱方法
  3. 如果線程模式是MAIN_ORDERED纺蛆,執(zhí)行與2相反的操作
  4. 如果線程模式是BACKGROUND吐葵,且訂閱方法定義在主線程,則通過(guò)backgroundPoster異步執(zhí)行訂閱方法桥氏,否則通過(guò)invoke直接執(zhí)行訂閱方法
  5. 如果線程模式是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è)操作:

  1. 根據(jù)事件類(lèi)型桨昙,按照priority優(yōu)先級(jí)將訂閱信息newSubscription存入訂閱信息集合subscriptions中
  2. 根據(jù)訂閱者類(lèi)的class對(duì)象检号,存儲(chǔ)訂閱者訂閱的事件類(lèi)型
  3. 執(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í)行的原因。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末狂打,一起剝皮案震驚了整個(gè)濱河市擂煞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌趴乡,老刑警劉巖对省,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝗拿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡官辽,警方通過(guò)查閱死者的電腦和手機(jī)蛹磺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)同仆,“玉大人,你說(shuō)我怎么就攤上這事裙品∷着” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵市怎,是天一觀的道長(zhǎng)岁忘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)区匠,這世上最難降的妖魔是什么干像? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮驰弄,結(jié)果婚禮上麻汰,老公的妹妹穿的比我還像新娘。我一直安慰自己戚篙,他們只是感情好五鲫,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著岔擂,像睡著了一般位喂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乱灵,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天塑崖,我揣著相機(jī)與錄音,去河邊找鬼痛倚。 笑死规婆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的状原。 我是一名探鬼主播聋呢,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼颠区!你這毒婦竟也來(lái)了削锰?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤毕莱,失蹤者是張志新(化名)和其女友劉穎器贩,沒(méi)想到半個(gè)月后颅夺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蛹稍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年吧黄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唆姐。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拗慨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奉芦,到底是詐尸還是另有隱情赵抢,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布声功,位于F島的核電站烦却,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏先巴。R本人自食惡果不足惜其爵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伸蚯。 院中可真熱鬧摩渺,春花似錦、人聲如沸朝卒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)抗斤。三九已至囚企,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瑞眼,已是汗流浹背龙宏。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伤疙,地道東北人银酗。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像徒像,于是被迫代替她去往敵國(guó)和親黍特。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容