EventBus系列『一』——注冊(cè)與注銷

簡介

本周我將開啟一個(gè)系列篇豆瘫,圍繞著EventBus的 運(yùn)行流程 + 源碼 的形式進(jìn)行精細(xì)剖析吞鸭,分為一下幾篇

EventBus系列『一』——注冊(cè)與注銷

EventBus系列『二』——Post與postSticky事件的發(fā)布與接收

EventBus系列『番外』——認(rèn)真剖析 『PendingPostQueue』隊(duì)列的實(shí)現(xiàn)思想

EventBus準(zhǔn)備提要

在講解我們注冊(cè)和注銷之前EventBus需要做一些準(zhǔn)備工作

[1] 將一些特殊重要屬性封裝成 ,PostingThreadState類型寺董,并將PostingThreadState 的實(shí)例放置于ThreadLocal
   final static class PostingThreadState {
        final List<Object> eventQueue = new ArrayList<>(); //訂閱事件隊(duì)列
        boolean isPosting; //是否已發(fā)布
        boolean isMainThread; //是否在主線程中發(fā)布的
        Subscription subscription; //訂閱信息
        Object event; //訂閱事件 
        boolean canceled; //是否已取消
    }
[2] 準(zhǔn)備一個(gè)Map集合將訂閱事件參數(shù)類與事件參數(shù)的所有父類集合關(guān)聯(lián)起來
private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();
[3]創(chuàng)建一個(gè)EventBus實(shí)例,初始化里面的參數(shù)
//創(chuàng)建單例
public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }
 public EventBus() {
        this(DEFAULT_BUILDER);
    }
EventBus(EventBusBuilder builder) {
    //創(chuàng)建EventBus的Log打印
    logger = builder.getLogger();  
    //用于將訂閱方法的EventType屬性與訂閱事件集合
    //(CopyOnWriteArrayList<Subscription>)關(guān)聯(lián)刻剥,緩存進(jìn)HashMap
    subscriptionsByEventType = new HashMap<>();  
   ////用于將注冊(cè)類與注冊(cè)類中的訂閱方法的類型集合(List<Class<?>>)關(guān)聯(lián)遮咖,緩存進(jìn)HashMap
    typesBySubscriber = new HashMap<>(); 
     // //用于緩存粘性事件,將粘性事件的消息實(shí)體類 與 粘性事件關(guān)聯(lián) 存入 ConcurrentHashMap中
     stickyEvents = new ConcurrentHashMap<>();
     //創(chuàng)建EventBus主線程實(shí)例
    mainThreadSupport = builder.getMainThreadSupport();  
    //創(chuàng)建主線程Poster造虏,用于將事件放入主線程處理隊(duì)列
    mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null; 
    //創(chuàng)建 后臺(tái)的Poster 御吞,用于將事件放入后臺(tái)處理隊(duì)列
    backgroundPoster = new BackgroundPoster(this); 
     //創(chuàng)建異步Poster  ,用于將事件放入異步處理隊(duì)列
    asyncPoster = new AsyncPoster(this); 
     //獲取訂閱事件注冊(cè)類的索引數(shù)量
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    //創(chuàng)建用于查詢訂閱事件的實(shí)例
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, 
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = 
    builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    eventInheritance = builder.eventInheritance;
    //獲取線程池實(shí)例
    executorService = builder.executorService; 
}

EventBus注冊(cè)

我們先來看一下EventBus注冊(cè)流程圖:

EventBus注冊(cè)流程圖.jpg

我將歷程圖分為兩個(gè)部分 第一部分為【主流程】而 第二部分為【事件集合的遍歷流程】 以方便大家理解漓藕,那么我們接下來就依據(jù)流程圖為大家逐一講解.

[1] EventBus.getDefault() 獲取EventBus實(shí)例

[2] 進(jìn)入 EventBus.java #方法 EventBus.getDefault().register(this)函數(shù)

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();  //獲取 subscriber類對(duì)象  即 上層參數(shù)  [ this ] 的類對(duì)象
     // [ 2.1 ] 檢索出注冊(cè)類中的所有? [ 訂閱信息方法]
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass)陶珠;
    //由于訂閱事件必須在同步塊中實(shí)現(xiàn),所以這里使用synchroniezd關(guān)鍵字鎖住??
    synchronized (this) {
       //使用for循環(huán),將事件逐個(gè)發(fā)布?
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            // [2.2] 發(fā)布訂閱信息
            subscribe(subscriber, subscriberMethod);
        }
    }
}?
  • 檢索出注冊(cè)類中的所有? [ 訂閱信息方法]
  • 遍歷獲取的訂閱信息集合享钞,并逐一發(fā)布訂閱信息
