EventBus源碼分析(一)

EventBus源碼分析(一)

EventBus官方介紹為一個(gè)為Android系統(tǒng)優(yōu)化的事件訂閱總線烙无,它不僅可以很方便的在同線程中傳遞事件或者對(duì)象翼虫,還可以在不同線程中實(shí)現(xiàn)事件或?qū)ο蟮膫鬟f颂斜,用法比較簡(jiǎn)單,可以很好地完成一些在原生系統(tǒng)中的Intent,Handler等可以完成的工作福荸,在Android開發(fā)過程中用途及其廣泛肢础,這里不再介紹它的具體用法还栓,由于在工作中遇到過一些關(guān)于EventBus的問題,所以計(jì)劃學(xué)習(xí)一下EventBus的設(shè)計(jì)與實(shí)現(xiàn)传轰。接下來的幾篇文章記錄對(duì)于EventBus源碼的學(xué)習(xí)過程剩盒,閱讀其源碼,不僅可以在遇到問題時(shí)可以很快找到原因慨蛙,更重要的是發(fā)現(xiàn)EventBus源碼程序中的類型結(jié)構(gòu)層次以及數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)有很多值得我們借鑒的地方辽聊。
EventBus的整體結(jié)構(gòu)是典型的觀察者模式,其工作流程如下:


EventBus-Publish-Subscribe.png

在傳統(tǒng)的觀察者模式中期贫,被觀察者記錄訂閱者的信息跟匆,在事件發(fā)生以后依次調(diào)用訂閱者的相應(yīng)方法。EventBus對(duì)其做了擴(kuò)展通砍,將訂閱信息的管理和事件的分發(fā)調(diào)度從被觀察者中抽離出來玛臂,就是我們的EventBus, 它作為一個(gè)事件總線烤蜕,被觀察者在事件發(fā)生后可以將事件推送到事件總線上,EventBus負(fù)責(zé)在合適的時(shí)機(jī)調(diào)用訂閱者的相應(yīng)方法迹冤。由于EventBus的存在讽营,被觀察者和訂閱者分離,所以我們的程序中使用EventBus傳遞事件或?qū)ο蠹捌浜?jiǎn)單泡徙,中間過程全交由EventBus處理橱鹏,本篇文章的重點(diǎn)就是關(guān)注EventBus在中間所做的事情。簡(jiǎn)單來說堪藐,EventBus的任務(wù)就是傳統(tǒng)觀察者當(dāng)中被觀察者的管理注冊(cè)信息和調(diào)度方法兩項(xiàng)任務(wù)蚀瘸。下面我們從EventBus這個(gè)類的源碼開始分析,首先從構(gòu)造器開始庶橱。

1. 構(gòu)造器

EventBus的構(gòu)造方式是典型的建造者模式贮勃,首先看我們獲取EventBus最常用的方法,getDefault()

/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}

如注釋所說苏章,這里是傳統(tǒng)的單例模式寂嘉,通常在一個(gè)應(yīng)用中使用同一個(gè)EventBus即可,這樣方便事件的處理枫绅,同時(shí)EventBus對(duì)事件的注冊(cè)和調(diào)度方法都是線程安全的泉孩,我們?cè)诤竺婢蜁?huì)看到,所以單例非常適合并淋。接下來看默認(rèn)構(gòu)造器:

 public EventBus() {
        this(DEFAULT_BUILDER);
    }

DEFAULT_BUILDER是一個(gè)靜態(tài)的構(gòu)造者寓搬,其中包含一系列的默認(rèn)屬性,最后就是以建造者為參數(shù)的構(gòu)造器县耽,過程就是一系列的參數(shù)初始化句喷,代碼如下:

EventBus(EventBusBuilder builder) {
        subscriptionsByEventType = new HashMap<>();  //以事件類的class對(duì)象為鍵值,記錄注冊(cè)方法信息兔毙,值為一個(gè)Subscription的列表
        typesBySubscriber = new HashMap<>();   //以注冊(cè)的類為鍵值唾琼,記錄該類所注冊(cè)的所有事件類型,值為一個(gè)Event的class對(duì)象的列表
        stickyEvents = new ConcurrentHashMap<>();  //記錄sticky事件
        //三個(gè)Poster, 負(fù)責(zé)在不同的線程中調(diào)用訂閱者的方法
        mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        ...
        //方法的查找類澎剥,用于查找某個(gè)類中有哪些注冊(cè)的方法
        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
        ...
        //后面是一些表示開關(guān)信息的boolean值以及一個(gè)線程池
    }

