EventBus源碼解析

博文出處:EventBus源碼解析,歡迎大家關注我的博客,謝謝!

0001B

時近年末,但是也沒閑著。最近正好在看 EventBus 的源碼。那就正好今天來說說 EventBus 的那些事兒。

EventBus 是什么呢(相信地球人都知道→_→)?

EventBus is a publish/subscribe event bus optimized for Android.

這是官方給的介紹,簡潔、明了、霸氣。翻譯過來就是:EventBus 是一種為 Android 而優化設計的發布/訂閱事件總線。這官方的套詞可能有些人看了還是不懂。。。

???

簡單地舉了栗子,EventBus 就好像一輛公交車(快上車,老司機要飆車 乀(ˉεˉ乀) )。相對應的,發布事件就可以類比為乘客,訂閱事件就好似接站服務的人。乘客想要到達指定目的地就必須上車乘坐該公交車,公交車會做統一配置管理每位乘客(發布事件流程)。達到目的地后,打開下車門,把乘客交任給接站服務的人做相應的處理(訂閱事件流程)。不知道這個栗子你們懂不懂,反正我是懂了( ̄ε  ̄)。

快上車

所以總的來說,對于一個事件,你只要關心發送和接收就行了,而其中的收集、分發等都交給 EventBus 來處理,你不需要做任何事。不得不說這太方便了,能讓代碼更見簡潔,大大降低了模塊之間的耦合性。

0002B 使用方法

現在,來看一下 EventBus 的使用方法,直接復制粘貼 GitHub 中的例子:

  1. 第一步,定義一個事件類 MessageEvent :

     public static class MessageEvent { 
         /* Additional fields if needed */ 
     }
    
  2. 定義一個訂閱方法,可以使用 @Subscribe 注解來指定訂閱方法所在的線程:

     @Subscribe(threadMode = ThreadMode.MAIN)  
     public void onMessageEvent(MessageEvent event) {
         /* Do something */
     };
    

    注冊和反注冊你的訂閱方法。比如在 Android 中,Activity 和 Fragment 通常在如下的生命周期中進行注冊和反注冊:

     @Override
     public void onStart() {
         super.onStart();
         EventBus.getDefault().register(this);
     }
     
     @Override
     public void onStop() {
         super.onStop();
         EventBus.getDefault().unregister(this);
     }
    

3.發送事件:

    EventBus.getDefault().post(new MessageEvent());

可以看出 EventBus 使用起來很簡單,就這么幾行代碼解決了許多我們備受困擾的問題。那么接下來我們就深入 EventBus 的源碼內部,一探究竟。

0003B EventBus

GitHub 上對于 EventBus 整體有一張示意圖,很明確地畫出了整個框架的設計原理:

EventBus示意圖

那么依據這張圖,我們先從 “Publisher” 開始講起吧。PS : 本文分析的 EventBus 源碼版本為 3.0.0 。

EventBus.getDefault()

來看一下 EventBus.getDefault() 的源碼(文件路徑:org/greenrobot/eventbus/EventBus.java):

private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private final Map<Object, List<Class<?>>> typesBySubscriber;
private final Map<Class<?>, Object> stickyEvents;

public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}

/**
 * Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a
 * central bus, consider {@link #getDefault()}.
 */
public EventBus() {
    this(DEFAULT_BUILDER);
}

EventBus(EventBusBuilder builder) {
    // key 為事件的類型,value 為所有訂閱該事件類型的訂閱者集合
    subscriptionsByEventType = new HashMap<>();
    // key 為某個訂閱者,value 為該訂閱者所有的事件類型
    typesBySubscriber = new HashMap<>();
    // 粘性事件的集合,key 為事件的類型,value 為該事件的對象
    stickyEvents = new ConcurrentHashMap<>();
    // 主線程事件發送者
    mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
    // 子線程事件發送者
    backgroundPoster = new BackgroundPoster(this);
    // 異步線程事件發送者
    asyncPoster = new AsyncPoster(this);
    // 索引類的數量
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    // 訂閱方法查找者
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    // 是否打印訂閱者異常的日志,默認為 true
    logSubscriberExceptions = builder.logSubscriberExceptions;
    // 是否打印沒有訂閱者的異常日志,默認為 true
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    // 是否允許發送 SubscriberExceptionEvent ,默認為 true
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    // 是否允許發送 sendNoSubscriberEvent ,默認為 true
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    // 是否允許拋出訂閱者的異常,默認是 false
    throwSubscriberException = builder.throwSubscriberException;
    // 是否支持事件繼承,默認是 true
    eventInheritance = builder.eventInheritance;
    // 創建線程池
    executorService = builder.executorService;
}

