三方庫原始碼筆記(1)-EventBus 原始碼詳解

葉志陳發表於2020-12-14

國慶假期想著閒著也是閒著,就想著來深入瞭解下幾個常用的開源庫??,看下其實現原理和原始碼,進行總結並輸出成文章。初定的目標是 EventBus、ARouter、LeakCanary、Glide、Coil、Retrofit、OkHttp 等幾個。目前已經完成了部分,在之後的幾天裡會將文章陸續釋出出來??

我們知道,EventBus 在有訊息被髮送出來時,可以直接為我們回撥該訊息的所有監聽方法,回撥操作是通過反射 method.invoke 來實現的。那麼 EventBus 在回撥之前也必須先拿到所有的監聽方法才行,這樣才知道該訊息型別對應什麼監聽方法以及對應多少監聽方法

EventBus 獲取監聽方法的方式有兩種:

  • 不配置註解處理器。在 subscriber 進行 register 時通過反射獲取到,這種方式是在執行時實現的
  • 配置註解處理器。預先解析監聽方法到輔助檔案中,在執行時就可以直接拿到所有的解析結果而不必依靠反射來實現,這種方式是在編譯階段實現的,相比第一種方式效能會高很多

這裡先介紹第一種方式,這種方式只需要匯入如下依賴即可

implementation "org.greenrobot:eventbus:3.2.0"

一、註冊

EventBus.java

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

EventBus 的註冊操作是通過 register(Object)方法來完成的。該方法會對註冊類進行解析,將註冊類包含的所有宣告瞭 @Subscribe 註解的方法的簽名資訊儲存到記憶體中,這樣當有訊息被 Post 時,就可以直接在記憶體中查詢到目標方法了

SubscriberMethod 類包含的所有引數可以看出來,它包含了我們對 @Subscribe 的配置資訊以及對應的方法簽名資訊

public class SubscriberMethod {
    final Method method;
    final ThreadMode threadMode;
    final Class<?> eventType;
    final int priority;
    final boolean sticky;
    /** Used for efficient comparison */
    String methodString;
	
    ···

}

這個查詢的過程是通過 SubscriberMethodFinder 類來完成的

SubscriberMethodFinder.java

這裡來看下 SubscriberMethodFinder是如何遍歷獲取到所有宣告瞭@Subscribe 註解的方法

    private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();

    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()) {
         	//如果為空,說明不包含使用 @Subscribe 註解的方法,那麼 register 操作就是沒有意義的,直接丟擲異常
            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;
        }
    }

SubscriberMethodFinder 會將每次的查詢結果快取到 METHOD_CACHE中,這對某些會先後經歷多次註冊和反註冊操作的頁面來說會比較有用,因為每次查詢可能需要依靠多次迴圈遍歷和反射操作,會稍微有點消耗效能

因為ignoreGeneratedIndex預設值是 false,所以這裡直接看 findUsingInfo(subscriberClass) 方法

其主要邏輯是:

  1. 通過 prepareFindState() 方法從物件池 FIND_STATE_POOL 中獲取空閒的 FindState 物件,如果不存在則初始化一個新的,並在使用過後通過 getMethodsAndRelease 方法將物件還給物件池。通過物件池來避免無限制地建立 FindState 物件,這也算做是一個優化點
  2. 在不使用註解處理器的情況下 findState.subscriberInfosubscriberInfoIndexes預設都是等於 null 的,所以主要看 findUsingReflectionInSingleClass 方法即可,從該方法名可知是通過反射操作來進行解析的。解析結果會被存到 findState
  3. 因為父類註冊的監聽方法會被子類繼承到,而解析過程是會從子類向其父類依次遍歷的,所以在解析完子類後需要通過 findState.moveToSuperclass() 方法將下一個查詢的 class 物件指向父類
    private static final int POOL_SIZE = 4;
    private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];

	private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        //步驟1
        FindState findState = prepareFindState();
        findState.initForSubscriber(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 {
                //步驟2
                findUsingReflectionInSingleClass(findState);
            }
            //步驟3
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

	private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
        List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
        findState.recycle();
        synchronized (FIND_STATE_POOL) {
            //回收 findState,嘗試將之存到物件池中
            for (int i = 0; i < POOL_SIZE; i++) {
                if (FIND_STATE_POOL[i] == null) {
                    FIND_STATE_POOL[i] = findState;
                    break;
                }
            }
        }
        return subscriberMethods;
    }

	//如果物件池中有可用的物件則取出來使用,否則的話就構建一個新的
    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();
    }

