EventBus 原理解析

-Sloth-發表於2020-11-01

EventBus 是一款在 Android 開發中使用的釋出/訂閱事件匯流排框架,基於觀察者模式,將事件的接收者和傳送者分開,簡化了元件之間的通訊,使用簡單、效率高、體積小!下邊是官方的 EventBus 原理圖:

EventBus 原理解析

EventBus 的用法可以參考官網,這裡不做過多的說明。本文主要是從 EventBus 使用的方式入手,來分析 EventBus 背後的實現原理,以下內容基於eventbus:3.1.1版本,主要包括如下幾個方面的內容:

  • Subscribe註解
  • 註冊事件訂閱方法
  • 取消註冊
  • 傳送事件
  • 事件處理
  • 粘性事件
  • Subscriber Index
  • 核心流程梳理

一、Subscribe註解

EventBus3.0 開始用Subscribe註解配置事件訂閱方法,不再使用方法名了,例如:

@Subscribe
public void handleEvent(String event) {
    // do something
}
複製程式碼

其中事件型別可以是 Java 中已有的型別或者我們自定義的型別。 具體看下Subscribe註解的實現:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    // 指定事件訂閱方法的執行緒模式,即在那個執行緒執行事件訂閱方法處理事件,預設為POSTING
    ThreadMode threadMode() default ThreadMode.POSTING;
    // 是否支援粘性事件,預設為false
    boolean sticky() default false;
    // 指定事件訂閱方法的優先順序,預設為0,如果多個事件訂閱方法可以接收相同事件的,則優先順序高的先接收到事件
    int priority() default 0;
}
複製程式碼

所以在使用Subscribe註解時可以根據需求指定threadModestickypriority三個屬性。

其中threadMode屬性有如下幾個可選值:

  • ThreadMode.POSTING,預設的執行緒模式,在那個執行緒傳送事件就在對應執行緒處理事件,避免了執行緒切換,效率高。
  • ThreadMode.MAIN,如在主執行緒(UI執行緒)傳送事件,則直接在主執行緒處理事件;如果在子執行緒傳送事件,則先將事件入佇列,然後通過 Handler 切換到主執行緒,依次處理事件。
  • ThreadMode.MAIN_ORDERED,無論在那個執行緒傳送事件,都先將事件入佇列,然後通過 Handler 切換到主執行緒,依次處理事件。
  • ThreadMode.BACKGROUND,如果在主執行緒傳送事件,則先將事件入佇列,然後通過執行緒池依次處理事件;如果在子執行緒傳送事件,則直接在傳送事件的執行緒處理事件。
  • ThreadMode.ASYNC,無論在那個執行緒傳送事件,都將事件入佇列,然後通過執行緒池處理。

二、註冊事件訂閱方法

註冊事件的方式如下:

EventBus.getDefault().register(this);
複製程式碼

其中getDefault()是一個單例方法,保證當前只有一個EventBus例項:

public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }
複製程式碼

繼續看new EventBus()做了些什麼:

public EventBus() {
        this(DEFAULT_BUILDER);
    }
複製程式碼

在這裡又呼叫了EventBus的另一個建構函式來完成它相關屬性的初始化:

EventBus(EventBusBuilder builder) {
        logger = builder.getLogger();
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadSupport = builder.getMainThreadSupport();
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        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;
        executorService = builder.executorService;
    }
複製程式碼

DEFAULT_BUILDER就是一個預設的EventBusBuilder

private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
複製程式碼

如果有需要的話,我們也可以通過配置EventBusBuilder來更改EventBus的屬性,例如用如下方式註冊事件:

EventBus.builder()
        .eventInheritance(false)
        .logSubscriberExceptions(false)
        .build()
        .register(this);
複製程式碼

有了EventBus的例項就可以進行註冊了:

public void register(Object subscriber) {
        // 得到當前要註冊類的Class物件
        Class<?> subscriberClass = subscriber.getClass();
        // 根據Class查詢當前類中訂閱了事件的方法集合,即使用了Subscribe註解、有public修飾符、一個引數的方法
        // SubscriberMethod類主要封裝了符合條件方法的相關資訊:
        // Method物件、執行緒模式、事件型別、優先順序、是否是粘性事等
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            // 迴圈遍歷訂閱了事件的方法集合,以完成註冊
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
複製程式碼

可以看到register()方法主要分為查詢和註冊兩部分,首先來看查詢的過程,從findSubscriberMethods()開始:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        // METHOD_CACHE是一個ConcurrentHashMap,直接儲存了subscriberClass和對應SubscriberMethod的集合,以提高註冊效率,賦值重複查詢。
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        // 由於使用了預設的EventBusBuilder,則ignoreGeneratedIndex屬性預設為false,即是否忽略註解生成器
        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        // 如果對應類中沒有符合條件的方法,則丟擲異常
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            // 儲存查詢到的訂閱事件的方法
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }
複製程式碼

