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

對於 Android Developer 來講,不少開源庫都是屬於開發必備的知識點,從使用方式到實現原理再到源碼解析,這些都須要咱們有必定程度的瞭解和運用能力。因此我打算來寫一系列關於開源庫源碼解析實戰演練的文章,初定的目標是 EventBus、ARouter、LeakCanary、Retrofit、Glide、OkHttp、Coil 等七個知名開源庫,但願對你有所幫助 😇😇java

公衆號:字節數組git

系列文章導航:github

咱們知道,EventBus 在發送了消息後,就會直接回調該消息類型的全部監聽方法,回調操做是經過反射 method.invoke 來實現的,那麼在回調以前也必須先拿到應用內全部的監聽方法才行。EventBus 獲取監聽方法的方式有兩種:數組

  • 不配置註解處理器。在 subscriber 進行 register 時經過反射獲取到 subscriber 的全部監聽方法,這種方式是在運行時實現的
  • 配置註解處理器。預先將全部的監聽方法的方法簽名信息保存到輔助文件中,在運行時就能夠直接拿到全部的解析結果而沒必要依靠反射來獲取,這種方式是在編譯階段實現的,相比第一種方式性能上會高不少

這裏先介紹第一種方式,這種方式只須要導入以下依賴便可緩存

implementation "org.greenrobot:eventbus:3.2.0"
複製代碼

1、註冊

EventBus 經過 EventBus.register(Object)方法來進行註冊的。該方法會對 subscriber 進行解析,經過 SubscriberMethodFinder 的 findSubscriberMethods 方法將 subscriber 包含的全部聲明瞭@Subscribe註解的方法的簽名信息保存到內存中,當有消息被 Post 時就能夠直接在內存中查找到目標方法了markdown

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

從 SubscriberMethod 包含的全部參數能夠看出來,它包含了 @Subscribe 的參數信息以及對應的方法簽名信息app

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 會將每次的查找結果緩存到 METHOD_CACHE中,這對某些會前後經歷屢次註冊和反註冊操做的 subscriber 來講比較有用,由於每次查找可能須要依靠屢次循環遍歷和反射操做,會稍微有點消耗性能,但緩存也會佔用一部份內存空間框架

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

由於ignoreGeneratedIndex默認是 false,因此這裏直接看 findUsingInfo(subscriberClass) 方法async

其主要邏輯是:ide

  1. 經過 prepareFindState() 方法從對象池 FIND_STATE_POOL 中獲取空閒的 FindState 對象,若是不存在則初始化一個新的,並在使用事後經過 getMethodsAndRelease 方法將對象還給對象池。經過對象池來避免無限制地建立 FindState 對象,這也算作是一個優化點。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");
            }
        }
    }
複製代碼

findUsingReflectionInSingleClass方法的一個重點是 findState.checkAdd方法。若是往簡單了想,只要把 subscriber 每一個聲明瞭 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.一、說明 methodClassOld 是 methodClass 的父類,須要以子類重寫的方法 method 爲準,容許添加
            // 實際上應該不存在這種狀況,由於 EventBus 是從子類開始向父類進行遍歷的
            //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;
            }
        }
複製代碼

進行上述操做後,就找到了 subscriber 包含的全部監聽方法了,這些方法都會保存到 List<SubscriberMethod> 中。拿到全部方法後,register 方法就須要對 subscriber 及其全部監聽方法進行歸類了

歸類的目的是既是爲了方便後續操做也是爲了提升效率。 由於可能同時存在多個 subscriber 聲明瞭多個對同種類型消息的監聽方法,那麼就須要將每種消息類型和其當前的全部監聽方法對應起來,提升消息的發送效率。並且在 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);
            }
        }
    }
複製代碼

2、消息

一、消息的執行策略

在介紹消息的具體發送步驟前,先來了解下 EventBus 幾種不一樣的消息執行策略。執行策略由枚舉 ThreadMode 來定義,在 @Subscribe 註解中進行聲明。執行策略決定了消息接收方是在哪個線程接收到消息的

ThreadMode 執行線程
POSTING 在發送事件的線程中執行 直接調用消息接收方
MAIN 在主線程中執行 若是事件就是在主線程發送的,則直接調用消息接收方,不然經過 mainThreadPoster 進行處理
MAIN_ORDERED 在主線程中按順序執行 經過 mainThreadPoster 進行處理,以此保證消息處理的有序性
BACKGROUND 在後臺線程中按順序執行 若是事件是在主線程發送的,則提交給 backgroundPoster 處理,不然直接調用消息接收方
ASYNC 提交給空閒的後臺線程執行 將消息提交到 asyncPoster 進行處理