這裡來主要看下 findUsingReflectionInSingleClass 方法是如何完成反射操作的。如果解析到的方法簽名不符合要求,則會在開啟了嚴格檢查的情況下會直接丟擲異常;如果方法簽名符合要求,則會將方法簽名儲存到subscriberMethods

	private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            //獲取 clazz 包含的所有方法,不包含繼承得來的方法
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            try {
                //獲取 clazz 以及其父類的所有 public 方法
                methods = findState.clazz.getMethods();
            } catch (LinkageError error) { // super class of NoClassDefFoundError to be a bit more broad...
                String msg = "Could not inspect methods of " + findState.clazz.getName();
                if (ignoreGeneratedIndex) {
                    msg += ". Please consider using EventBus annotation processor to avoid reflection.";
                } else {
                    msg += ". Please make this class visible to EventBus annotation processor to avoid reflection.";
                }
                throw new EventBusException(msg, error);
            }
            //由於 getDeclaredMethods() 都丟擲異常了,就不再繼續向下迴圈了,所以指定下次迴圈時忽略父類
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                //method 是 public 的,且不是 ABSTRACT、STATIC、BRIDGE、SYNTHETIC

                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {  //方法包含的引數個數是一
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) { //方法簽名包含 Subscribe 註解
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            //校驗通過後,就將 Subscribe 註解的配置資訊及 method 方法簽名儲存起來
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                    //因為 EventBus 只支援包含一個入參引數的註解函式,所以如果開啟了嚴格的方法校驗那麼就丟擲異常
                    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)) {
                //如果 method 的方法簽名不符合要求且開啟了嚴格的方法校驗那麼就丟擲異常
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }

SubscriberMethodFinder.FindState

findUsingReflectionInSingleClass方法的一個重點是 findState.checkAdd方法。如果往簡單了想,只要把註冊類每個宣告瞭 Subscribe 註解的方法都給儲存起來就可以了,可是還需要考慮一些特殊情況:

  1. Java 中類是可以有繼承關係的,如果父類宣告瞭 Subscribe 方法,那麼就相當於子類也持有了該監聽方法,那麼子類在 register 後就需要拿到父類的所有 Subscribe 方法
  2. 如果子類繼承並重寫了父類的 Subscribe 方法,那麼子類在 register 後就需要以自己重寫後的方法為準,忽略父類的相應方法