從上面的源碼中可以看出,平時的我們經常調用的 EventBus.getDefault() 代碼,其實是獲取了 EventBus 類的單例。若該單例未實例化,那么會根據 DEFAULT_BUILDER 采用構造者模式去實例化該單例。在 EventBus 構造器中初始化了一堆的成員變量,這些都會在下面中使用到。

register(Object subscriber)

事件訂閱者必須調用 register(Object subscriber) 方法來進行注冊,一起來看看在 register(Object subscriber) 中到底做了一些什么:

public void register(Object subscriber) {
    // 得到訂閱者的類 class
    Class<?> subscriberClass = subscriber.getClass();
    // 找到該 class 下所有的訂閱方法
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {        
            subscribe(subscriber, subscriberMethod);
        }
    }
}

register(Object subscriber) 中,利用 subscriberMethodFinder.findSubscriberMethods 方法找到訂閱者 class 下所有的訂閱方法,然后用 for 循環建立訂閱關系。其中 subscriberMethodFinder.findSubscriberMethods 方法我們暫時先不看了,跳過。在這里只要知道作用是找到該訂閱者所有的訂閱方法就好了。具體 SubscriberMethodFinder 的代碼會在后面的章節中詳細分析。

SubscriberMethod 其實就是訂閱方法的包裝類:

public class SubscriberMethod {
    // 訂閱的方法
    final Method method;
    // 訂閱所在的線程
    final ThreadMode threadMode;
    // 訂閱事件的類型
    final Class<?> eventType;
    // 優先級
    final int priority;
    // 訂閱是否是粘性的
    final boolean sticky;
    // 特定字符串,用來比較兩個 SubscriberMethod 是否為同一個
    String methodString;
    ...

}

然后就是輪到了 subscribe(subscriber, subscriberMethod) 方法:

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);
    } else {
        // 如果 subscriptions 已經包含了,拋出異常
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }
    // 根據該 subscriberMethod 優先級插入到 subscriptions 中
    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;
        }
    }
    // 放入 subscribedEvents 中,key:訂閱者  value:該訂閱者的所有訂閱事件的類型
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);
    // 如果訂閱的方法支持 sticky
    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();
            // 遍歷 stickyEvents
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                // 判斷 eventType 類型是否是 candidateEventType 的父類
                if (eventType.isAssignableFrom(candidateEventType)) {
                    // 得到對應 eventType 的子類事件,類型為 candidateEventType
                    Object stickyEvent = entry.getValue();
                    // 發送粘性事件給 newSubscription
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            // 拿到之前 sticky 的事件,然后發送給 newSubscription
            Object stickyEvent = stickyEvents.get(eventType);
            // 發送粘性事件給 newSubscription
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

其實 subscribe(subscriber, subscriberMethod) 方法主要就做了三件事:

  1. 得到 subscriptions ,然后根據優先級把 subscriberMethod 插入到 subscriptions 中;
  2. eventType 放入到 subscribedEvents 中;
  3. 如果訂閱方法支持 sticky ,那么發送相關的粘性事件。

粘性事件發送調用了 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, Looper.getMainLooper() == Looper.myLooper());
    }
}

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

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

checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) 方法的內部調用了 postToSubscription(Subscription subscription, Object event, boolean isMainThread) 。主要的操作都在 postToSubscription 中。根據 threadMode 共分為四種:

  1. 同一個線程:表示訂閱方法所處的線程和發布事件的線程是同一個線程;
  2. 主線程:如果發布事件的線程是主線程,那么直接執行訂閱方法;否則利用 Handler 回調主線程來執行;
  3. 子線程:如果發布事件的線程是主線程,那么調用線程池中的子線程來執行訂閱方法;否則直接執行;
  4. 異步線程:無論發布事件執行在主線程還是子線程,都利用一個異步線程來執行訂閱方法。

