EventBus 3.0解析


以下是基于3.0的代碼進(jìn)行的
git倉(cāng)庫(kù):https://github.com/greenrobot/EventBus.git


簡(jiǎn)介

簡(jiǎn)單來(lái)說(shuō),EventBus是用在Activity容劳,Service陈哑,F(xiàn)ragment以及Thread之間的一個(gè)事件總線框架恬叹,用來(lái)在他們之間傳遞消息识樱。

使用

下面簡(jiǎn)述一下用法迂烁。

  1. module級(jí)別的gradle中添加依賴 compile'org.greenrobot:eventbus:3.0.0'
  2. 拿Activity為例恭陡,在onCreate中添加注冊(cè)代碼 EventBus.getDefault().register(this)勋功,同時(shí)在onDestory中添加注銷代碼EventBus.getDefault().unregister(this)
    3.在當(dāng)前事件的訂閱者中添加Event方法坦报,方法內(nèi)部參數(shù)為你的事件類型。
    4.在事件發(fā)送的class里面通過(guò)post(事件類型)來(lái)傳達(dá)你的信息

解析

還是帶著問(wèn)題來(lái)看代碼吧狂鞋,這樣到最后至少給自己一個(gè)交代:理解了什么片择。
其實(shí)要去看源碼的時(shí)候,大都是會(huì)用這樣一個(gè)東西了骚揍,那我就會(huì)想字管,如果要我自己去實(shí)現(xiàn)這個(gè)bus我會(huì)怎么做啰挪。嗯,
a. 得有個(gè)東西收集我的訂閱的事情吧嘲叔,然后當(dāng)有人發(fā)布事件的時(shí)候亡呵,還能通知我;
b. 也得有個(gè)方法硫戈,當(dāng)我被銷毀的時(shí)候锰什,事件是不是也得被移除,不刪除的話必然npe了丁逝;
c. 粘性事件
d. 事件分發(fā)的原理

