EventBus原始碼分析(三):post方法釋出事件【獲取事件的所有訂閱者,反射呼叫訂閱者事件處理方法】(2.4版本)

王世暉發表於2016-07-04

EventBus原始碼分析(一):入口函式提綱挈領(2.4版本)
EventBus原始碼分析(二):register方法儲存事件的訂閱者列表(2.4版本)
EventBus原始碼分析(三):post方法釋出事件【獲取事件的所有訂閱者,反射呼叫訂閱者事件處理方法】(2.4版本)
EventBus原始碼分析(四):執行緒模型分析(2.4版本)
EventBus原始碼解讀詳細註釋(1)register的幕後黑手
EventBus原始碼解讀詳細註釋(2)MainThread執行緒模型分析
EventBus原始碼解讀詳細註釋(3)PostThread、MainThread、BackgroundThread、Async四種執行緒模式的區別
EventBus原始碼解讀詳細註釋(4)register時重新整理的兩個map
EventBus原始碼解讀詳細註釋(5)事件訊息繼承性分析 eventInheritance含義
EventBus原始碼解讀詳細註釋(6)從事件釋出到事件處理,究竟發生了什麼!

EventBus維護了一個重要的HashMap,這個HashMap的鍵是事件,值是該事件的訂閱者列表,因此post事件的時候就能夠從此HashMap中取出事件的訂閱者列表,對每個訂閱者反射呼叫事件處理方法。

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

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

因為EventBus釋出事件的執行緒多樣性,可能是主執行緒,可能是執行緒池分配的執行緒,也可能是臨時非配的一個執行緒。EventBus釋出事件和處理事件可能會執行緒切換,因此需要記錄釋出者執行緒的狀態。

PostingThreadState:記錄釋出者執行緒狀態

PostingThreadState類正是記錄了釋出者的狀態

    /** 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;
    }

可見PostingThreadState為給個執行緒設定了一個事件佇列,一個事件,一個封裝好的訂閱者Subscription物件和三個標誌位(最主要的資訊就是釋出者執行緒是否為主執行緒,隨後會根據釋出者執行緒是否為主執行緒決策是否要執行緒切換)。
Java中為每個執行緒設定不同的資料使用到的是ThreadLocal,EventBus通過ThreadLocal為每個執行緒建立一個PostingThreadState物件並儲存釋出者的狀態。

    private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };

post方法中首先獲取了釋出者執行緒的PostingThreadState物件,獲取該執行緒物件的事件佇列,並把這次釋出的事件新增到釋出者執行緒的事件佇列中。

        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

然後通過迴圈,取出釋出者執行緒的事件佇列的每一個事件,進行釋出,一直到佇列為空。
如果正在迴圈處理事件佇列,進行事件釋出,那麼釋出者執行緒的isPosting屬性為真,這時候再次呼叫post釋出事件,只會進佇列,不會再次迴圈,因為之前的迴圈會處理佇列中的事件,包括後來入佇列的事件。事件佇列此時為一個生產者消費者模型,EventBus消費事件,使用者通過post生產事件。

   postingState.isPosting = true;

因此post釋出事件的時候,會先獲取釋出者執行緒的資訊(最重要的執行緒資訊是釋出者執行緒是否為主執行緒isMainThread),然後對釋出者執行緒釋出的每一個事件進行排隊,通過postSingleEvent方法對佇列中的每一個事件進行處理,postSingleEvent方法需要知道釋出者執行緒是否為主執行緒,通過傳入釋出者執行緒的ThreadLocal的PostingThreadState物件即可。

postSingleEvent:事件繼承性分析

如果post(A),A extends B implements C
那麼onEvent(A)、onEvent(B)、onEvent(C)這三個個事件處理方法那些能得到呼叫呢
答案是onEvent(A)、onEvent(B)、onEvent(C)這三個事件處理方法都會得到呼叫。
EventBus有個屬性eventInheritance專門用來控制是否要處理事件的父類,預設值是true,表示預設是要處理事件父類的。
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));
            }
        }
    }

如果不需要處理事件父類的話,那麼直接呼叫postSingleEventForEventType對當前的事件進行釋出處理即可。
否則,需要找到當前釋出事件的所有父類(包括實現的介面),對父類也通過postSingleEventForEventType方法進行處理。

如何獲取一個類的所有父類和實現的介面

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

EventBus對於快取的設計是非常的精闢準確。獲取一個類的所有的父類和實現的介面這一操作,是比較耗費效能的,為了更快的獲取,採用快取策略,以空間換時間,快取同樣通過靜態的HashMap實現。
lookupAllEventTypes方法中同樣是先獲取快取,只有在快取不命中的情況下才進行查詢,查詢的過程就是向上迴圈迭代,遇到根類Object停止迭代 。
對應所實現的介面的處理,則是對每一層,進行遞迴獲取

    /** 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());
            }
        }
    }

postSingleEventForEventType:獲取事件的訂閱者列表,對每個訂閱者進行事件處理

最開始EventBus維護的事件到訂閱者列表的map終於派上用場了,從該map中獲取待發布事件的所有訂閱者,對每個訂閱者都要進行事件處理。

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

postSingleEventForEventType方法首先在同步程式碼塊中獲取事件的訂閱者列表

        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            subscriptions = subscriptionsByEventType.get(eventClass);
        }

對每一個訂閱者進行處理的方法需要傳入一個釋出者執行緒的資訊,其實EventBus只關心釋出者執行緒是否為主執行緒,因為根據不同的執行緒模型可能會執行緒切換

 for (Subscription subscription : subscriptions) {
     ...
     postToSubscription(subscription, event, postingState.isMainThread);
     ...
  }

postToSubscription:執行緒切換

在postToSubscription方法中,EventBus根據事件的執行緒模式和釋出者執行緒是否為主執行緒,在規定的執行緒中對事件處理方法進行反射呼叫

    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case PostThread:
                invokeSubscriber(subscription, event);
                break;
            case MainThread:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case BackgroundThread:
                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);
        }
    }
  1. PostThread模式不需執行緒切換,直接在釋出者執行緒進行事件處理。
  2. MainThread模式分類討論:釋出者執行緒是主執行緒則直接呼叫事件處理方法,否則通過Handler進行執行緒切換。
  3. BackgroundThread模式分類討論:釋出這執行緒不是主執行緒則在釋出者執行緒直接處理事件,否則執行緒切換至執行緒池處理。
  4. Async模式不關心釋出者執行緒直接線上程池中開闢一個新的執行緒處理事件。
    具體的執行緒模型分析將會在下一篇進行詳細分析。

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

這裡通過invoke方法對事件處理方法進行了反射呼叫。invoke方法會可能會丟擲三個異常,這裡只處理了兩個。IllegalArgumentException異常並沒有捕獲,因為在儲存事件處理方法的時候已經做了約束,儲存的事件處理方法肯定只有一個引數,因此肯定不會丟擲非法引數異常。

相關文章