Android 註解系列之 EventBus3 原理(四)

前言

在以前的文章 Android 註解系列之APT工具(三) 中,咱們介紹了 APT 技術的及其使用方式,也提到了一些知名的開源框架如 Dagger2ButterKnifeEventBus 都使用了該技術。爲了讓你們更好的瞭解 APT 技術的使用,在接下來的文章中我將會着重帶領你們來了解 EventBus 中 APT 技術的使用,在瞭解該知識以前,須要咱們對 EventBus 內部原理較爲熟悉,若是你已經熟悉其內部機制了,能夠跳過該篇文章,直接閱讀 Android 註解系列之EventBus3 「加速引擎「(五)java

閱讀該篇文章,咱們可以學到以下知識點:git

  • EventBus3 內部原理
  • EventBus3 訂閱與發送消息原理
  • EventBus3 線程切換的原理
  • EventBus3 粘性事件的處理

整篇文章結合 EventBus 3.1.1 版本進行講解。程序員

EventBus 簡介

EventBus 對於 Android 程序員來講應該不是很陌生,它是基於觀察者模式的事件發佈/訂閱框架,咱們經常用它來實現不一樣組件的通信,後臺線程通訊等。github

EventBus-Publish-Subscribe.png

雖然 EventBus 很是簡單好用,可是仍是會由於 EventBus 滿天飛,使程序代碼結構很是混亂,難以測試和追蹤。即便 EventBus 有不少詬病,但仍然不影響咱們去學習其中的原理與編程思想~編程

大概流程

在瞭解 EventBus 內部原理以前,咱們先了解一下 EventBus 框架的一個大概流程。以下圖所示:緩存

EventBus粗暴理解.jpg

上圖中綠色爲訂閱流程,紅色爲發送事件流程,你們能夠結合上圖,來理解源碼。安全

在上圖中咱們在 A.java 中訂閱了事件 AEvent,在 B.java 中訂閱了事件 AEventBEvent,下面咱們來分析 EventBus 中註冊與事件發送的兩個流程,在介紹兩個流程以前,先介紹一下 SubscriptionSubscriberMethod 中所包含的內容。數據結構

Subscription 類中包含如下內容:併發

  • 當前註冊對象
  • 對應訂閱方法的封裝對象 SubscriberMethod

SubscriberMethod 類中包含如下內容:框架

  • 包含 @Subscribe 註解的方法的 Method (java.lang.reflect 包下的對象)。
  • @Subscribe 註解中設置的線程模式 ThreadMode
  • 方法的註冊的事件類型的 Class 對象
  • @Subscribe中設置的優先級 priority
  • @Subscribe中設置事件是不是粘性事件 sticky

註冊流程

當咱們經過調用 EventBus.register() 註冊 A、B 兩個對象時,EventBus 會作如下幾件事件:

  • 經過內部的 SubscriberMethodFinder 來獲取 A、B類中含有 @Subscribe 註解的方法,並將該註解中的內容與對應方法封裝爲 SubscriberMethod 對象。而後再將當前訂閱對象與對應的 SubscriberMethod 再封裝爲 Subscription 對象。
  • 將全部的 Subscription 放在名爲 subscriptionsByEventType 類型爲 Map<Class<?>, CopyOnWriteArrayList<Subscription>> 數據結構(key 爲事件類型的 Class 對象) 中,由於 Subscription 對象內部包含 SubscriberMethod, 那麼就能知道訂閱的事件類型,因此咱們能夠根據事件類型來區分 Subscription ,又由於相同事件能夠被不一樣訂閱者中的方法來訂閱,因此相同類型的事件也就以對應不一樣的 Subscription
  • 將訂閱者中的全部訂閱的事件都封裝在名爲 typesBySubscriber 類型爲 Map<Object, List<Class<?>>>數據結構(key 爲訂閱對象,value 爲該對象訂閱的事件類型 Class 對象)。該集合主要用於取消訂閱,在下文中咱們會進行介紹。

在整個註冊流程中,最主要的流程就是 EventBus 經過 SubscriberMethodFinder 去獲取類中包含 @Subscribe 註解的訂閱方法。在 EventBus 3.0 以前該流程一直都是經過反射的方式去獲取。在 3.0 及之後版本,EventBus 採用了 APT 技術,對 SubscriberMethodFinder 查找訂閱方法流程進行了優化,使其能在 EventBus.register() 方法調用以前就能知道相關訂閱事件的方法,這樣就減小了程序在運行期間使用反射遍歷獲取方法所帶來的時間消耗。在下文中咱們也會指出具體的優化點。