這四種線程模式其實最后都會調用 invokeSubscriber(Subscription subscription, Object event) 方法通過反射來執行。至此,關于粘性事件的發送就告一段落了。

另外,在這里因篇幅原因就不對 mainThreadPosterbackgroundPoster 等細說了,可以自行回去看相關源碼,比較簡單。

unregister(Object subscriber)

看完 register(Object subscriber) ,接下來順便看看 unregister(Object subscriber) 的源碼:

public synchronized void unregister(Object subscriber) {
    // 通過 subscriber 來找到 subscribedTypes
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            // 解除每個訂閱的事件類型
            unsubscribeByEventType(subscriber, eventType);
        }
        // 從 typesBySubscriber 中移除
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

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

瞟了一眼 unregister(Object subscriber) 方法,我們基本上就已經知道其中做了什么。在之前 register(Object subscriber)subscriptionsByEventTypetypesBySubscriber 會對 subscriber 間接進行綁定。而在 unregister(Object subscriber) 會對其解綁,這樣就防止了造成內存泄露的危險。

post(Object event)

最后,我們來分析下發送事件 post(Object event) 的源碼:

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

public void post(Object event) {
    // 得到當前線程的 postingState
    PostingThreadState postingState = currentPostingThreadState.get();
    // 加入到隊列中
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);
    // 如果沒有持續在發送事件,那么開始發送事件并一直保持發送ing
    if (!postingState.isPosting) {
        postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            while (!eventQueue.isEmpty()) {
                // 發送單個事件
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

post(Object event) 中,首先根據 currentPostingThreadState 獲取當前線程狀態 postingStatecurrentPostingThreadState 其實就是一個 ThreadLocal 類的對象,不同的線程根據自己獨有的索引值可以得到相應屬于自己的 postingState 數據。

然后把事件 event 加入到 eventQueue 隊列中排隊。只要 eventQueue 不為空,就不間斷地發送事件。而發送單個事件的代碼在 postSingleEvent(Object event, PostingThreadState postingState) 中,我們跟進去看:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    // 得到事件的類型
    Class<?> eventClass = event.getClass();
    // 是否找到訂閱者
    boolean subscriptionFound = false;
    // 如果支持事件繼承
    if (eventInheritance) {
        // 查找 eventClass 的所有父類和接口
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            // 依次向訂閱方法類型為 eventClass 的父類或接口的發送事件
            // 只要其中有一個 postSingleEventForEventType 返回 true ,那么 subscriptionFound 就為 true
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        // 發送事件
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    // 如果沒有訂閱者
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            // 發送 NoSubscriberEvent 事件,可以自定義接收
            post(new NoSubscriberEvent(this, event));
        }
    }
}

postSingleEvent(Object event, PostingThreadState postingState) 中的代碼邏輯還是比較清晰的,會根據 eventInheritance 分成兩種:

  1. 支持事件繼承:得到 eventClass 的所有父類和接口,然后循環依次發送事件;
  2. 不支持事件繼承:直接發送事件。

另外,若找不到訂閱者,在默認配置下還會發送 NoSubscriberEvent 事件。需要開發者自定義訂閱方法接收這個事件。

關于發送的具體操作還是要到 postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) 中去看:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        // 得到訂閱者
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        // 依次遍歷訂閱者
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                // 發送事件
                postToSubscription(subscription, event, postingState.isMainThread);
                // 是否被取消了
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            // 如果被取消,則跳出循環
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

仔細看上面的代碼,我們應該能發現一個重要的線索—— postToSubscription 。沒錯,就是上面講解發送粘性事件中的 postToSubscription 方法。神奇地繞了一圈又繞回來了。

postSingleEventForEventType 方法做的事情只不過是遍歷了訂閱者,然后一個個依次調用 postToSubscription 方法,之后就是進入 switch 四種線程模式(POSTINGMAINBACKGROUNDASYNC)并執行訂閱者的訂閱方法的邏輯了。這里就不重復講了,具體可以查看上面發送粘性事件中的分析。