[2.1] 檢索注冊(cè)類中的所有訂閱方法 進(jìn)入 SubscriberMethodFinder.java 執(zhí)行

subscriberMethodFinder.findSubscriberMethods(subscriberClass)

//全局 [ 訂閱事件 ] 方法緩存Map
?private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
?List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    //根據(jù)注冊(cè)類class,從緩存中獲取訂閱事件鏈?
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
?    //已緩存過揍诽,直接返回訂閱事件List
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    //?是否使用通過反射生成的索引,默認(rèn)false
    if (ignoreGeneratedIndex) { 
         //通過索引類取出注冊(cè)類的訂閱事件信息  @問題:注冊(cè)時(shí)如何通過索引類獲取注冊(cè)類的訂閱事件集合
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {?
?        //使用反射獲取訂閱事件鏈List?    @問題:注冊(cè)時(shí)如何通過反射獲取注冊(cè)類的訂閱事件集合
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    //如果獲取的事件鏈List為空嫩与,則拋出異常?
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        //將注冊(cè)類與事件鏈List關(guān)聯(lián)起來寝姿,放入緩存?
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
       //返回訂閱鏈list?
        return subscriberMethods;
    }
}
  • 根據(jù)注冊(cè)類class,從緩存中獲取訂閱事件鏈?,若獲取的訂閱信息方法集合已存在,則直接返回該集合划滋。
  • 是否使用通過反射生成的索引饵筑,默認(rèn)false。
  • 若使用反射生成索引 則通過索引類取出注冊(cè)類的訂閱事件信息处坪,否則通過反射獲取訂閱信息集合
  • 將注冊(cè)類與事件鏈List關(guān)聯(lián)起來根资,放入緩存?
[2.2] 在 函數(shù) subscribe(subscriber, subscriberMethod) 中發(fā)布訂閱事件
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;   //獲取事件的類型
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);  //生成訂閱實(shí)例
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);  //根據(jù)事件類型架专,從Map緩存中查找訂閱鏈表
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);  //保存入Map中
    } else {
        if (subscriptions.contains(newSubscription)) {  //在訂閱鏈表中匹配當(dāng)前訂閱事件
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType); //拋出一個(gè)EventBusException異常,告訴用戶該事件已經(jīng)被訂閱過了
        }
    }
?    //將當(dāng)前的訂閱事件放入 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;
        }
    }
   //通過注冊(cè)類從typesBySubscriber中獲取?獲取對(duì)應(yīng)的subscribedEvents 
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        //將注冊(cè)類和注冊(cè)類中的訂閱方法的evenType屬性集合關(guān)聯(lián)起來放入? typesBySubscriber 中
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
   //將訂閱方法?的eventType屬性放入subscribedEvents集合
    subscribedEvents.add(eventType);
    //判斷當(dāng)前訂閱事件方法是不是粘性事件?
    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);
        }
    }
}
  • 首先根據(jù)傳遞的訂閱信息和完整的訂閱方法集合,生成訂閱實(shí)例
  • 根據(jù)事件類型玄帕,從subscriptionsByEventType Map緩存中查找對(duì)應(yīng)的訂閱鏈表
  • 若獲取的訂閱鏈表為空部脚,則將當(dāng)前訂閱方法的eventType屬性與空的CopyOnWriteArrayList關(guān)聯(lián)起來放入subscriptionsByEventTypeMap集合,之后我們會(huì)向CopyOnWriteArrayList的實(shí)例對(duì)象subscribedEvents填充數(shù)據(jù)裤纹,保證他們之間關(guān)聯(lián)準(zhǔn)確性
  • 將當(dāng)前的訂閱事件放入 subscriptions
  • 通過注冊(cè)類從typesBySubscriber 中獲取?獲取對(duì)應(yīng)的subscribedEvents
  • 將注冊(cè)類和注冊(cè)類中的訂閱方法的evenType屬性集合關(guān)聯(lián)起來放入? typesBySubscriber
  • 將訂閱方法?的eventType屬性放入subscribedEvents集合
  • 判斷當(dāng)前訂閱事件方法是不是粘性事件?,若是則 從緩存stickyEvents獲取委刘,并將其當(dāng)做一個(gè)新事件發(fā)布到總線