這里省略了一部分暫時(shí)沒有分析到的參數(shù)锡溯。首先來看管理注冊(cè)信息的兩個(gè)Map, 由于一個(gè)類中可能有多個(gè)方法監(jiān)聽多個(gè)事件,所以Subscription這個(gè)類封裝一個(gè)注冊(cè)信息哑姚,這個(gè)類很簡(jiǎn)單祭饭,只有三個(gè)屬性,如下:

final class Subscription {
    final Object subscriber;
    final SubscriberMethod subscriberMethod;
    /**
     * Becomes false as soon as {@link EventBus#unregister(Object)} is called, which is checked by queued event delivery
     * {@link EventBus#invokeSubscriber(PendingPost)} to prevent race conditions.
     */
    volatile boolean active;

    //之后為構(gòu)造器以及覆寫的equal和hashCode方法叙量。
    ...
}

public class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    final int priority;
    final boolean sticky;
    /** Used for efficient comparison */
    String methodString;

    //之后為構(gòu)造器以及覆寫的equal和hashCode方法倡蝙。
    ...

所以類和方法唯一確定一條注冊(cè)信息,active表示該注冊(cè)信息是否有效(如注釋所說它的作用)宛乃。所以在EventBus中悠咱,使用subscriptionsByEventType,以Event的class對(duì)象為鍵值征炼,管理注冊(cè)信息析既,值為一個(gè)處理事件類型為該鍵值的Subscription的列表。typesBySubscriber則相對(duì)簡(jiǎn)單谆奥,就是記錄一個(gè)類注冊(cè)了哪些事件類型眼坏。雖然二者有所冗余,它們?cè)诤竺娴淖?cè)和調(diào)度過程中都是為了便于查詢酸些,這樣更為高效宰译。
然后是stickyEvents,是一個(gè)線程安全的Map,用來記錄sticky事件魄懂,sticky事件的含義是指即使被觀察者發(fā)送sticky事件是在訂閱者訂閱該事件之前沿侈,訂閱者在訂閱之后,EventBus將該事件發(fā)送到該訂閱者市栗,即調(diào)用相應(yīng)的訂閱方法缀拭。(如果在之后,那就和普通事件一樣)
接下來的三個(gè)Poster極為重要填帽,但是這里理解很容易蛛淋,就是負(fù)責(zé)在不同線程中調(diào)用方法,他們分別對(duì)應(yīng)著threadMode中除去POSTING之外三種類型篡腌。
最后是一個(gè)Finder褐荷,由于在EventBus的使用簡(jiǎn)便,都是以對(duì)象為單位調(diào)用registe()方法嘹悼,但是一個(gè)對(duì)象中可能有多個(gè)注冊(cè)方法叛甫,所以注冊(cè)過程中需要subscriberMethodFinder查找一個(gè)類中有哪些注冊(cè)方法,最后生成一個(gè)Subscrip杨伙,并存在subscriptionsByEventType的某一個(gè)列表中合溺。

EventBus的構(gòu)造器很簡(jiǎn)單,這里重點(diǎn)講述了一下其中的幾個(gè)比較重要的成員變量缀台,尤其是管理注冊(cè)信息的數(shù)據(jù)結(jié)構(gòu)棠赛,下一步就是注冊(cè)源碼的分析。

2. 注冊(cè)

注冊(cè)方法rigister()的代碼如下:

public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

思路很明確膛腐,兩個(gè)步驟睛约,一是查找注冊(cè)方法的列表,這個(gè)利用到了SubscriberMethodFinder的對(duì)應(yīng)方法哲身,查找一個(gè)類中有哪些注冊(cè)方法辩涝,而是調(diào)用訂閱方法,參數(shù)為類和方法兩個(gè)勘天,即subscriber和subscriberMethod怔揩。查找我們留到下一部分捉邢,先看訂閱方法:

 // Must be called in synchronized block