至此,整個 EventBus 發布/訂閱的原理就講完了。EventBus 是一款典型的運行觀察者模式的開源框架,設計巧妙,代碼也通俗易懂,值得我們學習。

別以為到這里就本文結束了,可不要忘了,在前面我們還留下一個坑沒填—— SubscriberMethodFinder 。想不想知道 SubscriberMethodFinder 到底是如何工作的呢?那還等什么,我們趕快進入下一章節。

0004B SubscriberMethodFinder

SubscriberMethodFinder 的作用說白了其實就是尋找訂閱者的訂閱方法。正如在上面的代碼中提到的那樣, findSubscriberMethods 方法可以返回指定訂閱者中的所有訂閱方法。

findSubscriberMethods(Class<?> subscriberClass)

我們看下內部的源碼(文件路徑:org/greenrobot/eventbus/SubscriberMethodFinder.java):

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

內部有兩種途徑獲取:findUsingReflection(Class<?> subscriberClass)findUsingInfo(Class<?> subscriberClass) 。另外,還有緩存可以提高索引效率。

findUsingReflection(Class<?> subscriberClass)

那么我們先來看看 findUsingReflection(Class<?> subscriberClass) 方法:

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    // 做初始化操作
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        // 通過反射查找訂閱方法
        findUsingReflectionInSingleClass(findState);
        // 查找 clazz 的父類
        findState.moveToSuperclass();
    }
    // 返回 findState 中的 subscriberMethods
    return getMethodsAndRelease(findState);
}

這里出現一個新的類 FindState ,而 FindState 的作用可以對訂閱方法做一些校驗,以及查找到的所有訂閱方法也是封裝在 FindState.subscriberMethods 中的。另外,在 SubscriberMethodFinder 類內部還維持著一個 FIND_STATE_POOL ,可以循環利用,節省內存。

接著往下看,就發現了一個關鍵的方法: findUsingReflectionInSingleClass(FindState findState) 。根據這方法名可以知道反射獲取訂閱方法的操作就在這兒:

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // This is faster than getMethods, especially when subscribers are fat classes like Activities
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
        methods = findState.clazz.getMethods();
        findState.skipSuperClasses = true;
    }
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        // 方法的修飾符只能為 public 并且不能是 static 和 abstract
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            // 訂閱方法的參數只能有一個
            if (parameterTypes.length == 1) {
                // 得到 @Subscribe 注解,如果注解不為空那就認為是訂閱方法
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    Class<?> eventType = parameterTypes[0];
                    // 將該 method 做校驗
                    if (findState.checkAdd(method, eventType)) {
                        // 解析 @Subscribe 注解中的 threadMode
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        // 加入到 findState.subscriberMethods 中
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException("@Subscribe method " + methodName +
                        "must have exactly 1 parameter but has " + parameterTypes.length);
            }
        } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}

通過一個個循環訂閱者中的方法,篩選得到其中的訂閱方法后,保存在 findState.subscriberMethods 中。最后在 getMethodsAndRelease(FindState findState) 方法中把 findState.subscriberMethods 返回。(這里就不對 getMethodsAndRelease(FindState findState) 做解析了,可以下去自己看代碼,比較簡單 *ο* )

findUsingInfo(Class<?> subscriberClass)

最后,剩下另外一種獲取訂閱方法的途徑還沒講。

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        findState.subscriberInfo = getSubscriberInfo(findState);
        if (findState.subscriberInfo != null) {
            // 直接獲取 subscriberInfo 中的 SubscriberMethods
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            // 如果 subscriberInfo 沒有,就通過反射的方式
            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) {
        // 使用 SubscriberInfoIndex 來獲取 SubscriberInfo
        for (SubscriberInfoIndex index : subscriberInfoIndexes) {
            SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
            if (info != null) {
                return info;
            }
        }
    }
    return null;
}

我們發現在 findUsingInfo(Class<?> subscriberClass) 中是通過 SubscriberInfo 類來獲取訂閱方法的;如果沒有 SubscriberInfo ,就直接通過反射的形式來獲取。那么 SubscriberInfo 又是如何得到的呢?還要繼續跟蹤到 getSubscriberInfo(FindState findState) 方法中。然后又有一個新的類蹦出來—— SubscriberInfoIndex 。那么 SubscriberInfoIndex 又是什么東東啊(文件路徑:org/greenrobot/eventbus/meta/SubscriberInfoIndex.java)?

