Android 原始碼分析之 EventBus 的原始碼解析

Brick發表於2018-08-06

1、EventBus 的使用

1.1 EventBus 簡介

EventBus 是一款用於 Android 的事件釋出-訂閱匯流排,由 GreenRobot 開發,Gihub 地址是:EventBus。它簡化了應用程式內各個元件之間進行通訊的複雜度,尤其是碎片之間進行通訊的問題,可以避免由於使用廣播通訊而帶來的諸多不便。

首先是 EventBus 的三個重要角色

  1. Event:事件,它可以是任意型別,EventBus 會根據事件型別進行全域性的通知。
  2. Subscriber:事件訂閱者,在 EventBus 3.0 之前我們必須定義以onEvent開頭的那幾個方法,分別是 onEvent()onEventMainThread()onEventBackgroundThread()onEventAsync(),而在3.0之後事件處理的方法名可以隨意取,不過需要加上註解@subscribe,並且指定執行緒模型,預設是POSTING
  3. Publisher:事件的釋出者,可以在任意執行緒裡釋出事件。一般情況下,使用 EventBus.getDefault() 就可以得到一個EventBus物件,然後再呼叫 post(Object) 方法釋出事件即可。

其次是 EventBus 的四種執行緒模型(EventBus3.0),分別是:

  1. POSTING:預設,表示事件處理函式的執行緒跟釋出事件的執行緒在同一個執行緒。
  2. MAIN:表示事件處理函式的執行緒在主執行緒(UI)執行緒,因此在這裡不能進行耗時操作。
  3. BACKGROUND:表示事件處理函式的執行緒在後臺執行緒,因此不能進行UI操作。如果釋出事件的執行緒是主執行緒(UI執行緒),那麼事件處理函式將會開啟一個後臺執行緒,如果果釋出事件的執行緒是在後臺執行緒,那麼事件處理函式就使用該執行緒。
  4. ASYNC:表示無論事件釋出的執行緒是哪一個,事件處理函式始終會新建一個子執行緒執行,同樣不能進行UI操作。

1.2 使用 EventBus

在使用之前先要引入如下依賴:

implementation 'org.greenrobot:eventbus:3.1.1'
複製程式碼

然後,我們定義一個事件的封裝物件。在程式內部就使用該物件作為通訊的資訊:

public class MessageWrap {

    public final String message;

    public static MessageWrap getInstance(String message) {
        return new MessageWrap(message);
    }

    private MessageWrap(String message) {
        this.message = message;
    }
}
複製程式碼

然後,我們定義一個 Activity 要拿過來測試事件釋出的效果:

@Route(path = BaseConstants.LIBRARY_EVENT_BUS_ACTIVITY1)
public class EventBusActivity1 extends CommonActivity<ActivityEventBus1Binding> {

    @Override
    protected void doCreateView(Bundle savedInstanceState) {
        // 為按鈕新增新增單擊事件
        getBinding().btnReg.setOnClickListener(v -> EventBus.getDefault().register(this));
        getBinding().btnNav2.setOnClickListener( v ->
                ARouter.getInstance()
                        .build(BaseConstants.LIBRARY_EVENT_BUS_ACTIVITY2)
                        .navigation());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onGetMessage(MessageWrap message) {
        getBinding().tvMessage.setText(message.message);
    }
}
複製程式碼

這裡我們當按下按鈕的時候向 EventBus 註冊監聽,然後按下另一個按鈕的時候跳轉到拎一個 Activity,並在另一個 Activity 釋出我們輸入的事件。在上面的 Activity 中,我們會新增一個監聽的方法,即 onGetMessage(),這裡我們需要為其加入註解 @Subscribe 並指定執行緒模型為主執行緒 MAIN。最後,就是在 Activity 的 onDestroy() 方法中取消註冊該 Activity。

下面是另一個 Activity 的定義,在這個 Activity 中,我們當按下按鈕的時候從 EditText 中取出內容並進行釋出,然後我們退出到之前的 Activity,以測試是否正確監聽到釋出的內容:

@Route(path = BaseConstants.LIBRARY_EVENT_BUS_ACTIVITY2)
public class EventBusActivity2 extends CommonActivity<ActivityEventBus2Binding> {