private void subscribe(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;
        }
    }

    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    if (subscriberMethod.sticky) {
        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 {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

代碼雖然有點(diǎn)長(zhǎng),但是邏輯很簡(jiǎn)單清晰商膊,就是構(gòu)建Subscription, 然后將該注冊(cè)信息保存到兩個(gè)數(shù)據(jù)結(jié)構(gòu)中伏伐。對(duì)于subscriptionsByEventType首先獲取EventType對(duì)應(yīng)的列表,沒有則創(chuàng)建晕拆,重復(fù)注冊(cè)則拋異常藐翎,正常情況下,則根據(jù)priority插入到列表中適合的位置实幕。對(duì)于typesBySubscriber吝镣,則是更新該subscriber對(duì)應(yīng)的列表即可。最后是處理sticky事件昆庇,即在注冊(cè)時(shí)末贾,如果是監(jiān)聽sticky事件,則需要從stickyEvents中取出對(duì)應(yīng)sticky事件整吆,并發(fā)送到訂閱者未舟。這里需要注意eventInheritance是一個(gè)開關(guān),表示是否處理EventType的繼承關(guān)系掂为,默認(rèn)為true裕膀,如代碼中,EventBus會(huì)向訂閱者發(fā)送該類型的事件勇哗,以及該類型所有子類類型的事件昼扛。由于訂閱者監(jiān)聽一個(gè)sticky事件,那么該sticky事件的子類型也可以認(rèn)為是該類型的事件欲诺,所以訂閱者也同樣會(huì)接收到該事件抄谐。最后checkPostStickyEventToSubscription方法如下:

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
    if (stickyEvent != null) {
        // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
        // --> Strange corner case, which we don't take care of here.
        postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
    }
}

代碼很簡(jiǎn)單,至于如何發(fā)送扰法,放到第三部分統(tǒng)一分析蛹含,注釋中說的訂閱者不能終止sticky事件的發(fā)送,至于posting state則同樣放在但三部分說明塞颁。

注冊(cè)過程就是這些浦箱,還有一個(gè)重要且稍微復(fù)雜的查找過程放在第二篇中分析,在分析事件分發(fā)之前祠锣,與注冊(cè)相反的unrigister由于比較簡(jiǎn)單酷窥,這里一并說了,下面是其代碼:

 /** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

邏輯很明確伴网,調(diào)用unsubscribeByEventType方法蓬推,并更新typesBySubscriber數(shù)據(jù)結(jié)構(gòu),那么你可能猜到了澡腾,unsubscribeByEventType方法中就是更新另一個(gè)數(shù)據(jù)結(jié)構(gòu)沸伏,代碼如下:

/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    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);
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

這里邏輯很簡(jiǎn)單糕珊,但是有一點(diǎn)值得注意的是就遍歷刪除列表時(shí),注意序號(hào)i和size的改變毅糟,容易出現(xiàn)數(shù)組越界的錯(cuò)誤红选,遍歷刪除通常使用倒序的方式,不容易出現(xiàn)錯(cuò)誤留特,這里動(dòng)態(tài)改變size變量也是一樣纠脾。注銷過程需要考慮sticky事件玛瘸,也不需要查找過程蜕青,所以過程很簡(jiǎn)單。
register和unregister的過程除了查找訂閱方法以外糊渊,邏輯很簡(jiǎn)單右核,就是更新一下管理注冊(cè)信息的數(shù)據(jù)結(jié)構(gòu),可以看出數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)還是很重要的渺绒,設(shè)計(jì)的好贺喝,邏輯就會(huì)很清晰,程序代碼也就相對(duì)簡(jiǎn)潔易懂宗兼。下一步就是介紹事件的分發(fā)躏鱼。

3. 事件分發(fā)

在分析post()方法之前,先看EventBus的一個(gè)內(nèi)部類殷绍,PostState:

/** For ThreadLocal, much faster to set (and get multiple values). */
final static class PostingThreadState {
    final List<Object> eventQueue = new ArrayList<Object>();
    boolean isPosting;
    boolean isMainThread;
    Subscription subscription;
    Object event;
    boolean canceled;
}

這個(gè)內(nèi)部類只有幾個(gè)屬性變量染苛,其中我們現(xiàn)在只需要注意第一個(gè),事件消息隊(duì)列即可主到。如注釋所言茶行,這個(gè)類的實(shí)例對(duì)象在EventBus中是一個(gè)ThreadLocal變量,即線程本地變量登钥,不同線程之間不會(huì)相互影響畔师,而eventQueue則是用來保存當(dāng)前線程需要發(fā)送的事件(為什么會(huì)有隊(duì)列,是因?yàn)镻OST線程也就是調(diào)用post()方法的線程與調(diào)用訂閱者方法的線程不同牧牢,在POST線程中連續(xù)的調(diào)用post()方法發(fā)送事件看锉,會(huì)造成事件的累積)。后面的四個(gè)變量都是與cancelEventDelivery()方法有關(guān)塔鳍,在后面對(duì)其進(jìn)行分析度陆。
下面為post()方法的代碼:

/** Posts the given event to the event bus. */
public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);

    if (!postingState.isPosting) {
        postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

首先需要明確的是,如注釋所言献幔,post()的方法是將事件發(fā)送到EventBus懂傀,至于何時(shí)調(diào)用訂閱者的方法則有EventBus調(diào)度。從代碼中看postingState是一個(gè)ThreadLocal,用于保存當(dāng)前post事件的狀態(tài)蜡感。post()方法就是設(shè)置一些postState的屬性蹬蚁,然后遍歷事件消息隊(duì)列恃泪,調(diào)用postSingleEvent()方法,其代碼如下:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    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);
    }
    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));
        }
    }
}

這個(gè)方法中同樣也用到了eventInheritance這個(gè)開關(guān)犀斋,即是否考慮Event事件類型的繼承關(guān)系贝乎,默認(rèn)為true,這里的lookupAllEventTypes()方法是EventBus的靜態(tài)方法叽粹,查找eventClass所有包括自己在內(nèi)的父類以及它們所實(shí)現(xiàn)的接口览效,然后對(duì)于沒有eventClass,調(diào)用postSingleForEventType()方法虫几,返回的結(jié)果為是否找到了對(duì)應(yīng)的訂閱方法锤灿,在沒有找到的情況下,會(huì)做出打印Log信息和發(fā)送事件處理辆脸,這里的logNoSubscriberMessages和sendNoSubscriberEvent是EventBus的開關(guān)屬性但校,與eventInheritance類似,也可以在Builder中設(shè)置啡氢,默認(rèn)為true状囱。如果當(dāng)我們調(diào)用post()方法發(fā)出某個(gè)事件時(shí)想知道我們的事件有沒有被訂閱者接收,就可以在發(fā)送消息的類中接收NoSubscriberEvent事件倘是,如果收到該事件說明應(yīng)用中沒有訂閱者接收我們發(fā)出的事件亭枷。
這里在看源碼時(shí)有一點(diǎn)點(diǎn)小疑問,就是在注冊(cè)時(shí)處理sticky事件時(shí)是找到Event的所有子類并發(fā)送給該訂閱者搀崭,而這里是Event的所有父類叨粘,并將其發(fā)送出去。前者是站在訂閱者的角度上门坷,訂閱者在注冊(cè)時(shí)要求接收某個(gè)sticky事件宣鄙,那么該事件的所有子類也是該sticky事件的一種,所以應(yīng)該發(fā)送給該訂閱者默蚌。比如一個(gè)訂閱者訂閱天氣預(yù)報(bào)的sticky事件冻晤,那么如果在stickyEvent中有一個(gè)今天下雨的事件(假設(shè)該事件繼承自天氣預(yù)報(bào),隨便想的例子绸吸,可能不太恰當(dāng))也應(yīng)該發(fā)送給該訂閱者鼻弧。而此處postSingleEvent方法中是發(fā)送單個(gè)Event,是站在發(fā)送者發(fā)送到EventBus的角度上锦茁,我需要發(fā)送某個(gè)事件通知訂閱者攘轩,比如我發(fā)送一個(gè)天氣預(yù)報(bào)的事件到EventBus, 但是EventBus由于不知道天氣預(yù)報(bào)是否表示下雨,就不應(yīng)該通知哪些是否下雨事件的訂閱者码俩,相反那些監(jiān)聽新聞的訂閱者(假設(shè)天氣預(yù)報(bào)繼承自新聞度帮,而天氣預(yù)報(bào)是一條新聞),EventBus需要負(fù)責(zé)通知他們這條天氣預(yù)報(bào)的新聞。被觀察者沒有將新聞發(fā)送到EventBus上笨篷,但是EventBus則因?yàn)橐粭l天氣預(yù)報(bào)需要找出新聞并發(fā)送到相應(yīng)的訂閱者上瞳秽,所以發(fā)送是兩個(gè)過程。接下來是postSingleEventForEventType方法率翅,其代碼如下:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