findSubscriberMethods()流程很清晰,即先從快取中查詢,如果找到則直接返回,否則去做下一步的查詢過程,然後快取查詢到的集合,根據上邊的註釋可知findUsingInfo()方法會被呼叫:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        // 初始狀態下findState.clazz就是subscriberClass
        while (findState.clazz != null) {
            findState.subscriberInfo = getSubscriberInfo(findState);
            // 條件不成立
            if (findState.subscriberInfo != null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                // 通過反射查詢訂閱事件的方法
                findUsingReflectionInSingleClass(findState);
            }
            // 修改findState.clazz為subscriberClass的父類Class,即需要遍歷父類
            findState.moveToSuperclass();
        }
        // 查詢到的方法儲存在了FindState例項的subscriberMethods集合中。
        // 使用subscriberMethods構建一個新的List<SubscriberMethod>
        // 釋放掉findState
        return getMethodsAndRelease(findState);
    }
複製程式碼

findUsingInfo()方法會在當前要註冊的類以及其父類中查詢訂閱事件的方法,這裡出現了一個FindState類,它是SubscriberMethodFinder的內部類,用來輔助查詢訂閱事件的方法,具體的查詢過程在findUsingReflectionInSingleClass()方法,它主要通過反射查詢訂閱事件的方法:

private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        // 迴圈遍歷當前類的方法,篩選出符合條件的
        for (Method method : methods) {
            // 獲得方法的修飾符
            int modifiers = method.getModifiers();
            // 如果是public型別,但非abstract、static等
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                // 獲得當前方法所有引數的型別
                Class<?>[] parameterTypes = method.getParameterTypes();
                // 如果當前方法只有一個引數
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    // 如果當前方法使用了Subscribe註解
                    if (subscribeAnnotation != null) {
                        // 得到該引數的型別
                        Class<?> eventType = parameterTypes[0];
                        // checkAdd()方法用來判斷FindState的anyMethodByEventType map是否已經新增過以當前eventType為key的鍵值對,沒新增過則返回true
                        if (findState.checkAdd(method, eventType)) {
                             // 得到Subscribe註解的threadMode屬性值,即執行緒模式
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            // 建立一個SubscriberMethod物件,並新增到subscriberMethods集合
                            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("@Subscribe method " + methodName +
                            "must have exactly 1 parameter but has " + parameterTypes.length);
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }
複製程式碼

到此register()方法中findSubscriberMethods()流程就分析完了,我們已經找到了當前註冊類及其父類中訂閱事件的方法的集合。接下來分析具體的註冊流程,即register()中的subscribe()方法:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        // 得到當前訂閱了事件的方法的引數型別
        Class<?> eventType = subscriberMethod.eventType;
        // Subscription類儲存了要註冊的類物件以及當前的subscriberMethod
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        // subscriptionsByEventType是一個HashMap,儲存了以eventType為key,Subscription物件集合為value的鍵值對
        // 先查詢subscriptionsByEventType是否存在以當前eventType為key的值
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        // 如果不存在,則建立一個subscriptions,並儲存到subscriptionsByEventType
        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);
            }
        }
        // 新增上邊建立的newSubscription物件到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;
            }
        }
        // typesBySubscribere也是一個HashMap,儲存了以當前要註冊類的物件為key,註冊類中訂閱事件的方法的引數型別的集合為value的鍵值對
        // 查詢是否存在對應的引數型別集合
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        // 不存在則建立一個subscribedEvents,並儲存到typesBySubscriber
        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);
            }
        }
    }
複製程式碼

這就是註冊的核心流程,所以subscribe()方法主要是得到了subscriptionsByEventTypetypesBySubscriber兩個 HashMap。我們在傳送事件的時候要用到subscriptionsByEventType,完成事件的處理。當取消 EventBus 註冊的時候要用到typesBySubscribersubscriptionsByEventType,完成相關資源的釋放。

三、取消註冊

接下來看,EventBus 如何取消註冊:

EventBus.getDefault().unregister(this);
複製程式碼

核心的方法就是unregister()