    @Override
    protected void doCreateView(Bundle savedInstanceState) {
        getBinding().btnPublish.setOnClickListener(v -> publishContent());
    }

    private void publishContent() {
        String msg = getBinding().etMessage.getText().toString();
        EventBus.getDefault().post(MessageWrap.getInstance(msg));
        ToastUtils.makeToast("Published : " + msg);
    }
}
複製程式碼

根據測試的結果,我們的確成功地接收到了傳送的資訊。

1.3 黏性事件

所謂的黏性事件,就是指傳送了該事件之後再訂閱者依然能夠接收到的事件。使用黏性事件的時候有兩個地方需要做些修改。一個是訂閱事件的地方,這裡我們在先開啟的 Activity 中註冊監聽黏性事件:

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onGetStickyEvent(MessageWrap message) {
    String txt = "Sticky event: " + message.message;
    getBinding().tvStickyMessage.setText(txt);
}
複製程式碼

另一個是釋出事件的地方,這裡我們在新的開的 Activity 中釋出黏性事件。即呼叫 EventBus 的 postSticky() 方法來發布事件:

private void publishStickyontent() {
    String msg = getBinding().etMessage.getText().toString();
    EventBus.getDefault().postSticky(MessageWrap.getInstance(msg));
    ToastUtils.makeToast("Published : " + msg);
}
複製程式碼

按照上面的模式,我們先在第一個 Activity 中開啟第二個 Activity,然後在第二個 Activity 中釋出黏性事件,並回到第一個 Activity 註冊 EventBus。根據測試結果,當按下注冊按鈕的時候,會立即觸發上面的訂閱方法從而獲取到了黏性事件。

1.4 優先順序

@Subscribe 註解中總共有3個引數,上面我們用到了其中的兩個,這裡我們使用以下第三個引數,即 priority。它用來指定訂閱方法的優先順序,是一個整數型別的值,預設是 0,值越大表示優先順序越大。在某個事件被髮布出來的時候,優先順序較高的訂閱方法會首先接受到事件。

為了對優先順序進行測試,這裡我們需要對上面的程式碼進行一些修改。這裡,我們使用一個布林型別的變數來判斷是否應該取消事件的分發。我們在一個較高優先順序的方法中通過該布林值進行判斷,如果未 true 就停止該事件的繼續分發,從而通過低優先順序的訂閱方法無法獲取到事件來證明優先順序較高的訂閱方法率先獲取到了事件。

這裡有幾個地方需要注意

  1. 只有當兩個訂閱方法使用相同的ThreadMode引數的時候,它們的優先順序才會與priority指定的值一致;
  2. 只有當某個訂閱方法的ThreadMode引數為POSTING的時候,它才能停止該事件的繼續分發。

所以,根據以上的內容,我們需要對程式碼做如下的調整:

// 用來判斷是否需要停止事件的繼續分發
private boolean stopDelivery = false;

@Override
protected void doCreateView(Bundle savedInstanceState) {
    // ...

    getBinding().btnStop.setOnClickListener(v -> stopDelivery = true);
}

@Subscribe(threadMode = ThreadMode.POSTING, priority = 0)
public void onGetMessage(MessageWrap message) {
    getBinding().tvMessage.setText(message.message);
}

// 訂閱方法,需要與上面的方法的threadMode一致,並且優先順序略高
@Subscribe(threadMode = ThreadMode.POSTING, sticky = true, priority = 1)
public void onGetStickyEvent(MessageWrap message) {
    String txt = "Sticky event: " + message.message;
    getBinding().tvStickyMessage.setText(txt);
    if (stopDelivery) {
        // 終止事件的繼續分發
        EventBus.getDefault().cancelEventDelivery(message);
    }
}
複製程式碼

即我們在之前的程式碼之上增加了一個按鈕,用來將 stopDelivery 的值置為 true。該欄位隨後將會被用來判斷是否要終止事件的繼續分發,因為我們需要在程式碼中停止事件的繼續分發,所以,我們需要將上面的兩個訂閱方法的 threadMode 的值都置為ThreadMode.POSTING

按照,上面的測試方式,首先我們在當前的 Activity 註冊監聽,然後跳轉到另一個 Activity,釋出事件並返回。第一次的時候,這裡的兩個訂閱方法都會被觸發。然後,我們按下停止分發的按鈕,並再次執行上面的邏輯,此時只有優先順序較高的方法獲取到了事件並將該事件終止。

上面的內容是 EventBus 的基本使用方法,相關的原始碼參考:Github

2、原始碼分析

在分析 EventBus 原始碼的時候,我們先從獲取一個 EventBus 例項的方法入手,然後再分別看一下它的註冊、取消註冊、釋出事件以及觸發觀察方法的程式碼是如何實現的。在下面的文章中我們將會回答以下幾個問題:

  1. 在 EventBus 中,使用 @Subscribe 註解的時候指定的 ThreadMode 是如何實現在不同執行緒間傳遞資料的?
  2. 使用註解和反射的時候的效率問題,是否會像 Guava 的 EventBus 一樣有快取優化?
  3. 黏性事件是否是通過內部維護了之前釋出的資料來實現的,是否使用了快取?

2.1 獲取例項的過程

在建立 EventBus 例項的時候,一種方式是按照我們上面的形式,通過 EventBus 的靜態方法 getDefault() 來獲取一個例項。getDefault() 本身會呼叫其內部的構造方法,通過傳入一個預設 的EventBusBuilder 來建立 EventBus。此外,我們還可以直接通過 EventBus 的 builder() 方法獲取一個 EventBusBuilder 的例項,然後通過該構建者模式來個性化地定製自己的 EventBus。即:

// 靜態的單例例項
static volatile EventBus defaultInstance;

// 預設的構建者
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

// 實際上使用了DCL雙檢鎖機制,這裡簡化了一下
public static EventBus getDefault() {
    if (defaultInstance == null) defaultInstance = new EventBus();
    return defaultInstance;
}

public EventBus() {
    this(DEFAULT_BUILDER);
}

// 呼叫getDefault的時候,最終會呼叫該方法,使用DEFAULT_BUILDER建立一個例項
EventBus(EventBusBuilder builder) {
    // ...
}

// 也可以使用下面的方法獲取一個構建者,然後使用它來個性化定製EventBus
public static EventBusBuilder builder() {
    return new EventBusBuilder();
}
複製程式碼

2.2 註冊

當呼叫 EventBus 例項的 register() 方法的時候,會執行下面的邏輯:

public void register(Object subscriber) {
    // 首席會獲取註冊的物件的型別
    Class<?> subscriberClass = subscriber.getClass();
    // 然後獲取註冊的物件的訂閱方法
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    // 對當前例項加鎖,並不斷執行監聽的邏輯
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            // 對訂閱方法進行註冊
            subscribe(subscriber, subscriberMethod);
        }
    }
}
複製程式碼

這裡的 SubscriberMethod 封裝了訂閱方法(使用 @Subscribe 註解的方法)型別的資訊,它的定義如下所示。從下面可以的程式碼中我們可以看出,實際上該類就是通過幾個欄位來儲存 @Subscribe 註解中指定的型別資訊,以及一個方法的型別變數。

public class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    final int priority;
    final boolean sticky;

    // ...
}
複製程式碼

register() 方法通過 subscriberMethodFinder 例項的 findSubscriberMethods() 方法來獲取該觀察者型別中的所有訂閱方法,然後將所有的訂閱方法分別進行訂閱。下面我們先看下查詢訂閱者的方法。

查詢訂閱者的訂閱方法

下面是 SubscriberMethodFinder 中的 findSubscriberMethods() 方法:

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(...);
    } else {
        // 將獲取到的訂閱方法放置到快取當中
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}
複製程式碼