這里首先說明CopyOnWriteArrayList是為了線程安全练俐,每次對(duì)List的修改都會(huì)重新一份,由于是線程安全的所以不需要同步處理冕臭,但是對(duì)HashMap的讀取操作則不是線程安全的腺晾,所以需要線程同步。這個(gè)方法的邏輯也很簡(jiǎn)單辜贵,就是從subscriptionsByEventType中找出事件類型對(duì)應(yīng)的注冊(cè)信息列表悯蝉,然后遍歷調(diào)用postToSubscription()方法,這個(gè)方法有些熟悉了念颈,就是在注冊(cè)中處理sticky事件時(shí)調(diào)用的方法泉粉。不過這里需要注意的是abort每次都會(huì)讀取postingState的cancel狀態(tài)判斷發(fā)送事件是否被終止连霉,而另外兩個(gè)遍歷 event和subscription的賦值和清空也是為了用于cancelEventDelivery()方法榴芳,后面會(huì)統(tǒng)一說。最后終于到了postToSubscription()方法跺撼,其代碼為:

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 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);
    }
}

這里就到了真正的事件分發(fā)了窟感,分為我們所熟知的四種threadModed的情形,其代碼邏輯很清晰歉井,根據(jù)threadMode以及isMainThread選擇調(diào)用invokeSubscriber方法還是加入相應(yīng)的隊(duì)列柿祈,異步執(zhí)行。關(guān)于入隊(duì)異步執(zhí)行放在第三篇中講述哩至,重點(diǎn)分析三個(gè)Poster的實(shí)現(xiàn)躏嚎,這里先貼出invokeSubscriber的代碼,其實(shí)就是利用反射調(diào)用訂閱者的方法菩貌,方法存儲(chǔ)在subsription的subsribeMethod變量中:

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ā)就結(jié)束了卢佣,梳理一下就是一下流程:1. post(Object event) -> 2. postSingleEvent(Object event, PostingThreadState postingState) -> 3. postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) -> 4. postToSubscription(Subscription subscription, Object event, boolean isMainThread).
其中第一步是遍歷一個(gè)線程本地變量中保存的事件消息隊(duì)列的所有消息,第二步是遍歷一個(gè)事件類型的所有父類箭阶,第三步是遍歷一個(gè)事件類型的所有注冊(cè)信息虚茶,第四步則是事件分發(fā),根據(jù)threadMode選擇合適的處理方式股冗。
讀到這里就感覺優(yōu)秀代碼的確是邏輯結(jié)構(gòu)十分清晰杰妓,看起來一目了然秽澳,在一個(gè)任務(wù)中合理的劃分步驟,拆分成多個(gè)方法罩扇,讓人更容易理解。

接下來就是前面一直提到的cancelEventDelivery方法:

/**
 * Called from a subscriber's event handling method, further event delivery will be canceled. Subsequent
 * subscribers
 * won't receive the event. Events are usually canceled by higher priority subscribers (see
 * {@link Subscribe#priority()}). Canceling is restricted to event handling methods running in posting thread
 * {@link ThreadMode#POSTING}.
 */
public void cancelEventDelivery(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    if (!postingState.isPosting) {
        throw new EventBusException(
                "This method may only be called from inside event handling methods on the posting thread");
    } else if (event == null) {
        throw new EventBusException("Event may not be null");
    } else if (postingState.event != event) {
        throw new EventBusException("Only the currently handled event may be aborted");
    } else if (postingState.subscription.subscriberMethod.threadMode != ThreadMode.POSTING) {
        throw new EventBusException(" event handlers may only abort the incoming event");
    }

    postingState.canceled = true;
}