注冊(cè)過(guò)程

  public void register(Object subscriber) {
        //獲取訂閱者的類名
        Class<?> subscriberClass = subscriber.getClass();
        // 根據(jù)類名去查找他訂閱的方法  SubscriberMethod里面包含訂閱者的類名汁胆,方法的優(yōu)先級(jí),線程以及方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            //遍歷當(dāng)前類的所有訂閱方法霜幼,把他們放進(jìn)list里面
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

// 下面是訂閱方法


private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    //將subscriber和subscriberMethod封裝成 Subscription
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //根據(jù)事件類型獲取特定的 Subscription
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    //如果為null嫩码,說(shuō)明該subscriber尚未注冊(cè)該事件
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        //如果不為null,并且包含了這個(gè)subscription 那么說(shuō)明該subscriber已經(jīng)注冊(cè)了該事件罪既,拋出異常
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    //根據(jù)優(yōu)先級(jí)來(lái)設(shè)置放進(jìn)subscriptions的位置铸题,優(yōu)先級(jí)高的會(huì)先被通知
    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;
        }
    }

    //根據(jù)subscriber(訂閱者)來(lái)獲取它的所有訂閱事件
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        //把訂閱者、事件放進(jìn)typesBySubscriber這個(gè)Map中
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    //下面是對(duì)粘性事件的處理
    if (subscriberMethod.sticky) {
        //從EventBusBuilder可知琢感,eventInheritance默認(rèn)為true.
        if (eventInheritance) {
            // Existing sticky events of all subclasses of eventType have to be considered.
            // Note: Iterating over all events may be inefficient with lots of sticky events,
            // thus data structure should be changed to allow a more efficient lookup
            // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
            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 {
            //根據(jù)eventType回挽,從stickyEvents列表中獲取特定的事件
            Object stickyEvent = stickyEvents.get(eventType);
            //分發(fā)事件
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

首先,他先去自己家里面找猩谊,看一下這個(gè)對(duì)象,準(zhǔn)確的說(shuō)應(yīng)該是這個(gè)類祭刚,有沒(méi)有在EventBus里面注冊(cè)過(guò)如果注冊(cè)過(guò)就直接拿出保存method的list牌捷,否則就建立個(gè)list,通過(guò)注解信息(findUsingReflection)涡驮,或者(findUsingInfo)兩個(gè)方法來(lái)拿到注冊(cè)的方法暗甥。然后以eventType(類名)作為key,將他們存起來(lái)捉捅。

粘性事件(stickyEvent)

粘性事件與一般的事件不同撤防,粘性事件是先發(fā)送出去,然后讓后面注冊(cè)的訂閱者能夠收到該事件.
事件發(fā)送的時(shí)候是通過(guò)postSticky方法發(fā)送的棒口,然后把事件放到stickyEvents這個(gè)map里面去寄月,和原來(lái)的區(qū)分開(kāi),最后調(diào)用post方法无牵。

public void postSticky(Object event) {
     synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    // Should be posted after it is putted, in case the subscriber wants to remove immediately
    post(event);
}

那么為什么當(dāng)注冊(cè)訂閱者的時(shí)候可以馬上接收到匹配的事件呢漾肮。這是由于在subscribe方法中,我們都會(huì)便利一遍所有的粘性事件茎毁,然后調(diào)用checkPostStickyEventToSubscription方法進(jìn)行分發(fā)克懊。

unRegister 注銷過(guò)程

public synchronized void unregister(Object subscriber) {
    //根據(jù)當(dāng)前訂閱者來(lái)獲取自身所訂閱的所有事件
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        //遍歷所有事件注銷
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        // 注銷每個(gè)事件之后忱辅,訂閱者也要被清除掉
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

結(jié)合注冊(cè)過(guò)程,注銷比較容易理解谭溉。我注冊(cè)的時(shí)候不是有兩個(gè)集合放訂閱方法和訂閱者嗎墙懂,注銷的時(shí)候我就從typesBySubscriber里面拿到當(dāng)前subscriber訂閱的所有方法,讓subscriptionsByEventType去刪除扮念,然后再把這個(gè)訂閱者拿掉损搬。

事件如何調(diào)用

 /** Posts the given event to the event bus. */
   public void post(Object event) {
    //獲取一個(gè)postingState
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    //將事件加入隊(duì)列中
    eventQueue.add(event);
    if (!postingState.isPosting) {
    //判斷當(dāng)前線程是否是主線程
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }        
            try {
            //循環(huán)從隊(duì)列中將事件進(jìn)行分發(fā)
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    //eventInheritance  事件繼承。EventBus會(huì)考慮事件的繼承樹(shù)
    //如果事件繼承自父類扔亥,那么父類也會(huì)作為事件被發(fā)送
    if (eventInheritance) {
        //查找該事件的祖宗十八代以及接口
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
      //遍歷
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    
    //如果沒(méi)人訂閱這個(gè)事件就拋出異常
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}

//經(jīng)過(guò)一系列操作之后 來(lái)到最終的分發(fā)方法
 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);
        }
    }
    //通過(guò)反射來(lái)調(diào)用事件
      void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

事件發(fā)送代碼比較清晰场躯,就不細(xì)講了。

總結(jié)

EventBus的工作原理旅挤,主要分四個(gè)方面踢关,注冊(cè)注銷,事件分發(fā)與消化粘茄。

EventBus里面有兩個(gè)map签舞,一個(gè)subscriptionsByEventType的key是訂閱方法(eventType:onEvent(EventType e)),value是包含key下面的訂閱方法對(duì)象(Subscription)柒瓣;
另一個(gè)map(typesBySubscriber)儒搭,key是訂閱者類名(subscriber),value是訂閱的事件類型(eventType)芙贫。另一個(gè)map(typesBySubscriber)以訂閱者類名為key,訂閱者的eventTypes為value搂鲫,剛好能串起兩兄弟。

  • 注冊(cè) 注冊(cè)的時(shí)候就抽出Subscriber里面的訂閱方法磺平,根據(jù)方法的優(yōu)先級(jí)魂仍,是否是粘性事件等信息,包裝成一個(gè)subscription拣挪,分別存到上述兩個(gè)容器中擦酌。
  • 注銷 注冊(cè)注銷傳進(jìn)來(lái)的都是類對(duì)象,注銷的時(shí)候根據(jù)類對(duì)象的className菠劝,去typesBySubscriber里面找對(duì)應(yīng)的eventTypes(list)赊舶,然后遍歷list的value,以value為key,去subscriptionsByEventType
    中迭代拿掉注銷的內(nèi)容。
  • 事件發(fā)送 post的時(shí)候赶诊,先去找eventType的父類以及接口笼平,然后把所有的事件發(fā)送出去,牽扯到線程切換一類的都由封裝的一個(gè)poster處理舔痪。
  • 事件接收 接收就很簡(jiǎn)單了出吹,subscriber拿得到,方法也拿得到辙喂,直接去調(diào)用就好了捶牢。

為什么會(huì)有兩個(gè)呢鸠珠,內(nèi)容好像都差不多:你想啊,你發(fā)送事件的時(shí)候秋麸,發(fā)的是事件(eventType)吧渐排,如果只有一個(gè)容器,你是不是還得遍歷類名灸蟆,然后遍歷類中的方法去調(diào)用驯耻。兩個(gè)容器剛好根據(jù)功能區(qū)分開(kāi),一個(gè)用在注冊(cè)的時(shí)候炒考,另一個(gè)用在查狀態(tài)以及注銷的時(shí)候可缚,你注銷不是傳的類名嗎,我就去typesBySubscriber里面拿到你的信息斋枢,然后根據(jù)subscriptionsByEventType帘靡,去里面剔除掉你,效率也提高了瓤帚,也容易理解了描姚。

結(jié)語(yǔ)

以上是對(duì)EventbUs3.0的簡(jiǎn)單解析,歡迎勘誤

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末戈次,一起剝皮案震驚了整個(gè)濱河市轩勘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌怯邪,老刑警劉巖绊寻,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異悬秉,居然都是意外死亡榛斯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)搂捧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人懂缕,你說(shuō)我怎么就攤上這事允跑。” “怎么了搪柑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵聋丝,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我工碾,道長(zhǎng)弱睦,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任渊额,我火速辦了婚禮况木,結(jié)果婚禮上垒拢,老公的妹妹穿的比我還像新娘。我一直安慰自己火惊,他們只是感情好求类,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著屹耐,像睡著了一般尸疆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惶岭,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天寿弱,我揣著相機(jī)與錄音,去河邊找鬼按灶。 笑死症革,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的兆衅。 我是一名探鬼主播地沮,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼羡亩!你這毒婦竟也來(lái)了摩疑?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤畏铆,失蹤者是張志新(化名)和其女友劉穎雷袋,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體辞居,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡楷怒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瓦灶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸠删。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贼陶,靈堂內(nèi)的尸體忽然破棺而出刃泡,到底是詐尸還是另有隱情,我是刑警寧澤碉怔,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布烘贴,位于F島的核電站,受9級(jí)特大地震影響撮胧,放射性物質(zhì)發(fā)生泄漏桨踪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一芹啥、第九天 我趴在偏房一處隱蔽的房頂上張望锻离。 院中可真熱鬧铺峭,春花似錦、人聲如沸纳账。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疏虫。三九已至永罚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卧秘,已是汗流浹背呢袱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翅敌,地道東北人羞福。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蚯涮,于是被迫代替她去往敵國(guó)和親治专。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

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