checkAdd 方法就用於進行上述判斷

		//以 eventType 作為 key,method 或者 FindState 作為 value
        final Map<Class, Object> anyMethodByEventType = new HashMap<>();
        //以 methodKey 作為 key,methodClass 作為 value
        final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();

        boolean checkAdd(Method method, Class<?> eventType) {
            // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
            // Usually a subscriber doesn't have methods listening to the same event type.

            Object existing = anyMethodByEventType.put(eventType, method);
            if (existing == null) {
                //existing 等於 null 說明之前未解析到監聽相同事件的方法,檢查通過
                //因為大部分情況下監聽者不會宣告多個監聽相同事件的方法,所以先進行這步檢查效率上會比較高
                return true;
            } else { //existing 不等於 null 說明之前已經解析到同樣監聽這個事件的方法了

                if (existing instanceof Method) {
                    if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                        // Paranoia check
                        throw new IllegalStateException();
                    }
                    // Put any non-Method object to "consume" the existing Method
                    //會執行到這裡,說明存在多個方法監聽同個 Event,那麼將將 eventType 對應的 value 置為 this
                    //避免多次檢查,讓其直接去執行 checkAddWithMethodSignature 方法
                    anyMethodByEventType.put(eventType, this);
                }
                return checkAddWithMethodSignature(method, eventType);
            }
        }

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

            //以 methodName>eventTypeName 字串作為 key
            //通過這個 key 來判斷是否存在子類重寫了父類方法的情況
            String methodKey = methodKeyBuilder.toString();
            //獲取宣告瞭 method 的類對應的 class 物件
            Class<?> methodClass = method.getDeclaringClass();

            Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
            //1. 如果 methodClassOld == null 為 true,說明 method 是第一次解析到,允許新增
            //2. 如果 methodClassOld.isAssignableFrom(methodClass) 為 true
            //2.1、說明 methodClassOld 是 methodClass 的父類,需要以子類重寫的方法 method 為準,允許新增
            //     實際上應該不存在這種情況,因為 EventBus 是從子類開始向父類進行遍歷的
            //2.2、說明 methodClassOld 是 methodClass 是同個類,即 methodClass 宣告瞭多個方法對同個事件進行監聽 ,也允許新增
            if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
                // Only add if not already found in a sub class
                return true;
            } else {
                // Revert the put, old class is further down the class hierarchy
                //由於 EventBus 是從子類向父類進行解析
                //會執行到這裡就說明之前已經解析到了相同 key 的方法,對應子類重寫了父類方法的情況
                //此時需要以子類重寫的方法 method 為準,所以又將 methodClassOld 重新設回去
                subscriberClassByMethodKey.put(methodKey, methodClassOld);
                return false;
            }
        }

EventBus.java

進行上述操作後,就拿到了註冊類所有的包含了註解宣告的方法了,這些方法都會儲存到 List<SubscriberMethod> 中。拿到所有方法後,就需要對註冊者及其所有監聽方法進行歸類了

歸類的目的是既是為了方便後續操作也是為了提高效率。 因為在同個頁面或者多個頁面間可能存在多個對同種型別訊息的監聽方法,那麼就需要將每種訊息型別和其當前的所有監聽方法對應起來,提高訊息的傳送效率。而且在 subscriber 解除註冊時,也需要將 subscriber 包含的所有監聽方法都給移除掉,那麼就需要預先進行歸類。監聽方法也可以設定自己對訊息處理的優先順序順序,所以需要預先對監聽方法進行排序

	public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
    
    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
    private final Map<Object, List<Class<?>>> typesBySubscriber;

    // Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //subscriptionsByEventType 以訊息型別 eventType 作為 key,value 儲存了所有對該 eventType 的訂閱者,提高後續在傳送訊息時的效率
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            if (subscriptions.contains(newSubscription)) {
                //說明某個 Subscriber 重複註冊了
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

        //將訂閱者根據訊息優先順序高低進行排序
        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;
            }
        }

        //typesBySubscriber 以訂閱者 subscriber 作為 key,value 儲存了其訂閱的所有 eventType
        //用於向外提供某個類是否已註冊的功能,也方便後續在 unregister 時移除 subscriber 下的所有監聽方法
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        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);
            }
        }
    }

二、傳送訊息

1、訊息的執行策略

在介紹訊息的具體傳送步驟前,先來了解下 EventBus 幾種不同的訊息執行策略。執行策略由列舉 ThreadMode 來執行,在 Subscribe 註解中進行宣告。執行策略決定了訊息接收方是在哪一個執行緒接收到訊息的

ThreadMode執行執行緒
POSTING在傳送事件的執行緒中執行直接呼叫訊息接收方
MAIN在主執行緒中執行如果事件就是在主執行緒傳送的,則直接呼叫訊息接收方,否則通過 mainThreadPoster 進行處理
MAIN_ORDERED在主執行緒中按順序執行通過 mainThreadPoster 進行處理,以此保證訊息處理的有序性
BACKGROUND在後臺執行緒中按順序執行如果事件是在主執行緒傳送的,則提交給 backgroundPoster 處理,否則直接呼叫訊息接收方
ASYNC提交給空閒的後臺執行緒執行將訊息提交到 asyncPoster 進行處理

執行策略的具體細分邏輯是在 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);
        }
    }

