聊一聊 EventBus 源碼和設計之禪

歡迎關注本人公衆號,掃描下方二維碼或搜索公衆號 id: mxszgg緩存

前言

筆者看過一些知名開源項目的源碼,認爲 EventBus 算是其中最簡單的,甚至複雜程度不在一個級別上。解析源碼前先提一下如下幾個變量和類,掌握了這些變量和類基本上 EventBus 已經就掌握一半了。網絡

  • METHOD_CACHEMap<Class<?>, List<SubscriberMethod>> 類型。鍵爲註冊類的 Class,值爲該類中全部 EventBus 回調的方法鏈表(也就是被 @Subscribe 標記的方法們)。
  • typesBySubscriberMap<Object, List<Class<?>>> 類型。鍵爲對象自己(例如 Activity 對象),值爲該對象中全部的 Event 的類類型。該字段只用於僅用於判斷某個對象是否註冊過,在平常使用中幾乎沒什麼做用(感謝評論區指出)。
  • Subscription 類(文中稱訂閱信息):關注類中兩個字段,一個是 Object 類型的 subscriber,該字段即爲註冊的對象(在 Android 中時常爲 Activity);另外一個是 SubscriberMethod 類型的 subscriberMethod,細節以下:
    • subscriberMethodSubscriberMethod 類型(文中稱訂閱方法)。關注類中有個字段 eventTypeClass<?> 類型,表明 Event 的類類型。
  • subscribtionsByEventTypeMap<Class<?>, CopyonWriteArrayList<Subscribtion>> 類型。鍵爲 Event 的類類型,值爲元素爲 Subscription(訂閱信息)鏈表。核心字段。

register()

直接查看 EventBus#register() 源碼:多線程

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    // 根據當前註冊類獲取 List<SubscriberMethod>
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
        	// subsciber 對 List<SubscriberMethod> 中每一個 SubscriberMethod 進行訂閱
            subscribe(subscriber, subscriberMethod);
        }
    }
}
複製代碼

獲取當前註冊對象全部訂閱方法信息

先查看如何根據當前註冊類獲取 List 的,SubscriberMethodFinder#findSubscriberMethods(Class<?> subscriberClass) 源碼精簡以下:app

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    // 若是已存在則返回
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

    subscriberMethods = findUsingReflection(subscriberClass);
    METHOD_CACHE.put(subscriberClass, subscriberMethods);
    
    return subscriberMethods;
}
複製代碼

METHOD_CACHE 前面提到過,是存着註冊類與其全部須要回調的 Event 方法列表的鍵值對。若是已經存在則直接返回,若是不然須要經過 findUsingReflection(subscriberClass) 方法進行查找再返回,固然,返回以前須要存入 METHOD_CACHE 中,不然該 METHOD_CACHE 就沒有存在的意義了。async

SubscriberMethodFinder#findUsingReflection() 源碼以下:post

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
    	// 經過純反射去獲取被 @Subscribe 所修飾的方法
        findUsingReflectionInSingleClass(findState);
        // 將當前 class 的父類 class 賦值給 findState.clazz 
        findState.moveToSuperclass();
    }
    // 重置 FindState 便於下一次回收利用
    return getMethodsAndRelease(findState);
}    
複製代碼

初始化 FindState 對象後,會進入一個 while 循環中,不停地去反射獲取當前類和其父類(注意,在 Java 中,若是當前類實現了一個接口,即便該接口的方法被 @Subscribe 所修飾,當前類中的方法也是不包含該註解屬性的,因此若是在接口中對某個方法使用了 @Subscribe 修飾而後讓類去實現這個接口是沒有任何做用的)的訂閱方法並添入列表中,最終返回這個列表並重置 FindState 對象利於下一次重複使用。反射獲取當前類和其父類的訂閱方法源碼簡化以下:性能

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];
                    // needCheck
                    if (findState.checkAdd(method, eventType)) {
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            }
        }
    }
}
複製代碼

這裏想要說起的一點事,獲取到 @Subscribe 修飾的目標方法後,並不是無腦地添入 subscriberMethods 中,而其實是須要過濾一遍的,講解 checkAdd() 源碼前,但願讀者思考如下幾個問題:學習

  • 對於同一個 Event,當前類對該對象使用了多個方法進行了屢次訂閱,那麼若是該 Event 被髮射的時候,當前類會如何調用這些方法?
  • 對於同一個 Event,父類對該對象進行了一次訂閱,子類重寫該訂閱方法,那麼若是該 Event 被髮射的時候,父類子類當中會如何處理這些方法?

解決這些方法就須要去看看 checkAdd() 的底層實現了——優化

