EventBus源碼解析

前言

EventBus是安卓(Java中也可以用)開發中非常流行的一個第三方庫,是一種發佈/訂閱事件的總線.擁有以下特性:
* 簡化了組件間的通信,可應用與Activity,Fragment和後臺線程
* 將事件的發送方和接收方解耦
* 體積小

現在的EventBus早已經從2.0進入3.0版本,而且最新版3.1.1也已經穩定了挺長的時間,從17年底再沒有更新過GitHub.因爲之前的項目中集成的都是3.0.0版本的,因此以該版本源碼做分析.常見的使用方法就不介紹了,官方文檔裏都有.所有操作,包括髮送事件、在訂閱事件的組件中註冊
和解綁,都是通過EventBus.getDefault()創建一個單例對象,完成後續方法的調用.

register(註冊事件)

先看註冊事件的部分,因爲EventBus通常的使用順序就是接收方註冊->發送方定義發送事件->接收方定義接受入口->發送方發送事件->收到事件。當然從數據流向看先從發送事件開始也是可以理解的:

EventBus源碼解析_EventBus-Publish-Subscribe.png

源碼:

public void register(Object subscriber) {
    //註冊事件所在的類
    Class<?> subscriberClass = subscriber.getClass();
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

其中的subscriberMethodFinder是SubscriberMethodFinder類型對象,在EventBus的默認構造時初始化。通過findSubscriberMethods方法可以找到該類中添加了Subscribe註解的方法。只有添加了該註解的方法(方法名可以任意)纔可以收到事件。如果該類沒有添加過註解,拋異常。

findSubscriberMethods

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

METHOD_CACHE是Map<Class<?>, List<SubscriberMethod>>類型,它存儲的value是該類中所有添加了Subscribe註解的方法(被包裝爲SubscriberMethod類)的集合。該類第一次註冊事件的時候集合爲null,默認ignoreGeneratedIndex爲false,會走到方法findUsingInfo.而在該方法內部,默認的情況下經過一系列邏輯判斷又會走到findUsingReflectionInSingleClass

findUsingReflectionInSingleClass

精簡核心代碼如下:

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();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    Class<?> eventType = parameterTypes[0];
                    if (findState.checkAdd(method, eventType)) {
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            }
    }
}

前面的操作已經將該類封裝到了靜態內部類FindState中,作爲clazz字段。這裏重新加工要使用的FindState的步驟是:
* 通過getDeclaredMethods獲取該類本身定義的所有類型的方法.因爲訂閱類通常是Activity這樣的組件,層層繼承,這樣效率更高。
* 遍歷方法數組,獲取方法的修飾符並進行判斷。這也是爲什麼將接受事件的方法用private、static修飾會拋異常的原因
* 符合要求,獲取該方法的參數個數,必須爲1。
* 參數個數爲1,則判斷方法內是否定義了Subscribe註解。若有,則獲取參數的類,也就是發送事件方那邊所定義的事件類型。
* 通過FindState的checkAdd方法判斷,這裏有兩層判斷。校驗通過,會包裝一個SubscriberMethod對象添加到FindState的subscriberMethods集合中。
* 第一層,只是快速簡單的判斷事件類型。FindState在初始化的時候創建了對象anyMethodByEventType,類型爲Map<Class, Object>。訂閱事件的類如果只定義了一個符合要求的接收事件的方法,那麼以方法接收的事件類爲key存儲該方法。
* 第二層校驗,通常一個訂閱者中不會有多個方法接收同一事件,但如果定義了多個呢?這時anyMethodByEventType的value正是前一個定義的接收事件的方法,會調用checkAddWithMethodSignature方法校驗。源碼如下:

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

    String methodKey = methodKeyBuilder.toString();
    Class<?> methodClass = method.getDeclaringClass();
    Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
    if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
        // Only add if not already found in a sub class
        return true;
    } else {
        // Revert the put, old class is further down the class hierarchy
        subscriberClassByMethodKey.put(methodKey, methodClassOld);
        return false;
    }
}