public synchronized void unregister(Object subscriber) {
        // 得到當前註冊類物件 對應的 訂閱事件方法的引數型別 的集合
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            // 遍歷引數型別集合,釋放之前快取的當前類中的Subscription
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            // 刪除以subscriber為key的鍵值對
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }
複製程式碼

內容很簡單,繼續看unsubscribeByEventType()方法:

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
        // 得到當前引數型別對應的Subscription集合
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            // 遍歷Subscription集合
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                // 如果當前subscription物件對應的註冊類物件 和 要取消註冊的註冊類物件相同,則刪除當前subscription物件
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }
複製程式碼

所以在unregister()方法中,釋放了typesBySubscribersubscriptionsByEventType中快取的資源。

四、傳送事件

當傳送一個事件的時候,我們可以通過如下方式:

EventBus.getDefault().post("Hello World!")
複製程式碼

可以看到,傳送事件就是通過post()方法完成的:

public void post(Object event) {
        // currentPostingThreadState是一個PostingThreadState型別的ThreadLocal
        // PostingThreadState類儲存了事件佇列和執行緒模式等資訊
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        // 將要傳送的事件新增到事件佇列
        eventQueue.add(event);
        // isPosting預設為false
        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()) {
                    // 傳送單個事件
                    // eventQueue.remove(0),從事件佇列移除事件
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }
複製程式碼

所以post()方法先將傳送的事件儲存的事件佇列,然後通過迴圈出佇列,將事件交給postSingleEvent()方法處理:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        // eventInheritance預設為true,表示是否向上查詢事件的父類
        if (eventInheritance) {
            // 查詢當前事件型別的Class,連同當前事件型別的Class儲存到集合
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            // 遍歷Class集合,繼續處理事件
            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));
            }
        }
    }
複製程式碼