事件發送流程

知道了 EventBus 的註冊過程,再來了解事件的發生流程就很是簡單了。由於咱們已經經過 subscriptionsByEventType 存儲事件對應的 Subscription,只要找到了 Subscription ,那麼咱們就能從 Subscription 拿到訂閱事件的對象 subscriber ,以及對應的訂閱方法 Method (java.lang.reflect 包下的對象)。而後經過反射調用:

Subscription 內部包含訂閱者及 SubscriberMethod(內部包含訂閱方法 Method )

method.invoke(subscription.subscriber, event)
複製代碼

經過上述方法,就能將對應事件發送到相關訂閱者了。固然這裏只是簡單的介紹了事件是如何發送到相關訂閱者的。關於 EventBus 中粘性事件的處理,線程如何切換。會在下文中進行詳細介紹。

源碼分析

在瞭解了 EventBus 的內部大概流程後,如今咱們經過源碼來更深層次的瞭解其內部實現。仍是從訂閱過程與事件的發送兩個過程進行講解。

訂閱過程源碼分析

EventBus 的訂閱入口爲 register() 方法,以下所示:

public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        //流程1:獲取對應類中全部的訂閱方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            //流程2:實際訂閱
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
複製代碼

在該方法中,主要涉及到 SubscriberMethodFinder 查找方法與實際訂閱兩個流程,下面咱們會對這兩個流程進行介紹。

SubscriberMethodFinder 查找方法流程

在該流程中,主要經過 SubscriberMethodFinder 去獲取訂閱者中全部的 SubscriberMethod ,咱們先看 findSubscriberMethods() 方法:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        //從緩存中獲取訂閱者中的訂閱方法,若是有則讀緩存,若是沒有進行查找
        List<SubscriberMethod> subscriberMethods = (List)METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        } else {
            if (this.ignoreGeneratedIndex) {//若是忽略索引類,則使用反射。
                subscriberMethods = this.findUsingReflection(subscriberClass);
            } else {//不然使用索引類
                subscriberMethods = this.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;
            }
        }
    }
複製代碼

該方法的邏輯也很是簡單,爲以下幾個步驟:

  • 步驟1:先從緩存( METHOD_CACHE )中獲取訂閱者對應的 SubscriberMethod(訂閱方法) ,若是有則從緩存中取。
  • 步驟2:若是緩存中沒有,則經過布爾變量 ignoreGeneratedIndex,來判斷是直接使用反射獲取訂閱方法,仍是經過索引類(EventBus 3.0 使用APT 增長的類)來獲取。由於 ignoreGeneratedIndex 默認值爲 false ,則默認會走 findUsingInfo() 方法
  • 步驟3:將步驟2中得到的訂閱方法集合,存儲到緩存中,方便下一次獲取,提升效率。

由於默認會走 findUsingInfo() 方法,咱們繼續查看該方法:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        //步驟1:構建了查詢狀態緩存池,最多緩存4個類的查詢狀態
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            //步驟2,獲取查找狀態對應的訂閱信息,👇這裏EventBus 3.0 使用了索引類,
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    //將訂閱者的全部的訂閱方法添加到FindState的集合中
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {//步驟3:若是訂閱信息爲null,則經過反射來獲取類中全部的方法
                findUsingReflectionInSingleClass(findState);
            }// 繼續查找父類的方法
            findState.moveToSuperclass();
        }
        //步驟4,獲取findState中的全部方法,並清空對象池
        return getMethodsAndRelease(findState);
    }
複製代碼
  • 步驟1:建立與訂閱者相關的 FindState 對象。會從 FinState 對象緩存池(最大爲4個)中獲取,一個訂閱者對象對應一個FindState,一個訂閱者對象對應一個或多個訂閱方法。
  • 步驟2:經過 FindState 對象 調用 getSubscriberInfo() 方法去獲取訂閱者相關的訂閱方法信息。該方法使用了 APT 技術,構建了EventBus的索引類。關於具體的優化,會在下篇文章中Android 註解系列之EventBus3「加速引擎「(五)進行描述,你們這裏有個印象就行了。
  • 步驟3:若是經過步驟2獲取不到訂閱方法信息,則經過反射來獲取類中的全部的訂閱方法。並將獲取的方法,封裝到 FindState 中的 subscriberMethods 集合中去。
  • 步驟4:將 FindState 對象中的 subscriberMethods 集合返回。