public interface SubscriberInfoIndex {
    SubscriberInfo getSubscriberInfo(Class<?> subscriberClass);
}

點進去后發現 SubscriberInfoIndex 只是一個接口而已,是不是感到莫名其妙。What the hell is it!

我們把這個疑問先放在心里,到 EventBusPerformance 這個 module 中,進入 build/generated/source/apt/debug/org/greenrobot/eventbusperf 目錄下,發現有一個類叫 MyEventBusIndex

/** 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(org.greenrobot.eventbusperf.testsubject.SubscribeClassEventBusDefault.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEvent", TestEvent.class),
        }));

        putIndex(new SimpleSubscriberInfo(TestRunnerActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventMainThread", TestFinishedEvent.class, ThreadMode.MAIN),
        }));

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscriberClassEventBusAsync.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventAsync", TestEvent.class, ThreadMode.ASYNC),
        }));

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscribeClassEventBusBackground.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventBackgroundThread", TestEvent.class, ThreadMode.BACKGROUND),
        }));

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscribeClassEventBusMain.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventMainThread", TestEvent.class, ThreadMode.MAIN),
        }));

    }

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

從代碼中可知,MyEventBusIndex 其實是 SubscriberInfoIndex 的實現類,并且是 EventBus 自動生成的(根據注釋可知這點)。而 getSubscriberInfo(Class<?> subscriberClass) 方法已經實現了,內部維持著一個 SUBSCRIBER_INDEXHashMap ,用來保存訂閱類的相關信息 info 。然后在需要的時候可以通過 info 快速返回 SubscriberMethod 。這樣就達到了不用反射獲取訂閱方法的目的,提高了執行效率。

到了這里我們明白了上面關于 SubscriberInfoIndex 的疑問,但是又有一個新的疑問產生了:MyEventBusIndex 到底是如何生成的?想要解開這個疑問,我們就要去 EventBusAnnotationProcessor 類中尋找答案了。

0005B EventBusAnnotationProcessor

一看到 EventBusAnnotationProcessor ,菊花一緊,料想肯定逃不了注解。我們可以猜出個大概: EventBus 在編譯時通過 EventBusAnnotationProcessor 尋找到所有標有 @Subscribe 注解的訂閱方法,然后依據這些訂閱方法自動生成像 MyEventBusIndex 一樣的索引類代碼,以此提高索引效率。

總體來說,這種注解的思路和 DaggerButterKnife 等框架類似。想要了更多,可以閱讀我的上一篇博客《ButterKnife源碼分析》

在這里由于篇幅的原因只能簡單粗略地解析 EventBusAnnotationProcessor 的源碼了,還請多多諒解。

process(Set<?extendsTypeElement> annotations, RoundEnvironment env)

我們簡單地來分析一下 process(Set<? extends TypeElement> annotations, RoundEnvironment env)

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
    Messager messager = processingEnv.getMessager();
    try {

        ... // 省略一堆代碼
        // 根據 @Subscribe 的注解得到所有訂閱方法
        collectSubscribers(annotations, env, messager);
        // 校驗這些訂閱方法,過濾掉不符合的
        checkForSubscribersToSkip(messager, indexPackage);

        if (!methodsByClass.isEmpty()) {
            // 生成索引類,比如 MyEventBusIndex
            createInfoIndexFile(index);
        } else {
            messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
        }
        writerRoundDone = true;
    } catch (RuntimeException e) {
        // IntelliJ does not handle exceptions nicely, so log and print a message
        e.printStackTrace();
        messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected error in EventBusAnnotationProcessor: " + e);
    }
    return true;
}

其實在 process(Set<? extends TypeElement> annotations, RoundEnvironment env) 方法中重要的代碼就這么幾行,其他不重要的代碼都省略了。那現在我們順著一個一個方法來看。

collectSubscribers(Set<?extendsTypeElement> annotations, RoundEnvironment env, Messager messager)

我們先從 collectSubscribers(annotations, env, messager); 開始入手:

private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {
    for (TypeElement annotation : annotations) {
        // 根據注解去獲得 elements
        Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation);
        for (Element element : elements) {
            if (element instanceof ExecutableElement) {
                ExecutableElement method = (ExecutableElement) element;
                if (checkHasNoErrors(method, messager)) {
                    TypeElement classElement = (TypeElement) method.getEnclosingElement();
                    // 添加該訂閱方法
                    methodsByClass.putElement(classElement, method);
                }
            } else {
                messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
            }
        }
    }
}

private boolean checkHasNoErrors(ExecutableElement element, Messager messager) {
    // 方法不能是 static 的
    if (element.getModifiers().contains(Modifier.STATIC)) {
        messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must not be static", element);
        return false;
    }
    // 方法要是 public 的
    if (!element.getModifiers().contains(Modifier.PUBLIC)) {
        messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must be public", element);
        return false;
    }
    // 參數只能有一個
    List<? extends VariableElement> parameters = ((ExecutableElement) element).getParameters();
    if (parameters.size() != 1) {
        messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must have exactly 1 parameter", element);
        return false;
    }
    return true;
}

上面代碼做的事情就是根據注解獲取了對應的方法,然后初步篩選了一些方法,放入 methodsByClass 中。

checkForSubscribersToSkip(Messager messager, String myPackage)

得到這些初選的訂閱方法后,就要進入 checkForSubscribersToSkip(Messager messager, String myPackage) 環節:

private void checkForSubscribersToSkip(Messager messager, String myPackage) {
    for (TypeElement skipCandidate : methodsByClass.keySet()) {
        TypeElement subscriberClass = skipCandidate;
        while (subscriberClass != null) {
            // 如果該訂閱類是 public 的,可以通過
            // 如果該訂閱類是 private 或者 protected 的,會被加入到 classesToSkip 中
            // 如果該訂閱類是默認修飾符,但是訂閱類的包和索引類的包不是同一個包,會被加入到 classesToSkip 中
            if (!isVisible(myPackage, subscriberClass)) {
                boolean added = classesToSkip.add(skipCandidate);
                if (added) {
                    String msg;
                    if (subscriberClass.equals(skipCandidate)) {
                        msg = "Falling back to reflection because class is not public";
                    } else {
                        msg = "Falling back to reflection because " + skipCandidate +
                                " has a non-public super class";
                    }
                    messager.printMessage(Diagnostic.Kind.NOTE, msg, subscriberClass);
                }
                break;
            }
            List<ExecutableElement> methods = methodsByClass.get(subscriberClass);
            if (methods != null) {
                // 校驗訂閱方法是否合格
                for (ExecutableElement method : methods) {
                    String skipReason = null;
                    VariableElement param = method.getParameters().get(0);
                    TypeMirror typeMirror = getParamTypeMirror(param, messager);
                    if (!(typeMirror instanceof DeclaredType) ||
                            !(((DeclaredType) typeMirror).asElement() instanceof TypeElement)) {
                        skipReason = "event type cannot be processed";
                    }
                    if (skipReason == null) {
                        TypeElement eventTypeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
                        if (!isVisible(myPackage, eventTypeElement)) {
                            skipReason = "event type is not public";
                        }
                    }
                    if (skipReason != null) {
                        boolean added = classesToSkip.add(skipCandidate);
                        if (added) {
                            String msg = "Falling back to reflection because " + skipReason;
                            if (!subscriberClass.equals(skipCandidate)) {
                                msg += " (found in super class for " + skipCandidate + ")";
                            }
                            messager.printMessage(Diagnostic.Kind.NOTE, msg, param);
                        }
                        break;
                    }
                }
            }
            // 查找父類
            subscriberClass = getSuperclass(subscriberClass);
        }
    }
}

用一句話來概括,checkForSubscribersToSkip(Messager messager, String myPackage) 做的事情就是如果這些訂閱類中牽扯到不可見狀態,那么就會被加入到 classesToSkip 中,導致后面生成索引類中跳過這些訂閱類。

createInfoIndexFile(String index)

經過篩選后,EventBusAnnotationProcessor 最終要生成一個索引類,具體的代碼就在 createInfoIndexFile(String index) 中:

private void createInfoIndexFile(String index) {
    BufferedWriter writer = null;
    try {
        JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
        int period = index.lastIndexOf('.');
        String myPackage = period > 0 ? index.substring(0, period) : null;
        String clazz = index.substring(period + 1);
        writer = new BufferedWriter(sourceFile.openWriter());
        // 下面都是自動生成的代碼
        if (myPackage != null) {
            writer.write("package " + myPackage + ";\n\n");
        }
        writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");
        writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");
        writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");
        writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");
        writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");
        writer.write("import java.util.HashMap;\n");
        writer.write("import java.util.Map;\n\n");
        writer.write("/** This class is generated by EventBus, do not edit. */\n");
        writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
        writer.write("    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
        writer.write("    static {\n");
        writer.write("        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");
        writeIndexLines(writer, myPackage);
        writer.write("    }\n\n");
        writer.write("    private static void putIndex(SubscriberInfo info) {\n");
        writer.write("        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");
        writer.write("    }\n\n");
        writer.write("    @Override\n");
        writer.write("    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");
        writer.write("        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");
        writer.write("        if (info != null) {\n");
        writer.write("            return info;\n");
        writer.write("        } else {\n");
        writer.write("            return null;\n");
        writer.write("        }\n");
        writer.write("    }\n");
        writer.write("}\n");
    } catch (IOException e) {
        throw new RuntimeException("Could not write source for " + index, e);
    } finally {
        if (writer != null) {
            try {
                writer.close();
            } catch (IOException e) {
                //Silent
            }
        }
    }
}