這里重點(diǎn)先看一下注釋怕磨,這個(gè)方法的作用就是在事件處理方法中調(diào)用喂饥,終止事件的進(jìn)一步傳遞寞缝,這個(gè)和android系統(tǒng)中的順序廣播是相同的道理(即通過提高自己的的優(yōu)先權(quán)可以率先收到事件,然后截取該事件)仰泻。但是這里有一個(gè)條件就是事件處理方法的threadMode必須是POSTING荆陆,即執(zhí)行線程與事件的發(fā)送線程為同一個(gè)線程。
從代碼中可以看出終止事件條件十分苛刻集侯,PostState中的三個(gè)變量都是用來判斷是否可以終止事件的被啼,第一個(gè)是isPosting,該變量在post方法中被設(shè)置true棠枉,表示一個(gè)事件類型及其父類正在被發(fā)送的狀態(tài)中浓体,發(fā)送完畢以后才被設(shè)置為false。所以在這個(gè)過程以外都不可以調(diào)用該方法辈讶。最后兩個(gè)條件postSingleEventForEventType中被設(shè)置的命浴,對(duì)于一個(gè)事件類型(不包括其父類)遍歷它的所有注冊(cè)信息,針對(duì)每一個(gè)注冊(cè)信息調(diào)用postToSubscription方法之前和之后這兩個(gè)變量都會(huì)被設(shè)置贱除,就是為了在這里判斷生闲,是否可以在某一個(gè)注冊(cè)信息的方法被調(diào)用時(shí)終止這個(gè)事件的繼續(xù)發(fā)送。(這里有一點(diǎn)不明白月幌,一個(gè)事件的所有注冊(cè)信息遍歷時(shí)碍讯,事件是同一個(gè),為什么在遍歷過程中都要賦值和清空扯躺,這里是否可以改成設(shè)置一次和清空一次即可捉兴?)。最后在設(shè)置了canceled變量以后(如果cancelEventDelivery在事件處理方法中被調(diào)用了)录语,事件處理方法返回之后倍啥,postToSubscription接著返回,canceled被賦值到了abort變量澎埠,這時(shí)候abort如果為true, 則break跳出循環(huán)虽缕,從而終止了該事件類型的繼續(xù)發(fā)送到其他的注冊(cè)信息。(注意這里不會(huì)影響其他事件類型失暂,如其父類事件等)

看到這里也就明白了為什么限制在threadMode為POSTING的事件處理方法中調(diào)用cancelEventDelivery方法了彼宠,這是因?yàn)閜ost()之后的一系列方法是在事件發(fā)送的線程中執(zhí)行,而這些狀態(tài)字的賦值與判斷必須處在同一線程中才能有效弟塞,所以事件處理方法必須與post()方法處在同一線程凭峡,所以也就只能是POSTING模式下才能保證。

最后invokeSubscriber方法中在調(diào)用訂閱者方法失敗時(shí)有一個(gè)異常處理方法决记,其代碼如下:

private void handleSubscriberException(Subscription subscription, Object event, Throwable cause) {
    if (event instanceof SubscriberExceptionEvent) {
        if (logSubscriberExceptions) {
            // Don't send another SubscriberExceptionEvent to avoid infinite event recursion, just log
            Log.e(TAG, "SubscriberExceptionEvent subscriber " + subscription.subscriber.getClass()
                    + " threw an exception", cause);
            SubscriberExceptionEvent exEvent = (SubscriberExceptionEvent) event;
            Log.e(TAG, "Initial event " + exEvent.causingEvent + " caused exception in "
                    + exEvent.causingSubscriber, exEvent.throwable);
        }
    } else {
        if (throwSubscriberException) {
            throw new EventBusException("Invoking subscriber failed", cause);
        }
        if (logSubscriberExceptions) {
            Log.e(TAG, "Could not dispatch event: " + event.getClass() + " to subscribing class "
                    + subscription.subscriber.getClass(), cause);
        }
        if (sendSubscriberExceptionEvent) {
            SubscriberExceptionEvent exEvent = new SubscriberExceptionEvent(this, cause, event,
                    subscription.subscriber);
            post(exEvent);
        }
    }
}