例如,對於 AsyncPoster 來說,其每接收到一個訊息,都會直接在 enqueue 方法中將自己(Runnable)提交給執行緒池進行處理,而使用的執行緒池預設是 Executors.newCachedThreadPool(),該執行緒池每接收到一個任務都會馬上交由執行緒進行處理,所以 AsyncPoster並不保證訊息處理的有序性,但在訊息處理的及時性方面會比較高,且每次提交給 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);
    }

}

BackgroundPoster 只會在當前自己並沒有正在處理訊息的情況下才會將自己(Runnable)提交給執行緒池進行處理,所以 BackgroundPoster 會保證訊息佇列在處理時的有序性,但在訊息處理的及時性方面相比 AsyncPoster 要低一些

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

    ···
}

而不管是使用什麼訊息處理策略,最終都是通過呼叫以下方法來完成監聽方法的反射呼叫

    void invokeSubscriber(PendingPost pendingPost) {
        Object event = pendingPost.event;
        Subscription subscription = pendingPost.subscription;
        PendingPost.releasePendingPost(pendingPost);
        if (subscription.active) {
            invokeSubscriber(subscription, event);
        }
    }

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

2、傳送非黏性訊息

EventBus.getDefault().post(Any)方法用於傳送非黏性訊息。EventBus 會通過 ThreadLocal 為每個傳送訊息的執行緒維護一個 PostingThreadState 物件,用於為每個執行緒維護一個訊息佇列及其它輔助引數

	/**
     * For ThreadLocal, much faster to set (and get multiple values).
     */
    final static class PostingThreadState {
        final List<Object> eventQueue = new ArrayList<>();
        boolean isPosting;
        boolean isMainThread;
        Subscription subscription;
        Object event;
        boolean canceled;
    }
    
    private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };
    
    /**
     * 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 = 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);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

每次 post 進來的訊息都會先存到訊息佇列 eventQueue中,然後通過 while 迴圈進行處理,訊息處理邏輯是通過 postSingleEvent方法來完成的

其主要邏輯是:

  1. 假設 EventA 繼承於 EventB,那麼當傳送的訊息型別是 EventA 時,就需要考慮 EventB 的監聽方法是否可以接收到 EventA,即需要考慮訊息型別是否具有繼承關係
  2. 具有繼承關係。此時就需要拿到 EventA 的所有父型別,然後根據 EventA 本身和其父型別關聯到的所有監聽方法依次進行訊息傳送
  3. 不具有繼承關係。此時只需要向 EventA 的監聽方法進行訊息傳送即可
  4. 如果傳送的訊息最終沒有找到任何接收者,且 sendNoSubscriberEvent 為 true,那麼就主動傳送一個 NoSubscriberEvent 事件,用於向外通知訊息沒有找到任何接收者
  5. 監聽方法之間可以設定訊息處理的優先順序高低,高優先順序的方法可以通過呼叫 cancelEventDelivery 方法來攔截事件,不再繼續向下傳送。但只有在 POSTING 模式下才能攔截事件,因為只有在這個模式下才能保證監聽方法是按照嚴格的先後順序被執行的

最終,傳送的訊息都會通過 postToSubscription方法來完成,根據接收者方法不同的處理策略進行處理

	private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        //用於標記是否有找到訊息的接收者
        boolean subscriptionFound = false;
        if (eventInheritance) {
            //步驟2
            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 {
            //步驟3
            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) {
                //步驟4
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

	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;
                try {
                    postToSubscription(subscription, event, postingState.isMainThread);
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                //步驟5
                if (aborted) {
                    break;
                }
            }
            return true;
        }
        return false;
    }

3、傳送黏性訊息

黏性訊息的意義是為了使得在訊息發出來後,即使是後續再進行 registersubscriber 也可以收到之前傳送的訊息,這需要 @Subscribe 註解的 sticky 屬性設為 true,即表明訊息接收方希望接收黏性訊息

EventBus.getDefault().postSticky(Any)方法就用於傳送黏性訊息。黏性事件會被儲存到 stickyEvents 這個 Map 中,key 是 event 的 Class 物件,value 是 event 本身,這也說明對於同一型別的黏性訊息來說,只會儲存其最後一個訊息

    private final Map<Class<?>, Object> stickyEvents;

	/**
     * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky
     * event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}.
     */
    public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        post(event);
    }