private void writeIndexLines(BufferedWriter writer, String myPackage) throws IOException {
    for (TypeElement subscriberTypeElement : methodsByClass.keySet()) {
        // 如果是被包含在 classesToSkip 中的,就跳過
        if (classesToSkip.contains(subscriberTypeElement)) {
            continue;
        }
        // 生成對應的 index
        String subscriberClass = getClassString(subscriberTypeElement, myPackage);
        if (isVisible(myPackage, subscriberTypeElement)) {
            writeLine(writer, 2,
                    "putIndex(new SimpleSubscriberInfo(" + subscriberClass + ".class,",
                    "true,", "new SubscriberMethodInfo[] {");
            List<ExecutableElement> methods = methodsByClass.get(subscriberTypeElement);
            writeCreateSubscriberMethods(writer, methods, "new SubscriberMethodInfo", myPackage);
            writer.write("        }));\n\n");
        } else {
            writer.write("        // Subscriber not visible to index: " + subscriberClass + "\n");
        }
    }
}

上面的這幾行代碼應該很眼熟吧,MyEventBusIndex 就是從這個模子里“刻”出來的,都是寫死的代碼。不同的是在 writeIndexLines(BufferedWriter writer, String myPackage) 中會把之前包含在 classesToSkip 里的跳過,其他的都自動生成 index 。最后就能得到一個像 MyEventBusIndex 一樣的索引類了。