在上述方法中,咱們須要注意的是,若是當前訂閱着沒有相關的訂閱方法,那麼會依次遍歷其父類的訂閱方法。還有一個知識點,就是該方法中 FindState 使用了 對象緩存池,不會每次註冊一個訂閱者就建立 一個FindState 對象。這樣就節約了內存的使用。

關於索引類的知識點,會在下篇文章中進行介紹,這裏咱們直接查看 findUsingReflectionInSingleClass() 方法:

private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
             //獲取當前訂閱者中的全部的方法
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            //獲取該類的全部public 方法 包括繼承的公有方法
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        //循環遍歷全部的方法,經過相關注解找到相應的訂閱方法。
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            //知足修飾符爲 public 而且非抽象、非靜態
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                //找到參數爲1,且該方法包含Subscrile註解的方法
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            // 建立訂閱方法對象,並將對應方法對象,事件類型,線程模式,優先級,粘性事件封裝到SubscriberMethod對象中。
                            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("@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」);
            }
        }
    }
複製代碼

該方法的邏輯也很是簡單,經過獲取 FindState 中的訂閱者的 Class 對象,而後經過反射獲取全部包含 @Subscribe 註解且參數爲 1 的 Method 對象,並讀取到該參數的類型EventType,接着讀取註解中的 thredModeprioritysticy,最後將這些數據都統一分裝到新建的SubscriberMethod 對象中,最後將該對象添加到 FindState 中的 subscriberMethods 集合中去。

實際訂閱方法 subscribe

當找到訂閱者全部的方法集合後,最終會遍歷調用 subscribe() 方法,查看該方法:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;

        //步驟1,將每一個訂閱方法和訂閱者封裝成Subscription
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);

        //步驟2,獲取對應事件中全部的 Subscription,判斷是否重複添加
        CopyOnWriteArrayList<Subscription> subscriptions = (CopyOnWriteArrayList)this.subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList();
            this.subscriptionsByEventType.put(eventType, subscriptions);
        } else if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType);
        }

        //步驟3,根據優先級,將當前新封裝的Subscription對象添加到subscriptionsByEventType中去
        int size = subscriptions.size();
        for(int i = 0; i <= size; ++i) {
            if (i == size || subscriberMethod.priority > ((Subscription)subscriptions.get(i)).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }

       //步驟4,將當前訂閱者中與當前訂閱者所訂閱的事件類型,添加到typesBySubscriber中去
        List<Class<?>> subscribedEvents = (List)this.typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList();
            this.typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

        //步驟5,若是該方法有訂閱了粘性事件,則從stickyEvents中獲取相應粘性事件,併發送
        if (subscriberMethod.sticky) {
            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 = this.stickyEvents.get(eventType);
                this.checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }

    }
複製代碼

在上述方法中主要流程以下:

  • 步驟1,將每一個訂閱方法和訂閱者封裝成 Subscription。
  • 步驟2,獲取對應事件中全部的 Subscription ,判斷是否重複添加。
  • 步驟3,根據 優先級,將當前新封裝的 Subscription 對象添加到 subscriptionsByEventType 中去。(設置了優先級後,EvenBus 就能夠按照優先級順序,將事件發送給訂閱者)
  • 步驟4,將當前訂閱者中與當前訂閱者所訂閱的事件類型,添加到 typesBySubscriber 中去。
  • 步驟5,若是該方法有訂閱了粘性事件,則從 stickyEvents 中獲取相應粘性事件,併發送。

再結合咱們最開始所畫的 EventBus 大體流程,該方法其實就作了下圖紅色虛線框中的事:

subscribe()實際作的事.jpg

關於粘性事件的知識點,須要咱們瞭解事件的發送流程,咱們會在下文進行詳細介紹。

事件發送流程源碼分析

事件的發送,主要分爲簡單事件粘性事件,分別對應方法爲 post()postSticky() 兩個方法。這裏咱們先看簡單事件的發送,代碼以下:

簡單事件的發送