對於一個黏性訊息,會有兩種不同的時機被 subscriber 接收到

  1. 呼叫 postSticky 方法時,被其現有的 subscriber 直接接收到,這種方式通過在 postSticky 方法裡呼叫 post 方法來實現
  2. 呼叫 register 方法時,新新增的 subscriber 會判斷 stickyEvents 中是否存在關聯的 event 需要進行分發

這裡主要看第二種情況。register 操作會在 subscribe 方法裡完成黏性事件的分發。和 post 操作一樣,傳送黏性事件時也需要考慮 event 的繼承關係

	private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        
    	···
            
        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>).

                //事件型別需要考慮其繼承關係
                //因此需要判斷每一個 stickyEvent 的父型別是否存在監聽者,有的話就需要都進行回撥
                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);
            }
        }
    }

    private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
            // --> Strange corner case, which we don't take care of here.
            postToSubscription(newSubscription, stickyEvent, isMainThread());
        }
    }

4、移除黏性事件

移除指定的黏性事件可以通過以下方法來實現,都是用於將指定事件從 stickyEvents 中移除

	/**
     * Remove and gets the recent sticky event for the given event type.
     *
     * @see #postSticky(Object)
     */
    public <T> T removeStickyEvent(Class<T> eventType) {
        synchronized (stickyEvents) {
            return eventType.cast(stickyEvents.remove(eventType));
        }
    }

    /**
     * Removes the sticky event if it equals to the given event.
     *
     * @return true if the events matched and the sticky event was removed.
     */
    public boolean removeStickyEvent(Object event) {
        synchronized (stickyEvents) {
            Class<?> eventType = event.getClass();
            Object existingEvent = stickyEvents.get(eventType);
            if (event.equals(existingEvent)) {
                stickyEvents.remove(eventType);
                return true;
            } else {
                return false;
            }
        }
    }

三、解除註冊

解除註冊的目的是為了避免記憶體洩露,EventBus 使用了單例模式,如果不主動解除註冊的話,EventBus 就會一直持有註冊物件。解除註冊的操作是通過 unregister方法來實現的,該方法邏輯也比較簡單,只是將 subscriber 以及其關聯的所有 method 物件從集合中移除而已

而此處雖然會將關於 subscriber 的資訊均給移除掉,但是在 SubscriberMethodFinder 中的靜態成員變數 METHOD_CACHE 依然會快取著已經註冊過的 subscriber 的資訊,這也是為了在某些頁面會先後多次註冊 EventBus 時可以做到資訊複用,避免多次迴圈反射

	/**
     * Unregisters the given subscriber from all event classes.
     */
    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);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

    /**
     * Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber.
     */
    private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

四、註解處理器

使用註解處理器可以避免 subscriber 進行註冊時的多次迴圈反射操作,極大提升了 EventBus 的執行效率

APT(Annotation Processing Tool) 即註解處理器,是一種註解處理工具,用來在編譯期掃描和處理註解,通過註解來生成 Java 檔案。即以註解作為橋樑,通過預先規定好的程式碼生成規則來自動生成 Java 檔案。此類註解框架的代表有 ButterKnife、Dragger2、EventBus

Java API 已經提供了掃描原始碼並解析註解的框架,開發者可以通過繼承 AbstractProcessor 類來實現自己的註解解析邏輯。APT 的原理就是在註解了某些程式碼元素(如欄位、函式、類等)後,在編譯時編譯器會檢查 AbstractProcessor 的子類,並且自動呼叫其 process() 方法,然後將新增了指定註解的所有程式碼元素作為引數傳遞給該方法,開發者再根據註解元素在編譯期輸出對應的 Java 程式碼

關於 APT 技術的原理和應用可以看這篇文章:Android APT 例項講解

在 Kotlin 環境引入註解處理器的方法如下所示:

apply plugin: 'kotlin-kapt'

kapt {
    arguments {
        arg('eventBusIndex', 'github.leavesc.demo.MyEventBusIndex')
    }
}

dependencies {
    implementation "org.greenrobot:eventbus:3.2.0"
    kapt "org.greenrobot:eventbus-annotation-processor:3.2.0"
}

當中,github.leavesc.demo.MyEventBusIndex 就是生成的輔助檔案的包名路徑,可以由我們自己定義

原始檔案:

/**
 * 作者:leavesC
 * 時間:2020/10/01 12:17
 * 描述:
 * GitHub:https://github.com/leavesC
 */
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    @Subscribe
    fun fun1(msg: String) {

    }

    @Subscribe(threadMode = ThreadMode.MAIN, priority = 100)
    fun fun2(msg: String) {

    }

}

生成的輔助檔案如下所示。可以看出,MyEventBusIndex 檔案中封裝了 subscriber 和其所有監聽方法的簽名資訊,這樣我們就無需在執行時再來進行解析了,而是直接在編譯階段就拿到了

/** This class is generated by EventBus, do not edit. */
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("fun1", String.class),
            new SubscriberMethodInfo("fun2", String.class, ThreadMode.MAIN, 100, false),
        }));

    }

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

需要注意的是,在生成了輔助檔案後,還需要通過這些類檔案來初始化 EventBus

EventBus.builder().addIndex(MyEventBusIndex()).installDefaultEventBus()

注入的輔助檔案會被儲存到 SubscriberMethodFinder 類的成員變數 subscriberInfoIndexes 中,findUsingInfo 方法會先嚐試從輔助檔案中獲取 SubscriberMethod,只有在獲取不到的時候才會通過效能較低的反射操作來完成

	private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            //在沒有使用註解處理器的情況下,findState.subscriberInfo 和 subscriberInfoIndexes 的預設值都是為 null,所以 getSubscriberInfo 會返回 null
            //此時就需要通過 findUsingReflectionInSingleClass 方法來進行反射獲取

            //而在有使用註解處理器的情況下,subscriberInfoIndexes 就儲存了自動生成的輔助檔案,此時 getSubscriberInfo 就可以從輔助檔案中拿到目標資訊
            //從而避免了反射操作

            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.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

    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) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }

五、一些坑

1、奇怪的繼承關係

上文有介紹到,子類可以繼承父類的 Subscribe 方法。但有一個比較奇怪的地方是:如果子類重寫了父類多個 Subscribe 方法的話,就會丟擲 IllegalStateException。例如,在下面的例子中。父類 BaseActivity 宣告瞭兩個 Subscribe 方法,子類 MainActivity 重寫了這兩個方法,此時執行後就會丟擲 IllegalStateException。而如果 MainActivity 不重寫或者只重寫一個方法的話,就可以正常執行

/**
 * 作者:leavesC
 * 時間:2020/10/01 12:49
 * 描述:
 * GitHub:https://github.com/leavesC
 */
open class BaseActivity : AppCompatActivity() {

    @Subscribe
    open fun fun1(msg: String) {

    }

    @Subscribe
    open fun fun2(msg: String) {

    }

}

class MainActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        EventBus.getDefault().register(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        EventBus.getDefault().unregister(this)
    }

    @Subscribe
    override fun fun1(msg: String) {

    }

    @Subscribe
    override fun fun2(msg: String) {

    }

}

按道理來說,如果子類重寫了父類一個 Subscribe 方法都可以正常使用的話,那麼重寫兩個也應該可以正常使用才對。可是上述例子就表現得 EventBus 好像有 bug 似的。通過定位堆疊資訊,可以發現是在 FindStatecheckAdd 方法丟擲了異常

