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)長,但是邏輯很簡(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)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評(píng)論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評(píng)論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,207評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,419評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評(píng)論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評(píng)論 2 374

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

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