這裡我們先從快取當中嘗試獲取某個觀察者中的所有訂閱方法,如果沒有可用快取的話就從該類中查詢訂閱方法,並在返回結果之前將這些方法資訊放置到快取當中。這裡的 ignoreGeneratedIndex 參數列示是否忽略註解器生成的 MyEventBusIndex,該值預設為 false。然後,我們會進入到下面的方法中獲取訂閱方法資訊:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    // 這裡通過FindState物件來儲存找到的方法資訊
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    // 這裡是一個迴圈操作,會從當前類開始遍歷該類的所有父類
    while (findState.clazz != null) {
        // 獲取訂閱者資訊
        findState.subscriberInfo = getSubscriberInfo(findState); // 1
        if (findState.subscriberInfo != null) {
            // 如果使用了MyEventBusIndex,將會進入到這裡並獲取訂閱方法資訊
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            // 未使用MyEventBusIndex將會進入這裡使用反射獲取方法資訊
            findUsingReflectionInSingleClass(findState); // 2
        }
        // 將findState.clazz設定為當前的findState.clazz的父類
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}
複製程式碼

在上面的程式碼中,會從當前訂閱者類開始直到它最頂層的父類進行遍歷來獲取訂閱方法資訊。這裡在迴圈的內部會根據我們是否使用了 MyEventBusIndex 走兩條路線,對於我們沒有使用它的,會直接使用反射來獲取訂閱方法資訊,即進入2處。

下面是使用反射從訂閱者中得到訂閱方法的程式碼:

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // 獲取該類中宣告的所有方法
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        methods = findState.clazz.getMethods();
        findState.skipSuperClasses = true;
    }
    // 對方法進行遍歷判斷
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        // 這裡會對方法的修飾符進行校驗
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            // 這裡對方法的輸入引數進行校驗
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
                // 獲取方法的註解,用來從註解中獲取註解的宣告資訊
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    // 獲取該方法的第一個引數
                    Class<?> eventType = parameterTypes[0];
                    if (findState.checkAdd(method, eventType)) {
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        // 最終將封裝之後的方法塞入到列表中
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(...);
            }
        } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(...);
        }
    }
}
複製程式碼

這裡會對當前類中宣告的所有方法進行校驗,並將符合要求的方法的資訊封裝成一個SubscriberMethod物件塞到列表中。

註冊訂閱方法

直到了如何拿到所有的訂閱方法之後,我們回到之前的程式碼,看下訂閱過程中的邏輯:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    // 將所有的觀察者和訂閱方法封裝成一個Subscription物件
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod); // 1
    // 嘗試從快取中根據事件型別來獲取所有的Subscription物件
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); // 2
    if (subscriptions == null) {
        // 指定的事件型別沒有對應的觀察物件的時候
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException(...);
        }
    }

    // 這裡會根據新加入的方法的優先順序決定插入到佇列中的位置
    int size = subscriptions.size(); // 2
    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); // 3
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    // 如果是黏性事件還要進行如下的處理
    if (subscriberMethod.sticky) { // 4
        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);
        }
    }
}
複製程式碼

這裡涉及到了幾個集合,它們是用來做快取的,還有就是來維護觀察者、事件型別和訂閱方法之間的關係的。註冊觀察的方法比較長,我們可以一點一點來看。首先,會在程式碼1處將觀察者和訂閱方法封裝成一個 Subscription 物件。然後,在2處用到了 CopyOnWriteArrayList 這個集合,它是一種適用於多讀寫少場景的資料結構,是一種執行緒安全的陣列型的資料結構,主要用來儲存一個事件型別所對應的全部的 Subscription 物件。EventBus在這裡通過一個 Map<Class<?>, CopyOnWriteArrayList<Subscription>> 型別的雜湊表來維護這個對映關係。然後,我們的程式執行到2處,在這裡會對 Subscription 物件的列表進行遍歷,並根據訂閱方法的優先順序,為當前的 Subscription 物件尋找一個合適的位置。3的地方主要的邏輯是獲取指定的觀察者對應的全部的觀察事件型別,這裡也是通過一個雜湊表來維護這種對映關係的。然後,在程式碼 4 處,程式會根據當前的訂閱方法是否是黏性的,來決定是否將當前快取中的資訊傳送給新訂閱的方法。這裡會通過 checkPostStickyEventToSubscription() 方法來傳送資訊,它內部的實現的邏輯和 post() 方法類似,我們不再進行說明。

取消註冊的邏輯比較比較簡單,基本上就是註冊操作反過來——將當前訂閱方法的資訊從快取中踢出來,我們不再進行分分析。下面我們分析另一個比較重要的地方,即傳送事件相關的邏輯。