其丟擲異常的步驟是這樣的:

  1. EventBus 對 Subscribe 方法的解析方向是子類向父類進行的,同個類下的 Subscribe 方法按照宣告順序進行解析
  2. checkAdd 方法開始解析 BaseActivityfun2 方法時,existing 物件就是 BaseActivity.fun1,此時就會執行到操作1,而由於子類已經重寫了 fun1 方法,此時 checkAddWithMethodSignature 方法就會返回 false,最終導致丟擲異常
        boolean checkAdd(Method method, Class<?> eventType) {
            // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
            // Usually a subscriber doesn't have methods listening to the same event type.
            Object existing = anyMethodByEventType.put(eventType, method);
            if (existing == null) {
                return true;
            } else {
                if (existing instanceof Method) {
                    //操作1
                    if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                        // Paranoia check
                        throw new IllegalStateException();
                    }
                    // Put any non-Method object to "consume" the existing Method
                    anyMethodByEventType.put(eventType, this);
                }
                return checkAddWithMethodSignature(method, eventType);
            }
        }

EventBus 中有一個 issues 也反饋了這個問題:issues,該問題在 2018 年時就已經存在了,EeventBus 的作者也只是回覆說:只在子類進行方法監聽

2、移除黏性訊息

removeStickyEvent 方法會有一個比較讓人誤解的點:對於通過 EventBus.getDefault().postSticky(XXX)方法傳送的黏性訊息無法通過 removeStickyEvent 方法來使現有的監聽者攔截該事件

例如,假設下面的兩個方法都已經處於註冊狀態了,postSticky 後,即使在 fun1 方法中移除了黏性訊息,fun2 方法也可以接收到訊息。這是因為 postSticky 方法最終也是要靠呼叫 post 方法來完成訊息傳送,而 post 方法並不受 stickyEvents 的影響

    @Subscribe(sticky = true)
    fun fun1(msg: String) {
        EventBus.getDefault().removeStickyEvent(msg)
    }

    @Subscribe(sticky = true)
    fun fun2(msg: String) {
        
    }

而如果 EventBus 中已經儲存了黏性事件,那麼在上述兩個方法剛 register 時,fun1 方法就可以攔截住訊息使 fun2 方法接收不到訊息。這是因為 register 方法是在 for 迴圈中遍歷 method,如果之前的方法已經移除了黏性訊息的話,那麼後續方法就沒有黏性訊息需要處理了

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            //在 for 迴圈中遍歷 method 
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

六、總結

EventBus 的原始碼解析到這裡就結束了,本文所講的內容應該也已經涵蓋了大部分內容了。這裡再來為 EventBus 的實現流程做一個總結

  1. EventBus 包含 register 和 unregister 方法用於標記當前 subscriber 是否需要接收訊息,內部對應向 CopyOnWriteArrayList 新增和移除元素這兩個操作
  2. 每當有 event 被 post 出來時,就需要根據 eventClass 物件找到所有所有宣告瞭 @Subscribe 註解且對這種訊息型別進行監聽的方法,這些方法都是在 subscriber 進行 register 的時候,從 subscriber 中獲取到的
  3. 從 subscriber 中獲取所有宣告瞭 @Subscribe 註解的方法有兩種。第一種是通過反射的方式拿到 subscriber 這個類中包含的所有宣告瞭 @Subscribe 註解的方法,對應的是沒有配置註解處理器的情況。第二種對應的是有配置註解處理器的情況,通過在編譯階段全域性掃描 @Subscribe 註解並生成輔助檔案,從而在 register 的時候省去了效率低下的反射操作。不管是通過什麼方式進行獲取,拿到所有方法後都會將 methods 按照訊息型別 eventType 進行歸類,方便後續遍歷
  4. 每當有訊息被髮送出來時,就根據 event 對應的 Class 物件找到相應的監聽方法,然後通過反射的方式來回撥方法。外部可以在初始化 EventBus 的時候選擇是否要考慮 event 的繼承關係,即在 event 被 Post 出來時,對 event 的父型別進行監聽的方法是否需要被回撥

EventBus 的實現思路並不算多難,難的是在實現的時候可以方方面面都考慮周全,做到穩定高效,從 2018 年到現在 2020 年也才釋出了兩個版本(也許是作者懶得更新?)。原理懂了,那麼下一篇就進入實戰篇,自己來動手實現一個 EventBus ??

相關文章