執行策略的具體細分邏輯是在 EventBus 類的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 會將任務依次緩存到 PendingPostQueue 中,每次只取出一個任務交由線程池來執行,因此 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);
        }
    }
複製代碼

二、發送非黏性消息

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

複製代碼

三、發送黏性消息

黏性消息的意義是爲了使得在消息發出來後,即便是後續再進行 register 的 subscriber 也能夠收到以前發送的消息,這須要將 @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());
        }
    }
複製代碼

四、移除黏性事件

移除指定的黏性事件能夠經過如下方法來實現,都是用於將指定事件從 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;
            }
        }
    }

複製代碼

3、解除註冊

解除註冊的目的是爲了不內存泄露,EventBus 使用了單例模式,若是不主動解除註冊的話,EventBus 就會一直持有 subscriber。解除註冊是經過 unregister方法來實現的,該方法邏輯也比較簡單,只是將 subscriber 以及其關聯的全部 method 對象從集合中移除而已

而此處雖然會將關於 subscriber 的信息均給移除掉,可是在 SubscriberMethodFinder 中的靜態成員變量 METHOD_CACHE 依然會緩存着已經註冊過的 subscriber 的信息,這也是爲了在某些 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--;
                }
            }
        }
    }
複製代碼

4、註解處理器

使用註解處理器(Annotation Processing Tool)能夠避免 subscriber 進行註冊時的屢次循環反射操做,極大提高了 EventBus 的運行效率。註解處理器是一種註解處理工具,用來在編譯期掃描和處理註解,經過註解來生成 Java 文件。即以註解做爲橋樑,經過預先規定好的代碼生成規則來自動生成 Java 文件。此類註解框架的表明有 ButterKnife、Dragger二、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"
}
複製代碼

當中,MyEventBusIndex 就是在編譯階段將生成的輔助文件,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;
    }
複製代碼

使用了註解處理器後也有必定的弊端。因爲 MyEventBusIndex 是經過靜態常量類型的 Map 來保存全部的方法簽名信息,當在初始化 EventBus 時該 Map 就同時被初始化了,這就至關於在一開始就進行了全量加載,而某些 subscriber 咱們可能不會使用到,這就形成了內存浪費。而若是是經過反射來獲取,那就至關於在按需加載,只有 subscriber 進行註冊了纔會去緩存 subscriber 帶有的監聽方法

5、一些坑

一、奇怪的繼承關係

上文有介紹到,子類能夠繼承父類的 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 的做者也只是回覆說:只在子類進行方法監聽

二、移除黏性消息

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

6、總結

EventBus 的源碼解析到這裏就結束了,本文所講的內容應該也已經涵蓋了大部份內容了。這裏再來爲 EventBus 的實現流程作一個總結

  1. EventBus 包含 register 和 unregister 兩個方法用於標記當前 subscriber 是否須要接收消息,內部對應向 CopyOnWriteArrayList 添加和移除元素這兩個操做
  2. 每當有 event 被 post 出來時,就須要根據 eventClass 對象找到全部全部聲明瞭 @Subscribe 註解且對這種消息類型進行監聽的方法,這些方法都是在 subscriber 進行 register 的時候,從 subscriber 中獲取到的
  3. 從 subscriber 中獲取全部監聽方法的方式有兩種。第一種是在運行階段經過反射來拿到,對應的是沒有配置註解處理器的狀況。第二種對應的是有配置註解處理器的狀況,經過在編譯階段全局掃描 @Subscribe 註解並生成輔助文件,從而在 register 的時候省去效率低下的反射操做。無論是經過什麼方式進行獲取,拿到全部方法後都會將 methods 按照消息類型 eventType 進行歸類,方便後續遍歷
  4. 每當有消息被髮送出來時,就根據 event 對應的 Class 對象找到相應的監聽方法,而後經過反射的方式來回調方法。外部能夠在初始化 EventBus 的時候選擇是否要考慮 event 的繼承關係,即在 event 被 Post 出來時,對 event 的父類型進行監聽的方法是否須要被回調

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

相關文章
相關標籤/搜索