代碼邏輯很簡(jiǎn)單摧冀,通常由于是發(fā)送的是我們自定義的Event, 所以會(huì)走else, 接著就會(huì)根據(jù)開關(guān)拋異常打log以及發(fā)送事件等,讀一下這段代碼也有利于我們以后調(diào)試有關(guān)EventBus的相關(guān)問題。

至此事件分發(fā)邏輯就分析結(jié)束了索昂。下一步簡(jiǎn)單介紹和sticky事件相關(guān)的幾個(gè)方法建车。

4. sticky事件

在注冊(cè)部分我們提到過sticky事件,即在訂閱時(shí)即可以接收到之前post出去的sticky事件以及其子類事件椒惨,下面為postSticky()方法:

/**
 * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky
 * event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}.
 */
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);
}

之前可能對(duì)于sticky的解釋不太清楚缤至,不過這個(gè)方法的注釋則對(duì)sticky事件的解釋很清晰,淺顯易懂康谆。接著需要注意方法中的那句注釋领斥,就是需要先將sticky事件保存到stickyEvents中在調(diào)用post()方法,是為了防止remove失敗沃暗,如在事件處理方法中調(diào)用removeStickyEvent月洛,remove在put之前則會(huì)造成remove失敗,post方法返回以后event又被添加到stickyEvents孽锥,與我們期望的就有差別了嚼黔。接下來是sticky事件的remove方法:

 /**
 * Remove and gets the recent sticky event for the given event type.
 *
 * @see #postSticky(Object)
 */
public <T> T removeStickyEvent(Class<T> eventType) {
    synchronized (stickyEvents) {
        return eventType.cast(stickyEvents.remove(eventType));
    }
}

/**
 * Removes the sticky event if it equals to the given event.
 *
 * @return true if the events matched and the sticky event was removed.
 */
public boolean removeStickyEvent(Object event) {
    synchronized (stickyEvents) {
        Class<?> eventType = event.getClass();
        Object existingEvent = stickyEvents.get(eventType);
        if (event.equals(existingEvent)) {
            stickyEvents.remove(eventType);
            return true;
        } else {
            return false;
        }
    }
}
/**
 * Removes all sticky events.
 */
public void removeAllStickyEvents() {
    synchronized (stickyEvents) {
        stickyEvents.clear();
    }
}

這幾個(gè)方法都比較容易理解,不再解釋了惜辑,最后關(guān)于sticky還有一個(gè)get方法唬涧,如下:

/**
 * Gets the most recent sticky event for the given type.
 *
 * @see #postSticky(Object)
 */
public <T> T getStickyEvent(Class<T> eventType) {
    synchronized (stickyEvents) {
        return eventType.cast(stickyEvents.get(eventType));
    }
}

最后貼出來兩個(gè)靜態(tài)方法,就是前面提到的查找事件類型和它所有父類以及它們的實(shí)現(xiàn)的所有接口的方法韵丑,對(duì)于過程的分析無關(guān)緊要爵卒,有興趣的可以看一下源碼虚缎,如下:

/** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */
private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
    synchronized (eventTypesCache) {
        List<Class<?>> eventTypes = eventTypesCache.get(eventClass);
        if (eventTypes == null) {
            eventTypes = new ArrayList<>();
            Class<?> clazz = eventClass;
            while (clazz != null) {
                eventTypes.add(clazz);
                addInterfaces(eventTypes, clazz.getInterfaces());
                clazz = clazz.getSuperclass();
            }
            eventTypesCache.put(eventClass, eventTypes);
        }
        return eventTypes;
    }
}

/** Recurses through super interfaces. */
static void addInterfaces(List<Class<?>> eventTypes, Class<?>[] interfaces) {
    for (Class<?> interfaceClass : interfaces) {
        if (!eventTypes.contains(interfaceClass)) {
            eventTypes.add(interfaceClass);
            addInterfaces(eventTypes, interfaceClass.getInterfaces());
        }
    }
}

到這里撵彻,EventBus這個(gè)類的代碼幾乎全部分析結(jié)束了,還有幾個(gè)方法會(huì)在其他地方用到实牡,不會(huì)影響這個(gè)注冊(cè)和分發(fā)的流程陌僵,所以這里不再貼出其源碼了,有興趣的可以自行查看创坞。