methodKeyBuilder是一個StringBuilder對象。可以看出,每次調用該方法都將重置它,並且以方法名+'>'+事件類型作爲鍵值。subscriberClassByMethodKey是一個Map<String, Class>對象,存儲的value爲接受事件的方法所在類。只有該Map存在重複鍵時該方法纔會返回false,但是我們定義的鍵值,因爲同一個類中不會出現同名同參數類型同參數個數的方法,所以不會重複,返回true。

額外話:該方法什麼情況會返回false?假設有一個類ActivityA,其中定義了訂閱和接受事件的方法,另外有一個類ActivityB繼承了A作爲頁面展示,並重寫了其中的訂閱方法。這時候在findUsingInfo方法中的while循環體內,會繼續向B的父類A調用。這時的subscriberClassByMethodKey相同的鍵對應的value就不爲空了,並且由於A、B之間 繼承關係,方法會走入else分支返回false。最終拋出IllegalStateException。因此要注意派生關係在訂閱事件的應用

繼續向下,出現了接受同一事件的多個方法,這時anyMethodByEventType的value用任意的對象代替以“消費”該方法,源碼中直接取的FindState的當前對象。接着又一次調用了checkAddWithMethodSignature方法。前面說過了因爲鍵不會重複的原因,因此最終返回true。該接受事件方法校驗通過。也就是說,在同一個類中定義多個接受同一事件的方法,那麼這些方法都會執行。

準備好事件集合

上面的方法調用完畢後就獲取到了訂閱事件方法的集合,並且將其緩存到METHOD_CACHE中,這樣同一個類多次註冊事件時就不必做重複的大量邏輯判斷。這時候就可以遍歷該集合,正式開始事件的訂閱了。

subscribe(訂閱事件)

精簡代碼:

// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    }

    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

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

    //後面分析stick事件
}

將訂閱事件的類和訂閱方法包裝成一個Subscription對象。CopyOnWriteArrayList相當於一個線程安全的ArrayList,其實就是內部實現的方法上加了鎖。subscriptionsByEventType是類型爲Map<Class<?>, value爲CopyOnWriteArrayList<Subscription>>的對象,會以事件類型爲鍵存儲所有訂閱該事件的方法集合,根據@SubScribe的priority,優先級高的會添加到頭部,保證了多個訂閱方法的執行順序。typesBySubscriber是類型爲Map<Object, List<Class<?>>>的對象,用於存儲該訂閱類中訂閱的所有事件類型

post(發送事件)

接收方註冊了事件,訂閱了事件,就來看發送方發佈事件的代碼.最常用的post方法,其中的參數event就是我們在要發送事件的地方自己定義的一個對象,通常是一個靜態內部類:

public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);

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

首先在EventBus第一次被初始化的時候創建了一個ThreadLocal類的對象currentPostingThreadState,其泛型形參爲EventBus中的靜態內部類PostingThreadState.在post的時候會獲取該內部類,從而獲取到其內部的eventQueue(事件隊列,用ArrayList存儲),並且將要發送的事件(抽象爲發送方自己定義的對象)加入到該隊列中.isPosting可以避免併發情況下一個Event的多次被調用.判斷事件不處於發送中的狀態後,開始依次從隊列中的頭部取出事件發送,直到隊列爲空.可以看出,發送事件的同時會將事件移除出隊列.發送單個事件的方法爲postSingleEvent。

postSingleEvent

忽略一些邏輯判斷,核心代碼是:

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

看到這個方postSingleEventForEventType,該方法內部的邏輯是:如果對應事件類型的訂閱方法存在,那麼會爲每個訂閱方法調用postToSubscription。它會比較發送事件和訂閱事件各自所在的線程來決定是否立即執行。符合條件的話,有訂閱類,有事件類型,最終會反射調用Method的invoke方法,也就是我們接受到了事件開始處理了。這樣整個發送->事件流也就結束了。