public void post(Object event) {
     //步驟1,獲取當前線程中獨立擁有的PostingThreadState,並從中獲取事件隊列(eventQueue),將發送的事件添加到該隊列中
        EventBus.PostingThreadState postingState = (EventBus.PostingThreadState)this.currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        //步驟2:判斷當前線程是否正在分發事件,若是不是,則循環遍歷事件隊列中的事件,並將事件分發出去,直到當前事件隊列空爲止
        if (!postingState.isPosting) {
            postingState.isMainThread = this.isMainThread();
            postingState.isPosting = true;
            //若是當前分發事件狀態爲取消,則拋出異常
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset」);
            }
            //循環遍歷事件隊列,並將消息發送出去
            try {
                while(!eventQueue.isEmpty()) {
                    this.postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }

    }

複製代碼

在 EventBus 中會爲個每調用 post() 方法的線程都會建立一個惟一的 PostingThreadState 對象,用於記錄當前線程存儲發送消息與發送的狀態,其內部結構以下所示:

PositingThreadState與線程的關係.jpg

PostingThreadState 使用了 ThreadLocal 不熟悉 ThreadLocal 的小夥伴,能夠查看該篇文章:Android Handler機制之ThreadLocal

也就是說當咱們調用 EventBus.post() 方法,實際上是從 EventQueue 隊列中取出消息,而後經過調用 postSingleEvent()方法 來實際發送消息,該方法代碼以下所示:

private void postSingleEvent(Object event, EventBus.PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        //步驟1:👇判斷否事件傳遞發送
        if (this.eventInheritance) {
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            for(int h = 0; h < countTypes; ++h) {
                Class<?> clazz = (Class)eventTypes.get(h);
                //👇循環遍歷遍歷事件併發送
                subscriptionFound |= this.postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
            //步驟2:👇若是不支持事件的傳遞,那麼這裏開始發送事件。
            subscriptionFound = this.postSingleEventForEventType(event, postingState, eventClass);
        }
        //步驟3:若是沒有找到訂閱的方式,提示用戶
        if (!subscriptionFound) {
            if (this.logNoSubscriberMessages) {
                this.logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }

            if (this.sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {
                this.post(new NoSubscriberEvent(this, event));
            }
        }

    }
複製代碼

該方法主要爲以下三個步驟:

  • 步驟1:經過布爾變量 eventInheritance 判斷是否支持事件是否傳遞發送,若是支持,那麼經過lookupAllEventTypes() 方法得到發送事件祖先類及其接口。而後經過 postSingleEventForEventType()方法,將它們都發送出去,
  • 步驟2:步驟1返回 false 那麼就直接使用 postSingleEventForEventType() 方法發送事件。
  • 步驟3:若是沒有找到相關的訂閱方法,那麼就提示用戶沒有相關的訂閱方法。

布爾變量 eventInheritance 默認爲 false ,咱們能夠經過 EventBusBuilder 來配置該變量的值。

那什麼是事件的傳遞發送呢?咱們來查看 lookupAllEventTypes()方法:

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

    //將接口添加到集合中
    static void addInterfaces(List<Class<?>> eventTypes, Class<?>[] interfaces) {
        for (Class<?> interfaceClass : interfaces) {
            if (!eventTypes.contains(interfaceClass)) {
                eventTypes.add(interfaceClass);
                addInterfaces(eventTypes, interfaceClass.getInterfaces());
            }
        }
    }
複製代碼

在該方法中,會獲取發送事件的全部的祖先類及其接口,最後將他們以集合的方式返回,在 postSingleEvent 方法中拿到這個集合以後,那麼就會將集合中全部的數據都發送出去。這樣作會形成什麼效果呢?若是當前咱們的繼承體系爲 Aevent -> Bevent -> Cevent ( -> 表示繼承),那麼經過發送 Aevent,那麼其餘全部訂閱過 Bevent 及 Cevent 的訂閱者都會收到消息。

咱們繼續查看 postSingleEventForEventType() 方法,代碼以下所示:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        //👇從緩存中拿取以前存取的 Subscription
        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;
    }

複製代碼

該方法的邏輯很是簡單,就是從咱們以前的 subscriptionsByEventType 集合中拿到存儲的 Subscription,並根據當前線程狀態設置關聯的 PostingStatecanceledsubscriptionisMainThread 等屬性值,而後經過 postToSubscription() 方法來真正的執行事件的傳遞。

到目前爲止整個流程以下所示:

簡單事件的發送.jpg

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 {
                    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("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }
複製代碼

從上述方法中,咱們拿到 Subscription 中成員變量 SubscriberMethod 中的線程模式 threadMode 來判斷訂閱方法須要執行的線程。若是當前線程模式是 POSTING ,那麼默認就直接調用 invokeSubscriber() 方法。具體代碼以下所示:

void invokeSubscriber(Subscription subscription, Object event) {
        try {
            //👇直接經過反射調用訂閱方法。
            subscription.subscriberMethod.method.
            invoke(subscription.subscriber, event);
        }
        //省略部分代碼
    }
複製代碼

若是爲其餘模式,那麼會根據相應的 poster 調用 enqueue() 方法來控制執行訂閱方法所在的線程。在 EventBus 中提供了以下三個 Poster 來控制訂閱方法的所運行的線程。

  • HandlerPoster (切換到主線程)
  • BackgroundPoster (切換到後臺線程)
  • AsyncPoster (切換到後臺線程)

以上三個 Poster 都實現了 Poster 接口,且內部都維護了一個名爲 PendingPostQueue 的隊列,該隊列以 PendingPost 爲存儲單元,其中 PendingPost 中存儲內容爲咱們根據當前事件所找到的 Subscription 與當前所發生的事件。

那麼結合整個流程,咱們能獲得下圖:

簡單事件發送的整個流程.jpg

針對上圖,再進行一下簡單的說明。

  • 當咱們調用 EventBus.post() 發送簡單事件時,會將該事件放入與線程相關的 PostingThreadStateEventQueue 中。
  • 接着會從以前在 subscriptionsByEventType 集合中找到與該事件相關的 Subscription
  • 接着將找到的 Subscription 與當前所發送的事件都封裝爲 PendingPost 並添加到對應 Poster 中的 PendingPostQueue 隊列中。
  • 最後對應的 Poster 從隊列中取出相應的 PendingPost,經過反射調用訂閱者的訂閱方法。

其中訂閱方法執行線程的規則,以下所示:

訂閱方法規則.png

線程的切換

在上節中,訂閱者的訂閱方法執行的所在線程,是由 EventBus 中內部的三個 Poster來實現的。那下面咱們就來看看這三個 Poster 的實現。

HandlerPoster
public class HandlerPoster extends Handler implements Poster {

    private final PendingPostQueue queue;
    private final int maxMillisInsideHandleMessage;
    private final EventBus eventBus;
    private boolean handlerActive;

    //默認會傳遞主線程的Looper
    protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
        super(looper);
        this.eventBus = eventBus;
        this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            //👇這裏將PedingPost放入PendingPostQueue中,而後發送消息
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                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 pendingPost = queue.poll();
                if (pendingPost == null) {
                    synchronized (this) {
                        // Check again, this time in synchronized
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false;
                            return;
                        }
                    }
                }
                //👇直接經過反射,調用訂閱者的訂閱方法。
                eventBus.invokeSubscriber(pendingPost);
                long timeInMethod = SystemClock.uptimeMillis() - started;
                if (timeInMethod >= maxMillisInsideHandleMessage) {
                    if (!sendMessage(obtainMessage())) {
                        throw new EventBusException("Could not send handler message」);
                    }
                    rescheduled = true;
                    return;
                }
            }
        } finally {
            handlerActive = rescheduled;
        }
    }
}
複製代碼

HanderPoster 中的邏輯很是容易理解,繼承 Handler,並在初始化的時候默認會關聯 主線程 的 Looper,這樣該 Handler 所發送的消息將會在主線程中被處理。

分析一下 HanderPoster 中主要的步驟:

  • 在調用 enqueue() 方法時,會將以前咱們封裝好的 PendingPost 放入 PendingPostQueue 隊列中,同時發送消息。
  • handleMessage() 方法中,從 PendingPostQueue 隊列中取出最近的 PendingPost,而後直接經過 eventBus.invokeSubscriber() 反射執行訂閱者的訂閱方法。
BackgroundPoster
final class BackgroundPoster implements Runnable, Poster {

    private final PendingPostQueue queue;
    private final EventBus eventBus;

    private volatile boolean executorRunning;

    BackgroundPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        //使用線程池來提交任務,該方法是線程安全的。
        synchronized (this) {
            queue.enqueue(pendingPost);
            if (!executorRunning) {
                executorRunning = true;
                eventBus.getExecutorService().execute(this);
            }
        }
    }

    @Override
    public void run() {
        try {
            try {
                while (true) {
                    PendingPost pendingPost = queue.poll(1000);
                    if (pendingPost == null) {
                        synchronized (this) {
                            // Check again, this time in synchronized
                            pendingPost = queue.poll();
                            if (pendingPost == null) {
                                executorRunning = false;
                                return;
                            }
                        }
                    }
                    eventBus.invokeSubscriber(pendingPost);
                }
            } catch (InterruptedException e) {
                eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
            }
        } finally {
            executorRunning = false;
        }
    }
}
複製代碼

BackgroundPoster 與 HandlerPoster 最大的不一樣是其內部使用了線程池,而且該類也實現了 Runnable 接口。

在 BackgroundPoster 中的 enqueue() 方法中,默認會使用 EventBus 中默認的線程池 DEFAULT_EXECUTOR_SERVICE來提交任務 ,該線程池的聲明以下:

private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
複製代碼

CachedThreadPool 適用於大量的且耗時較少的任務

一樣的,BackgroundPoster 也就是經過反射調用訂閱者的訂閱方法,只不過不一樣的是它是放入線程池中的非主線程中進行執行。

須要注意的是不論是在任何線程中發送消息,EventBus 老是線程安全的。從 BackgroundPoster 的代碼中咱們就能夠看出。

AsyncPoster
class AsyncPoster implements Runnable, Poster {

    private final PendingPostQueue queue;
    private final EventBus eventBus;

    AsyncPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        queue.enqueue(pendingPost);
        eventBus.getExecutorService().execute(this);
    }

    @Override
    public void run() {
        PendingPost pendingPost = queue.poll();
        if(pendingPost == null) {
            throw new IllegalStateException("No pending post available」);
        }
        eventBus.invokeSubscriber(pendingPost);
    }
}
複製代碼

這裏就不對 AsyncPoster 進行講解了,相信你們根據以前的內容也能理解。

粘性事件的發送

如今咱們還剩最後一個知識點了,就是粘性事件的發送。在 EventBus 中發送粘性事件,咱們須要調用方法 postSticky() 方法,代碼以下所示:

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

從代碼中,咱們不難看出,粘性的事件發送與簡單事件的發送惟一的區別就是將發送的事件添加到 stickyEvents 集合中去了。那爲何要這麼作呢?在瞭解具體的緣由以前,咱們須要瞭解粘性事件的概念。

粘性事件的概念:當訂閱者尚未訂閱相關事件 A 時,程序已經發送了一些事件 A,按照正常的邏輯,當訂閱者開始訂閱事件 A 時,是接受不到程序已經發送過的事件 A ,可是咱們但願接受到那些已經發送過的消息。這種已通過時,但又被從新接受的事件,咱們稱之爲粘性事件。

那麼根據粘性事件的思想,咱們須要將已經發送的事件存儲下來,並在粘性事件的訂閱的過程當中進行特別的處理,也就是在 EventBus.register() 方法中進行處理。還記得以前註冊過程當中的 subscribe() 方法嗎?該方法內部對粘性事件進行了特殊的處理,代碼以下所示:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        //省略部分代碼
        //判斷是不是粘性事件
        if (subscriberMethod.sticky) {
            //👇支持事件傳遞的粘性事件
            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);
            }
        }
    }
複製代碼

在上述邏輯中,會從 stickyEvents 中獲取以前發送的事件,而後調用 checkPostStickyEventToSubscription()。該方法代碼以下所示:

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

又由於checkPostStickyEventToSubscription() 方法內部會調用 postToSubscription() 方法。那麼最終訂閱者就能接受到以前發送的事件,並執行相應的訂閱方法啦。

最後

EventBus 主要的流程到如今已經講完了。從實際的代碼中,咱們不只能看到其良好的代碼規範以及封裝思想。還能看到該框架對性能的優化,尤爲是添加了一些必要的緩存。我相信以上的這些點,都是值得咱們借鑑與參考的。在接下來的文章中咱們會講解 EventBus 中的 「加速引擎" 索引類。有興趣的小夥伴能夠繼續關注。

相關文章
相關標籤/搜索