后記

本篇文章主要分析了EventBus這個(gè)類碗短,分析了EventBus基于建造者模式的構(gòu)造器以及整個(gè)流程中的注冊(cè)訂閱者和事件分發(fā)的流程,重點(diǎn)是EventBus中設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu)题涨,以及在過程中對(duì)這些數(shù)據(jù)結(jié)構(gòu)的操作偎谁,從中有很多的借鑒意義。在這整個(gè)流程中還有兩個(gè)重要的步驟纲堵,一是查找所有訂閱方法的信息巡雨,使用反射查找方法信息較為簡(jiǎn)單,由于在EventBus3.0之后使用了注解席函,訂閱方法的名字不再限制為onEvent()铐望,編程更為方便,但是需要對(duì)訂閱方法的查詢,反射由于處于運(yùn)行期影響性能正蛙,所以EventBus選擇在編譯期處理注解督弓,查詢訂閱方法信息,這部分在下一篇文章中做分析乒验。第二個(gè)沒有分析的重要的步驟就是三個(gè)Poster的實(shí)現(xiàn)愚隧,即如何在不同線程中發(fā)送事件,我們都知道android的異步事件都是通過handler處理锻全,EventBus也是基于Handler進(jìn)行異步處理奸攻,但是EventBus使用明顯方便太多,這一切都?xì)w功于Poster虱痕,對(duì)于Poster的分析則放在第三篇文章中分析睹耐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市部翘,隨后出現(xiàn)的幾起案子硝训,更是在濱河造成了極大的恐慌,老刑警劉巖新思,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窖梁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡夹囚,警方通過查閱死者的電腦和手機(jī)纵刘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荸哟,“玉大人假哎,你說我怎么就攤上這事“袄” “怎么了舵抹?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)劣砍。 經(jīng)常有香客問我惧蛹,道長(zhǎng),這世上最難降的妖魔是什么刑枝? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任香嗓,我火速辦了婚禮,結(jié)果婚禮上装畅,老公的妹妹穿的比我還像新娘靠娱。我一直安慰自己,他們只是感情好洁灵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布饱岸。 她就那樣靜靜地躺著掺出,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苫费。 梳的紋絲不亂的頭發(fā)上汤锨,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音百框,去河邊找鬼闲礼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛铐维,可吹牛的內(nèi)容都是我干的柬泽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼嫁蛇,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼锨并!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起睬棚,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤第煮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后抑党,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體包警,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年底靠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了害晦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡暑中,死狀恐怖壹瘟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情痒芝,我是刑警寧澤俐筋,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站严衬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏笆呆。R本人自食惡果不足惜请琳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赠幕。 院中可真熱鬧俄精,春花似錦、人聲如沸榕堰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至圾旨,卻和暖如春踱讨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背砍的。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工痹筛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人廓鞠。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓帚稠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親床佳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子滋早,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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

  • 1.Event概述 1.1EventBus是什么? EventBus是一個(gè)使用發(fā)布-訂閱模式(觀察者模式)進(jìn)行松耦...
    官先生Y閱讀 321評(píng)論 0 0
  • 前言# 之前在聊觀察者模式的時(shí)候說過之后要來個(gè)EventBus的源碼分析砌们,今天就開始補(bǔ)上馆衔。 首先我們做一點(diǎn)準(zhǔn)備工作...
    珠穆朗瑪小王子閱讀 680評(píng)論 0 0
  • 我每周會(huì)寫一篇源代碼分析的文章,以后也可能會(huì)有其他主題.如果你喜歡我寫的文章的話,歡迎關(guān)注我的新浪微博@達(dá)達(dá)達(dá)達(dá)s...
    SkyKai閱讀 24,900評(píng)論 23 184
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)怨绣,斷路器角溃,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • 北京時(shí)間10月17日,搭載兩名宇航員的中國(guó)神舟十一號(hào)載人飛船發(fā)射成功篮撑,進(jìn)入預(yù)定軌道减细。神舟十一號(hào)載人飛船發(fā)射任務(wù)圓滿...
    優(yōu)質(zhì)寫作俠閱讀 1,110評(píng)論 3 8