2.3 通知

通知的邏輯相對來說會比較複雜一些,因為這裡面涉及一些執行緒之間的操作。我們看下下面的程式碼吧:

public void post(Object event) {
    // 這裡從執行緒區域性變數中取出當前執行緒的狀態資訊
    PostingThreadState postingState = currentPostingThreadState.get();
    // 這裡是以上執行緒區域性變數內部維護的一個事件佇列
    List<Object> eventQueue = postingState.eventQueue;
    // 將當前要傳送的事件加入到佇列中
    eventQueue.add(event);

    if (!postingState.isPosting) {
        postingState.isMainThread = isMainThread();
        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); // 1
            }
        } finally {
            // 恢復當前執行緒的資訊
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}
複製程式碼

這裡的 currentPostingThreadState 是一個 ThreadLocal 型別的變數,其中儲存了對應於當前執行緒的 PostingThreadState 物件,該物件中儲存了當前執行緒對應的事件列表和執行緒的狀態資訊等。從上面的程式碼中可以看出,post() 方法會在1處不斷從當前執行緒對應的佇列中取出事件並進行釋出。下面我們看以下這裡的 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) {
            logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent 
                && eventClass != NoSubscriberEvent.class 
                && eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}
複製程式碼

在上面的程式碼中,我們會根據 eventInheritance 的值決定是否要同時遍歷當前事件的所有父類的事件資訊並進行分發。如果設定為 true 就將執行這一操作,並最終使用 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;
}
複製程式碼

在上面的程式碼中,我們會通過傳入的事件型別到快取中取尋找它對應的全部的 Subscription,然後對得到的 Subscription 列表進行遍歷,並依次呼叫 postToSubscription() 方法執行事件的釋出操作。下面是 postToSubscription() 方法的程式碼,這裡我們會根據訂閱方法指定的 threadMode資訊來執行不同的釋出策略。

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 {
                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(...);
    }
}
複製程式碼

在上面的方法中,會根據當前的執行緒狀態和訂閱方法指定 的 threadMode 資訊來決定合適觸發方法。這裡的 invokeSubscriber() 會在當前執行緒中立即呼叫反射來觸發指定的觀察者的訂閱方法。否則會根據具體的情況將事件加入到不同的佇列中進行處理。這裡的mainThreadPoster 最終繼承自 Handler,當呼叫它的 enqueue() 方法的時候,它會傳送一個事件並在它自身的 handleMessage() 方法中從佇列中取值並進行處理,從而達到在主執行緒中分發事件的目的。這裡的 backgroundPoster 實現了 Runnable 介面,它會在呼叫 enqueue() 方法的時候,拿到 EventBus 的 ExecutorService 例項,並使用它來執行自己。在它的 run() 方法中會從佇列中不斷取值來進行執行。

總結

以上就是Android中的EventBus的原始碼分析,這裡我們回答之前提出的幾個問題來作結:

  1. 在EventBus中,使用 @Subscribe 註解的時候指定的 ThreadMode 是如何實現在不同執行緒間傳遞資料的?

要求主執行緒中的事件通過 Handler 來實現在主執行緒中執行,非主執行緒的方法會使用 EventBus 內部的 ExecutorService 來執行。實際在觸發方法的時候會根據當前執行緒的狀態和訂閱方法的 ThreadMode 指定的執行緒狀態來決定何時觸發方法。非主執行緒的邏輯會在 post() 的時候加入到一個佇列中被隨後執行。

  1. 使用註解和反射的時候的效率問題,是否會像 Guava 的 EventBus 一樣有快取優化?

內部使用了快取,確切來說就是維護了一些對映的關係。但是它的快取沒有像 Guava 一樣使用軟引用之類方式進行優化,即一直是強引用型別的。

  1. 黏性事件是否是通過內部維護了之前釋出的資料來實現的,是否使用了快取?

黏性事件會通過 EventBus 內部維護的一個事件型別-黏性事件的雜湊表儲存,當註冊一個觀察者的時候,如果發現了它內部有黏性事件監聽,會執行 post() 類似的邏輯將事件立即傳送給該觀察者。


如果您喜歡我的文章,可以在以下平臺關注我:

相關文章