postSingleEvent()方法中,根據eventInheritance屬性,決定是否向上遍歷事件的父型別,然後用postSingleEventForEventType()方法進一步處理事件:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            // 獲取事件型別對應的Subscription集合
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        // 如果已訂閱了對應型別的事件
        if (subscriptions != null && !subscriptions.isEmpty()) {
            for (Subscription subscription : subscriptions) {
                // 記錄事件
                postingState.event = event;
                // 記錄對應的subscription
                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;
    }
複製程式碼

postSingleEventForEventType()方法核心就是遍歷傳送的事件型別對應的Subscription集合,然後呼叫postToSubscription()方法處理事件。

五、處理事件

接著上邊的繼續分析,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 {
                     // 如果是在子執行緒傳送事件,則將事件入佇列,通過Handler切換到主執行緒執行處理事件
                    // mainThreadPoster 不為空
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            // 無論在那個執行緒傳送事件,都先將事件入佇列,然後通過 Handler 切換到主執行緒,依次處理事件。
            // mainThreadPoster 不為空
            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("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }
複製程式碼

可以看到,postToSubscription()方法就是根據訂閱事件方法的執行緒模式、以及傳送事件的執行緒來判斷如何處理事件,至於處理方式主要有兩種: 一種是在相應執行緒直接通過invokeSubscriber()方法,用反射來執行訂閱事件的方法,這樣傳送出去的事件就被訂閱者接收並做相應處理了:

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

另外一種是先將事件入佇列(其實底層是一個List),然後做進一步處理,我們以mainThreadPoster.enqueue(subscription, event)為例簡單的分析下,其中mainThreadPosterHandlerPoster類的一個例項,來看該類的主要實現:

public class HandlerPoster extends Handler implements Poster {
    private final PendingPostQueue queue;
    private boolean handlerActive;
    ......
    public void enqueue(Subscription subscription, Object event) {
        // 用subscription和event封裝一個PendingPost物件
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            // 入佇列
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                // 傳送開始處理事件的訊息,handleMessage()方法將被執行,完成從子執行緒到主執行緒的切換
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

    @Override
    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            long started = SystemClock.uptimeMillis();
            // 死迴圈遍歷佇列
            while (true) {
                // 出佇列
                PendingPost pendingPost = queue.poll();
                ......
                // 進一步處理pendingPost
                eventBus.invokeSubscriber(pendingPost);
                ......
            }
        } finally {
            handlerActive = rescheduled;
        }
    }
}
複製程式碼

所以HandlerPosterenqueue()方法主要就是將subscriptionevent物件封裝成一個PendingPost物件,然後儲存到佇列裡,之後通過Handler切換到主執行緒,在handleMessage()方法將中將PendingPost物件迴圈出佇列,交給invokeSubscriber()方法進一步處理:

void invokeSubscriber(PendingPost pendingPost) {
        Object event = pendingPost.event;
        Subscription subscription = pendingPost.subscription;
        // 釋放pendingPost引用的資源
        PendingPost.releasePendingPost(pendingPost);
        if (subscription.active) {
            // 用反射來執行訂閱事件的方法
            invokeSubscriber(subscription, event);
        }
    }
複製程式碼

這個方法很簡單,主要就是從pendingPost中取出之前儲存的eventsubscription,然後用反射來執行訂閱事件的方法,又回到了第一種處理方式。所以mainThreadPoster.enqueue(subscription, event)的核心就是先將將事件入佇列,然後通過Handler從子執行緒切換到主執行緒中去處理事件。

backgroundPoster.enqueue()asyncPoster.enqueue也類似,內部都是先將事件入佇列,然後再出佇列,但是會通過執行緒池去進一步處理事件。

六、粘性事件

一般情況,我們使用 EventBus 都是準備好訂閱事件的方法,然後註冊事件,最後在傳送事件,即要先有事件的接收者。但粘性事件卻恰恰相反,我們可以先傳送事件,後續再準備訂閱事件的方法、註冊事件。

由於這種差異,我們分析粘性事件原理時,先從事件傳送開始,傳送一個粘性事件通過如下方式:

EventBus.getDefault().postSticky("Hello World!");
複製程式碼

來看postSticky()方法是如何實現的:

public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        post(event);
    }
複製程式碼

postSticky()方法主要做了兩件事,先將事件型別和對應事件儲存到stickyEvents中,方便後續使用;然後執行post(event)繼續傳送事件,這個post()方法就是之前傳送的post()方法。所以,如果在傳送粘性事件前,已經有了對應型別事件的訂閱者,及時它是非粘性的,依然可以接收到傳送出的粘性事件。

傳送完粘性事件後,再準備訂閱粘性事件的方法,並完成註冊。核心的註冊事件流程還是我們之前的register()方法中的subscribe()方法,前邊分析subscribe()方法時,有一段沒有分析的程式碼,就是用來處理粘性事件的:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        ......
        ......
        ......
        // 如果當前訂閱事件的方法的Subscribe註解的sticky屬性為true,即該方法可接受粘性事件
        if (subscriberMethod.sticky) {
            // 預設為true,表示是否向上查詢事件的父類
            if (eventInheritance) {
                // stickyEvents就是傳送粘性事件時,儲存了事件型別和對應事件
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    // 如果candidateEventType是eventType的子類或
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        // 獲得對應的事件
                        Object stickyEvent = entry.getValue();
                        // 處理粘性事件
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }
複製程式碼

可以看到,處理粘性事件就是在 EventBus 註冊時,遍歷stickyEvents,如果當前要註冊的事件訂閱方法是粘性的,並且該方法接收的事件型別和stickyEvents中某個事件型別相同或者是其父類,則取出stickyEvents中對應事件型別的具體事件,做進一步處理。繼續看checkPostStickyEventToSubscription()處理方法:

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            postToSubscription(newSubscription, stickyEvent, isMainThread());
        }
    }
複製程式碼

最終還是通過postToSubscription()方法完成粘性事件的處理,這就是粘性事件的整個處理流程。

七、Subscriber Index

回顧之前分析的 EventBus 註冊事件流程,主要是在專案執行時通過反射來查詢訂事件的方法資訊,這也是預設的實現,如果專案中有大量的訂閱事件的方法,必然會對專案執行時的效能產生影響。其實除了在專案執行時通過反射查詢訂閱事件的方法資訊,EventBus 還提供了在專案編譯時通過註解處理器查詢訂閱事件方法資訊的方式,生成一個輔助的索引類來儲存這些資訊,這個索引類就是Subscriber Index,其實和 ButterKnife 的原理類似。

要在專案編譯時查詢訂閱事件的方法資訊,首先要在 app 的 build.gradle 中加入如下配置:

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                // 根據專案實際情況,指定輔助索引類的名稱和包名
                arguments = [ eventBusIndex : 'com.shh.sometest.MyEventBusIndex' ]
            }
        }
    }
}
dependencies {
    compile 'org.greenrobot:eventbus:3.1.1'
    // 引入註解處理器
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}
複製程式碼

然後在專案的 Application 中新增如下配置,以生成一個預設的 EventBus 單例:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
複製程式碼

之後的用法就和我們平時使用 EventBus 一樣了。當專案編譯時會在生成對應的MyEventBusIndex類:

MyEventBusIndex
對應的原始碼如下:

public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("changeText", String.class),
        }));

    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}
複製程式碼

其中SUBSCRIBER_INDEX是一個HashMap,儲存了當前註冊類的 Class 型別和其中事件訂閱方法的資訊。

接下來分析下使用 Subscriber Index 時 EventBus 的註冊流程,我們先分析:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
複製程式碼

首先建立一個EventBusBuilder,然後通過addIndex()方法新增索引類的例項:

public EventBusBuilder addIndex(SubscriberInfoIndex index) {
        if (subscriberInfoIndexes == null) {
            subscriberInfoIndexes = new ArrayList<>();
        }
        subscriberInfoIndexes.add(index);
        return this;
    }
複製程式碼

即把生成的索引類的例項儲存在subscriberInfoIndexes集合中,然後用installDefaultEventBus()建立預設的 EventBus例項:

public EventBus installDefaultEventBus() {
        synchronized (EventBus.class) {
            if (EventBus.defaultInstance != null) {
                throw new EventBusException("Default instance already exists." +
                        " It may be only set once before it's used the first time to ensure consistent behavior.");
            }
            EventBus.defaultInstance = build();
            return EventBus.defaultInstance;
        }
    }
複製程式碼
public EventBus build() {
        // this 代表當前EventBusBuilder物件
        return new EventBus(this);
    }
複製程式碼

即用當前EventBusBuilder物件建立一個 EventBus 例項,這樣我們通過EventBusBuilder配置的 Subscriber Index 也就傳遞到了EventBus例項中,然後賦值給EventBus的 defaultInstance成員變數。之前我們在分析 EventBus 的getDefault()方法時已經見到了defaultInstance

public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }
複製程式碼

所以在 Application 中生成了 EventBus 的預設單例,這樣就保證了在專案其它地方執行EventBus.getDefault()就能得到唯一的 EventBus 例項!之前在分析註冊流程時有一個 方法findUsingInfo()

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            // 查詢SubscriberInfo
            findState.subscriberInfo = getSubscriberInfo(findState);
            // 條件成立
            if (findState.subscriberInfo != null) {
                // 獲得當前註冊類中所有訂閱了事件的方法
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    // findState.checkAdd()之前已經分析過了,即是否在FindState的anyMethodByEventType已經新增過以當前eventType為key的鍵值對,沒新增過返回true
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        // 將subscriberMethod物件新增到subscriberMethods集合
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }
複製程式碼

由於我們現在使用了 Subscriber Index 所以不會通過findUsingReflectionInSingleClass()來反射解析訂閱事件的方法。我們重點來看getSubscriberInfo()都做了些什麼:

private SubscriberInfo getSubscriberInfo(FindState findState) {
        // 該條件不成立
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }
        // 該條件成立
        if (subscriberInfoIndexes != null) {
            // 遍歷索引類例項集合
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                // 根據註冊類的 Class 類查詢SubscriberInfo
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }
複製程式碼

subscriberInfoIndexes就是在前邊addIndex()方法中建立的,儲存了專案中的索引類例項,即MyEventBusIndex的例項,繼續看索引類的getSubscriberInfo()方法,來到了MyEventBusIndex類中:

@Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
複製程式碼

即根據註冊類的 Class 型別從 SUBSCRIBER_INDEX 查詢對應的SubscriberInfo,如果我們在註冊類中定義了訂閱事件的方法,則 info不為空,進而上邊findUsingInfo()方法中findState.subscriberInfo != null成立,到這裡主要的內容就分析完了,其它的和之前的註冊流程一樣。

所以 Subscriber Index 的核心就是專案編譯時使用註解處理器生成儲存事件訂閱方法資訊的索引類,然後專案執行時將索引類例項設定到 EventBus 中,這樣當註冊 EventBus 時,從索引類取出當前註冊類對應的事件訂閱方法資訊,以完成最終的註冊,避免了執行時反射處理的過程,所以在效能上會有質的提高。專案中可以根據實際的需求決定是否使用 Subscriber Index。

八、小結

結合上邊的分析,我們可以總結出registerpostunregister的核心流程:

register

post

unregister

到這裡 EventBus 幾個重要的流程就分析完了,整體的設計思路還是值得我們學習的。和 Android 自帶的廣播相比,使用簡單、同時又兼顧了效能。但如果在專案中濫用的話,各種邏輯交叉在一起,也可能會給後期的維護帶來困難。

相關文章