boolean checkAdd(Method method, Class<?> eventType) {
    Object existing = anyMethodByEventType.put(eventType, method);
    if (existing == null) {
        return true;
    } else {
        return checkAddWithMethodSignature(method, eventType);
    }
}
複製代碼

能夠看到 anyMethodByEventType 使用了 Event 的 Class 做爲鍵,這像是意味着一個類對於同一個 Event 只能訂閱一次,事實上是否是這樣,還得繼續看看 checkAddWithMethodSignature(),其源碼簡化以下:ui

private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
    methodKeyBuilder.setLength(0);
    methodKeyBuilder.append(method.getName());
    methodKeyBuilder.append('>').append(eventType.getName());

    String methodKey = methodKeyBuilder.toString();
    Class<?> methodClass = method.getDeclaringClass();
    Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
    if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
        return true;
    } else {
        subscriberClassByMethodKey.put(methodKey, methodClassOld);
        return false;
    }
}
複製代碼

能夠看到 subscriberClassByMethodKey 使用方法名 + '>' + 事件類型做爲鍵,這意味着對於同一個類來講,subscriberClassByMethodKey 確定不會鍵重複(畢竟一個類中不可以方法名相同且方法參數、個數都相同),所以它最終會返回 true。這意味着一個類若是使用了多個方法對同一個 Event 對象進行註冊,那麼當該 Event 對象被髮射出來的時候,全部的方法都將會獲得回調。

可是當父類執行上述操做的時候,若是子類有「顯示」實現父類的訂閱方法,那麼此時 subscriberClassByMethodKey.put(methodKey, methodClass) 返回值不會爲空,且爲子類的 Class,此時 if 上分支將會判斷子類 Class 是否 isAssignableFrom 父類 Class,這確定是會爲 false 的,這將會走入 if 下分支並返回 false。這意味着當子類「顯示」實現父類的訂閱方法的時候,若是此時發射指定 Event 的話,父類的訂閱方法將不會執行,而僅會執行子類的訂閱方法。

subscribe()

獲取到相應的 SubscriberMethod 鏈表後,就是對鏈表中的 SubscriberMethod 對象進行訂閱了,EventBus#subscribe() 方法源碼精簡以下:

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

    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
        	// 根據 priority 大小放入 List 中
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    // 省略 sticky 事件
}
複製代碼

subscriptionsByEventType 根據 Event 事件類類型獲取訂閱信息鏈表,固然,若是沒有的話那就 new 一個並放入其中。接着根據訂閱方法的優先級塞入該鏈表中。最後 typesBySubscriber 獲取該 subsciber 的全部 Event 事件類型鏈表,並添加當前 Event 事件類型。關於 sticky 事件的具體內容在 sticky 中會具體講解。

至此 EventBus#register(Object) 方法算是結束了。

post()

EventBus#post(Object) 源碼精簡以下:

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;
        try {
            while (!eventQueue.isEmpty()) {
            	// 分發 Event 事件
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
        	// 最後要 reset flag
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}
複製代碼

currentPostingThreadState 是一個 ThreadLocal 類,經過它獲取到 PostingThreadState 對象,再根據該對象獲取到 event 鏈表(有沒有聯想到 Android 中的消息機制?),並將傳入的 event 塞入該鏈表。爲了控制 Event 出隊列不會被調用屢次,PostingThreadState 對象有一個 isPosting 來標記當前鏈表是否已經開始進行回調操做,經過源碼能夠看到,每次分發完一個 Event 事件,該事件也會被從鏈表中 remove 出去。

postSingleEvent()

具體 postSingleEvent() 源碼精簡以下:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    postSingleEventForEventType(event, postingState, eventClass);
}
複製代碼

追溯 EventBus#postSingleEventForEventType() 源碼精簡以下:

private void 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;
            try {
                postToSubscription(subscription, event, postingState.isMainThread);
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
        }
    }
}
複製代碼

經過 subscriptionsByEventType 獲取該 Event 事件對應的訂閱信息鏈表,而後將該訂閱信息Event 和當前線程信息傳給了 postToSubscription() 方法,該方法戳進去一看就知道是用來去回調全部訂閱方法的,該方法的具體分析在 threadMode 中。實際上到這裏 post() 流程就算是結束了。因此實際上核心方法 post() 的源碼是十分簡單的,也能夠看獲得,核心字段也僅有 subscriptionsByEventType 一個而已。

unregister()

EventBus#unregister(Object) 方法源碼精簡以下:

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