EventBus注銷

EventBus注銷流程圖 :

EventBus注銷流程圖.jpg

與注冊(cè)相比注銷流程就顯得簡單的多了,主要的就是將事件及注冊(cè)類的移除鹰椒,接下來我們結(jié)合源碼進(jìn)行了解一下

[1] EventBus.getDefault() 獲取EventBus實(shí)例

[2] 進(jìn)入 EventBus.java #方法 EventBus.getDefault().unregister(this)函數(shù)

public synchronized void unregister(Object subscriber) {
     //通過注冊(cè)類從typesBySubscriber中獲取對(duì)應(yīng)的訂閱事件類型集合
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {?
        for (Class<?> eventType : subscribedTypes) {
            // [ 2.1 ] 移除與當(dāng)前注冊(cè)類相同的對(duì)象中包含的訂閱事件
            unsubscribeByEventType(subscriber, eventType);
        }
         //從?typesBySubscriber中移除注冊(cè)類
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}
  • 通過注冊(cè)類從typesBySubscriber中獲取對(duì)應(yīng)的訂閱事件類型集合
  • 遍歷集合移除與當(dāng)前注冊(cè)類相同的對(duì)象中包含的訂閱事件
  • 遍歷結(jié)束后 即代表注冊(cè)類中的所有的訂閱都已經(jīng)被移除后從?typesBySubscriber中移除注冊(cè)類
[ 2.1 ] 移除與當(dāng)前注冊(cè)類相同的對(duì)象中包含的訂閱事件
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    //通過eventType從?subscriptionsByEventType獲取訂閱事件集合
    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);
             //判定當(dāng)前訂閱事件的注冊(cè)類是否與要取消的注冊(cè)類對(duì)象相同?
            if (subscription.subscriber == subscriber) {
                 //從訂閱集合中移除?
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}
  • 通過eventType?subscriptionsByEventType獲取訂閱事件集合
  • 遍歷集合锡移,判定當(dāng)前訂閱事件的注冊(cè)類是否與要取消的注冊(cè)類對(duì)象相同,若判定成功則將訂閱事件從集合中移除漆际。

總結(jié)

本篇我們?cè)敿?xì)講解了EventBus注冊(cè)注銷流程淆珊,從中可以發(fā)現(xiàn) :

  1. 在注銷流程其實(shí)沒什么復(fù)雜操作,就是講訂閱事件和注冊(cè)類進(jìn)行了逐一移除.
  2. 注冊(cè)流程就相對(duì)復(fù)雜許多奸汇,在注冊(cè)的最后我們發(fā)現(xiàn)了對(duì)postSticky 粘性事件的特殊處理施符,而并沒有對(duì)POST 事件做什么特殊處理。

This ALL! Thanks EveryBody!

請(qǐng)關(guān)注下篇

EventBus系列——Post 事件的發(fā)布與接收

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末擂找,一起剝皮案震驚了整個(gè)濱河市戳吝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贯涎,老刑警劉巖骨坑,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異柬采,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)且警,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門粉捻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人斑芜,你說我怎么就攤上這事肩刃。” “怎么了杏头?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵盈包,是天一觀的道長。 經(jīng)常有香客問我醇王,道長呢燥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任寓娩,我火速辦了婚禮叛氨,結(jié)果婚禮上呼渣,老公的妹妹穿的比我還像新娘。我一直安慰自己寞埠,他們只是感情好屁置,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仁连,像睡著了一般蓝角。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饭冬,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天使鹅,我揣著相機(jī)與錄音,去河邊找鬼伍伤。 笑死并徘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的扰魂。 我是一名探鬼主播麦乞,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼劝评!你這毒婦竟也來了姐直?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤蒋畜,失蹤者是張志新(化名)和其女友劉穎声畏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姻成,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡插龄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了科展。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片均牢。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖才睹,靈堂內(nèi)的尸體忽然破棺而出徘跪,到底是詐尸還是另有隱情,我是刑警寧澤琅攘,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布垮庐,位于F島的核電站,受9級(jí)特大地震影響坞琴,放射性物質(zhì)發(fā)生泄漏哨查。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一剧辐、第九天 我趴在偏房一處隱蔽的房頂上張望解恰。 院中可真熱鬧锋八,春花似錦、人聲如沸护盈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腐宋。三九已至紊服,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胸竞,已是汗流浹背欺嗤。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卫枝,地道東北人煎饼。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像校赤,于是被迫代替她去往敵國和親吆玖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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