ThreadMode

EventBus定義的一個枚舉。我們通過訂閱方法上添加的Subscribe註解就可以設置這個值。它代表了訂閱方法在哪類線程被EventBus調用。設計之初就是讓發送和接收事件的線程獨立開來。共定義了四種線程:

POSTING

默認。訂閱事件和發送事件處在同一線程。最常用的是這種,它可以避免切換線程的損耗。但由於發送事件可能在主線程中,因此接收事件的方法不能做耗時操作,否則會阻塞線程。

MAIN

接收事件總會在主線程(UI線程)中,也要避免耗時操作。如果發送事件在子線程,則接收事件會放到MessageQueue中等待執行

BACKGROUND

接收事件總會在子線程中。如果發送事件也在子線程中,那麼接收事件應立即在發送事件所在的線程中執行。如果發送在主線程中,接收事件運行在EventBus維護的單一子線程中,因爲還可能存在其他接收事件的方法,所以也要避免阻塞。

ASYNC

接收事件在一個獨立的線程中執行,獨立於發送方所在線程和主線程。適合做耗時操作,如網絡請求。EventBus內部使用了線程池來複用這些異步的接收事件方法所在線程,但還是要避免同一時間內大量觸發此類型方法。

Poster

前面提到,如果接收和發送所處的線程不一樣,會將事件先放到隊列中等待執行。有這麼三種情況:

發送方子,接收方主

加入到HandlerPoster,這個類繼承了Handler,其實就是在enque方法中調用了sendMessage,又在handleMessage中調用了EventBus的invokeSubscriber方法,完成了接受事件方法的調用。

發送方主,接收方子

加入到BackgroundPoster,這個類實現類Runnable,在enque方法中找到了EventBus全局定義的一個默認的線程池:Executors.newCachedThreadPool(),獲取了一個無數量上限,無核心線程,可以複用空閒線程的線程池。enque方法加鎖,而且全局定義了一個volatile類型變量,保證了任一時間只有一個任務被執行,也就是前面說的單一子線程。在run方法中調用了invokeSubscriber。

接收方獨立

加入到AsyncPoster,它也實現Runnable。但不同於BackgroundPoster,它沒有加鎖和保證有序性,因此等待被執行的任務只要放入線程池就行了。

postSticky

假設有這種場景,我在登錄頁面輸入用戶名和密碼後跳轉到其他的一些界面,相應的頁面都要有所變化。那這時候發送事件的時候因爲新打開的頁面還沒有初始化,還沒有完成訂閱事件,subscriptionsByEventType裏不存在value,也就無法接收到事件。這時候就會用到postSticky.

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

和普通的發送事件相比,就是同步將要發送的事件類型和事件對象存儲到了stickyEvents中。然後到了訂閱事件時,會走到subsribe方法的該分支,默認精簡如下:

if (subscriberMethod.sticky) {

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

取出之前的stickyEvents,遍歷其鍵值對。校驗發送的和接受的是同一事件後,判斷如果存儲的事件對象不爲空,則調用postToSubscription方法。這樣也就保證了即使發送事件在先,註冊事件在後,也可以接收到事件。注意的是接受事件的方法的Subscribe註解中的sticky要置爲true.

unregister

/** 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 {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

取消訂閱的方法中,首先移除了所有訂閱了該事件的方法集合中該類中所定義的方法。然後移除了該類中所有的訂閱事件類型。

總結

EventBus作爲部分替代BroadcastReceiver和接口回調的一款知名框架,本身是基於觀察者模式實現的,實現原理總結了有下面幾個核心要點:
* 單例模式
* 建造者模式
* 包裝類
* 鍵值對存儲
* 使用鎖同步方法
* 基於註解使用
* 通過反射找匹配,大量的反射方法:getClass、getSuperclass、getDeclaredMethods、getParameterTypes、getAnnotation、isAssignableFrom、invoke

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章