總體看來分兩步走,一步是移除註冊對象和其全部 Event 事件鏈表,即 typesBySubscriber 移除相關鍵值對的;再就是在 unsubscribeByEventType() 方法中對 subscriptionsByEventType 移除了該 subscriber 的全部訂閱信息(能夠看到實際上沒有對 METHOD_CACHE 進行相關移除操做,便於下一次註冊的時候能夠很方便拿到以前的信息,這即是緩存的做用所在)。

threadMode

在 EventBus 中,共有四種 threadMode,以下:

public enum ThreadMode {
    POSTING,

    MAIN,

    MAIN_ORDERED,

    BACKGROUND,

    ASYNC
}
複製代碼
  • POSTING:接收事件方法應執行在發射事件方法所在的線程(因爲發射事件方法線程多是主線程,這意味着接收方法不能執行耗時操做,不然會阻塞主線程)
  • MAIN:在 Android 中則接收事件方法應執行在主線程,不然(在 Java 項目中)等同於 POSTING。若是發射事件方法已位於主線程,那麼接收事件方法會被「當即」調用(這意味着接收事件方法不能執行耗時操做,不然會阻塞主線程;同時,因爲是「當即」調用,因此發射事件方法此時是會被接收事件方法所阻塞的),不然等同於 MAIN_ORDERED
  • MAIN_ORDERED:在 Android 中則接收事件方法會被扔進 MessageQueue 中等待執行(這意味着發射事件方法是不會被阻塞的),不然(在 Java 項目中)等同於 POSTING
  • BACKGROUND
    • 在 Android 中
      • 發射事件方法在主線程中執行,則接收事件方法應執行在子線程執行,但該子線程是 EventBus 維護的單一子線程,因此爲了不影響到其餘接收事件方法的執行,該方法不該太耗時避免該子線程阻塞。
      • 發射事件方法在子線程中執行,則接收事件方法應執行在發射事件方法所在的線程。
    • 在 Java 項目中,接收事件方法會始終執行在 EventBus 維護的單一子線程中。
  • ASYNC:接收方法應執行在不一樣於發射事件方法所在的另外一個線程。經常使用於耗時操做,例如網絡訪問。固然,儘可能避免在同一個時間大量觸發此類型方法,儘管 EventBus 爲此專門建立了線程池來管理回收利用這些線程。

關於以上 threadMode 哪幾種應避免耗時操做,耗時時阻塞的是哪條線程,但願各位讀者可以仔細閱讀。

說完幾種 threadMode 以後,再來看看前文遺留下來的問題——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 {
                // temporary: technically not correct as poster not decoupled from subscriber
                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);
    }
}
複製代碼

細看源碼,其實能夠發現只用到了兩種方法,一種是 invokeSubscriber 意味着當即調用該方法,另外一種是 xxxPoster.enqueue() 意味着須要使用其餘線程來執行該方法。

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

實在是簡單粗暴直接通俗易懂,筆者佩服。

那麼那些狀況會使用 invokeSubscriber() 方法呢?

  • POSTING:不用說,既然和發射事件線程同一條線程執行,那麼固然直接調用 invokeSubscriber() 便可。
  • MAIN:在確保發射事件線程是主線程的狀況下,直接調用 invokeSubscriber()
  • MAIN_ORDERED:若是當前項目不是 Android 項目狀況下(純 Java 項目),將會直接調用 invokeSubscriber()
  • BACKGROUND:前面提到若是發射事件線程不是主線程的話,接收事件將會執行於發射事件所在的線程,因此也會直接調用 invokeSubscriber()

文中已屢次提到 Android 項目和純 Java 項目,是因爲在 Java 項目中大部分狀況下不須要特意區分主線程和子線程(這一點筆者也獲得了女票的證明)。其實不只是 EventBus,RxJava 也是如此,RxJava 中是沒有 Schedulers.mainThread() 一說的,僅有 Schedulers.trampoline() 表當前線程。

Poster#enqueue()

根據源碼能夠看出來分爲如下三種:

這裏寫圖片描述

HandlerPoster 源碼不在此擴展了,熟悉 Android 的讀者們應該都猜獲得 HandlerPoster 底層實現確定是經過 Handler 機制來實現的,HandlerPoster#enqueue() 方法的實現離不開 Hanlder#sendMessage(),而處理方式確定就是在 Hanlder#handleMessage() 中去調用 invokeSubscriber()

BackgroundPoster 源碼也不在此擴展了,前面提到 EventBus 會維護單一線程去執行接收事件方法,因此確定會在 Runnable#run() 中去調用 invokeSubscriber()

AsyncPoster 的底層實現實際上與 BackgroundPoster 大同小異,可是有讀者會疑惑了,BackgroundPoster 底層維護的是「單一」線程,而 AsyncPoster 確定不是這樣的啊。這裏的細節留到設計技巧一節再來細說。