另外補充一句,如果你想使用像 MyEventBusIndex 一樣的索引類,需要在初始化 EventBus 時通過 EventBus.builder().addIndex(new MyEventBusIndex()).build(); 形式來將索引類配置進去。

話已至此,整個 EventBusAnnotationProcessor 我們大致地分析了一遍。利用編譯時注解的特性來生成索引類是一種很好的解決途徑,避免了程序在運行時利用反射去獲取訂閱方法,提高了運行效率的同時又提高了逼格。

0006B 總結

從頭到尾分析下來,發現 EventBus 真的是一款不錯的開源框架,完美詮釋了觀察者模式。從之前的 2.0 版本到現在的 3.0 版本,加入了注解的同時也減少了反射,提高了性能,為此增添了不少的色彩。

EventBus 相似的還有 Otto 框架,當然現在業內也有不少使用 RxJava 來實現具備發布/訂閱功能的 “RxBus” 。對此我的看法是,如果是小型項目,可以使用 RxBus 來代替 EventBus ,但是一旦項目成熟起來,涉及到模塊之前通信和解耦,那么還是使用更加專業的 EventBus 吧。畢竟若是新手想上手 RxJava 還是需要一段時間的。

今天就到這了,對 EventBus 有問題的同學可以留言,bye bye !

0007B References

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容