sticky

什麼叫作 sticky 事件筆者此處就不作擴展了。項目中若是想要發射 sticky 事件須要經過 EventBus#postSticky() 方式,源碼以下:

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

能夠看到第一步是將該事件放入 stickyEvents 中,第二步則是正常 post()。爲避免多線程操做 postSticky(Object)removeStickyEvent(Class<?>) 引起的衝突,因此對 stickyEvents 對象添加了 synchronized 關鍵字,不得不說 EventBus 做者的設計實在是縝密啊。前文提到 EventBus#register() 中關於 sticky 事件的代碼簡化以下:

if (subscriberMethod.sticky) {
    Object stickyEvent = stickyEvents.get(eventType);
    if (stickyEvent != null) {
        postToSubscription(newSubscription, stickyEvent, isMainThread());
    }
}
複製代碼

能夠看到,沒有什麼特殊的地方,判斷當前事件是否 sticky,若是 sticky 則從 stickyEvents 拿出該事件並執行 postToSubscription() 方法。

優化操做

eventInheritance

不知道各位讀者在平常使用 EventBus 中會不會在 Event 之間存在繼承關係,反正筆者是沒這樣用過。也正是存在筆者這種不會這樣使用 Event 和會使用 Event 繼承的開發者之間的矛盾纔會有這個字段出現。全局搜索該字段僅用於發射事件的時候判斷是否須要發射父類事件,因爲該字段默認爲 true,因此若是各位讀者和筆者同樣在項目開發中 Event 不存在繼承關係的話,能夠將該字段設爲 false 以提升性能。

APT

EventBus 內部使用了大量的反射去尋找接收事件方法,實際上有經驗的小夥伴知道可使用 APT 來優化。這也就是 EventBus 3.0 引入的技術,此處的使用便不在此處擴展了,代碼中經過 ignoreGeneratedIndex 來判斷是否使用生成的 APT 代碼去優化尋找接收事件的過程,若是開啓了的話,那麼將會經過 subscriberInfoIndexes 來快速獲得接收事件方法的相關信息。因此各位讀者若是沒有在項目中接入 EventBus 的 APT,那麼能夠將 ignoreGeneratedIndex 設爲 false 提升性能。

設計技巧

反射方法

EventBus 在獲取接收事件方法的信息中,經過 getDeclaredMethods() 來獲取類中全部方法而並非經過 getMethods(),因爲前者只反射當前類的方法(不包括隱式繼承的父類方法),因此前者的效率較後者更高些。

FindState

如下代碼是 FindState 的獲取:

private FindState prepareFindState() {
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            FindState state = FIND_STATE_POOL[i];
            if (state != null) {
                FIND_STATE_POOL[i] = null;
                return state;
            }
        }
    }
    return new FindState();
}
複製代碼

如下代碼是 FindState 的回收複用:

private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
    List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
    findState.recycle();
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            if (FIND_STATE_POOL[i] == null) {
                FIND_STATE_POOL[i] = findState;
                break;
            }
        }
    }
    return subscriberMethods;
}
複製代碼

能夠看到,EventBus 使用 FindState 並非簡單的 new,因爲 FindState 在註冊流程中使用頻繁且建立耗費資源,故建立 FindState 池複用 FindState 對象,與此相同的還有 PendingPost,它用於反射調用接收事件方法,具體不在此擴展。

AsyncPoster、BackgroundPoster

前面提到 AsyncPosterBackgroundPoster 的底層實現是同樣的,可是有讀者會疑惑了,BackgroundPoster 底層維護的是「單一」線程,而 AsyncPoster 確定不是這樣的啊——筆者也是讀了源碼以後才發現被 EventBus 做者擺了一道——在默認狀況下實際上二者底層維護的都是 Executors.newCachedThreadPool(),這是一個有則用、無則建立、無數量上限的線程池。而 BackgroundPoster 是如何控制「單一」的呢?其在 Executor#execute() 上添加了 synchronized 並設立 flag,保證任一時間只且僅能有一個任務會被線程池執行;而 AsyncPoster 只需無腦地將傳來的任務塞入線程池便可。

後記

EventBus 源碼雖簡單,可是當中的不少設計技巧是很是值得學習的,例如前文提到的複用池,以及遍及 EventBus 源碼各處的 synchronized 關鍵字。但願各位讀者也可以深刻到其中去探索一番,尋找到筆者未找到的寶藏。

另外,我建了一個羣,若是你對文章有什麼疑問,或者想和我討論 Android 技術,歡迎入羣哈。

相關文章
相關標籤/搜索