EventBus詳解及原始碼分析

仰簡發表於2019-04-28

一、前言

從 EventBus 的介紹中,EventBus 給的定位是:

Event bus for Android and Java that simplifies communication between Activities, Fragments, Threads, Services, etc. Less code, better quality

簡單理解一下就是 Event bus 給 Android 及 Java (當然主要是 Android)的 Activity,Fragment,Threads,Services 之間提供一個簡單的通訊方式,從而能讓我們用質量高且少的程式碼來完成我們的功能。

二、EventBus 詳解之官文解讀

2.1 架構概述

EventBus 是一個基於釋出/訂閱的設計模式的事件匯流排,其架構圖如下。

EventBus 架構圖

從架構圖上來看,還是很簡單的,跟我們所最熟悉的觀察者模式是類似的。大概工作過程就是: (1) Subscriber 也就是訂閱者,訂閱一個 Event,其中 Event 是自己定義的,符合 POJO 的規範即可。 (2) 在需要時,Publisher 也就是釋出者,就可將事件通過 EventBus 分發布相應的訂閱者 Subscriber。

恩,就是如此簡單。當然,其實現遠不會這麼簡單了,還是幹了很多髒活和累活的。

2.2 特性概述

  • 簡化了元件之間的通訊
    • 事件傳送者和接收者分離
    • 在Activity,Fragment和後臺執行緒中都可以很好的進行事件的分發
    • 避免複雜且容易出錯的依賴關係和生命週期問題
  • 簡化程式碼
  • 快,多快呢?
  • 小(約50k)
  • 已經通過100,000,000+安裝的應用程式在實踐中得到證實
  • 具有指定分發執行緒,優先順序等高階功能。

2.3 功能概述

  • 事件訂閱 / 分發 這是其最核心的功能,也是要在原始碼分析中需要深入的。
  • 指定分發執行緒 Delivery Threads (ThreadMode) 即指定訂閱者將在哪個執行緒中響應事件的處理。恩,這主要用於如網路或者耗時的執行緒處理,需要切換到主執行緒來更新 UI 的場景。當然,如果用 RxJava 的話,這個就再熟悉不過了。
  • 配置 Configuration 通過 EventBusBuilder 類對 EventBus 進行各方面的配置,如配置可允許事件的分佈沒有訂閱者處理,而不報異常。
  • 粘性事件 Sticky Events 將使用者傳送過的事件暫時儲存在記憶體中,當訂閱者一旦訂閱了該事件,就可以立即收到該事件。恩,這個就與粘性廣播的概念是一致的了。
  • 優先順序以及取消事件 主要是指定 Subscriber 的優先順序,其預設優先順序為 0,指定了較高的優先順序就可以優先收到事件進行處理。而收到事件的 Subcriber 中提前取消事件的繼續分發,從而中斷事件的繼續分發。恩,這個也跟廣播裡的 abortBroadcast 概念一樣。
  • Subscriber Index 用中文不知道咋說。這個是 3.0 的版本加的,主要是將事件處理中的對事件的反射處理,提前到編譯時來處理了。從而提高效能。當然,主要也是推薦在 Android 下使用。
  • 非同步執行器 AsyncExecutor 主要就是一個執行緒池,可以理解就是一個幫助類。

三、原始碼解析

3.1 開始嚮導

EventBus 的官方文件為我們提供了經典的使用 EventBus 的 3 個過程。

3.1.1定義事件

普通的 POJO 即可

public class MessageEvent {
 
    public final String message;
 
    public MessageEvent(String message) {
        this.message = message;
    }
}
複製程式碼

3.1.2 準備訂閱者

文章是基於 EventBus 3 進行分析,對於訂閱者的方法命名可以自由命名了,而不需要採用約定命名。

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}
 
// This method will be called when a SomeOtherEvent is posted
@Subscribe
public void handleSomethingElse(SomeOtherEvent event) {
    doSomethingWith(event);
}
複製程式碼

準備好了訂閱者之後,我們還需要向匯流排註冊這個訂閱者,這個我們的訂閱者方法才能接收到相應的事件。

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}
 
@Override
public void onStop() {
    EventBus.getDefault().unregister(this);
    super.onStop();
}
複製程式碼

3.1.3 傳送事件

我們可以在任何地方傳送事件。

EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
複製程式碼

3.2 原始碼解析

根據前文的 3 步經典使用方法,原始碼的分析大概也分成如下 4 步來進行。

3.2.1 註冊訂閱者

EventBus Register.jpg
如上面的時序圖,事件的訂閱大概可以分成 6 個步驟,下面來一一分析。

方法getDefault()

public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }
複製程式碼

getDefault() 方法就是一個“懶漢”的單例模式,目的就是讓我們能在全域性對 EventBus 的引用進行使用。值得關注的是,這個懶漢的單例模式實現並不會有多執行緒的安全問題。因為對於 defaultInstance 的定義是 volatile 的。

static volatile EventBus defaultInstance;
複製程式碼

接下來繼續看看構造方法。

構造方法EventBus()

public EventBus() {
        this(DEFAULT_BUILDER);
    }

    EventBus(EventBusBuilder builder) {
        logger = builder.getLogger();
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadSupport = builder.getMainThreadSupport();
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        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);
        logSubscriberExceptions = builder.logSubscriberExceptions;
        logNoSubscriberMessages = builder.logNoSubscriberMessages;
        sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
        sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
        throwSubscriberException = builder.throwSubscriberException;
        eventInheritance = builder.eventInheritance;
        executorService = builder.executorService;
    }
複製程式碼

構造方法使用了常見的 Builder 模式對 EventBus 中的各個屬性進行了初始化。這裡使用的是預設配置。一般情況下,我們也都是使用 getDefault() 及其預設配置來獲取例項的。我們也可以通過配置 EventBusBuilder 來 build 出一個自己的例項,但是要注意的是,後面的註冊、登出以及傳送事件都要基於此例項來進行,否則就會發生事件錯亂髮錯的問題。

方法register() 如果不看方法的實現,根據經驗判斷,我想當 register 的發生,應該是將訂閱者中用於接收事件的方法與該事件關聯起來。那是不是這樣呢?

public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        // 1.通過訂閱者類找到其訂閱方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
           // 2. 然後進行逐個訂閱
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
複製程式碼

首行來看看找訂閱方法。這裡封裝了一個專門的類 SubscriberMethodFinder,並且通過其方法 findSubscriberMethods() 來進行查詢,這裡就不貼 findSubscriberMethods() 的程式碼,而是看看其內部實際用於尋找訂閱方法的 findUsingReflection()。

private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
           ......
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            ......
        }
        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()));
                        }
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                    ......
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                ......
            }
        }
    }
複製程式碼

總結一下,該方法主要就是尋找訂閱者類中的公有方法,且其引數唯一的以及含有註解宣告@Subscribe 的方法。就是我們在寫程式碼時所定義的訂閱者方法了。找到訂閱者方法後,會將其 method(方法的反射類 Method) 、event、thread mode 以及優先順序等封裝成一個 SubscribeMethod 然後新增到 FindState 中。

這個 FindState 是 SubscriberMethodFinder 的一個內部類。其用了大小隻有 4 的 FIND_STATE_POOL 來進行管理,這樣避免了 FindState 物件的重複建立。

最後在 find 中會將所找到訂閱者方法新增到 “METHOD_CACHE” 中,這是一個 ConcurrentHashMap 的結構。

Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
複製程式碼

方法subscribe()

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            ......
            }
        }

        // 按優先順序進行插入
        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);

        // 粘性事件,註冊就會傳送
        if (subscriberMethod.sticky) {
                .....
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }
複製程式碼

subscribe() 方法完成事件與 subscribe 、 subscribeMethod 的關聯,具體怎麼關聯的呢,請看圖。

subscriptionsByEventType.jpg

typesBySubscriber.jpg

這裡分別用了 2 個 HashMap 來描述。subscriptionsByEventType 記錄了一個事件應該呼叫哪個訂閱者的訂閱方法來處理。而 typesBySubscriber 記錄了訂閱者訂閱了哪些事件。

至此,訂閱者的註冊完成之後,也就相當於是 EventBus 的初始化完成了,那麼接下來就可以愉快傳送事件了。

3.2.2 事件傳送

EventBus post.jpg
事件的傳送看起來好像也比較簡單。

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) {
               ......
            }
            try {
               // 在傳送狀態下,通過遍歷事件佇列逐個傳送事件
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }
複製程式碼

post() 方法的主要過程都寫在程式碼註釋裡了。這裡主要關注下 currentPostingThreadState,它的定義如下:

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };
複製程式碼

而 PostingThreadState 的定義如下。

final static class PostingThreadState {
        final List<Object> eventQueue = new ArrayList<>();
        boolean isPosting;
        boolean isMainThread;
        Subscription subscription;
        Object event;
        boolean canceled;
    }
複製程式碼

這裡用了 ThreadLocal 來儲存 PostingThreadState。ThreadLocal 的主要作用是使得所定義的資源是執行緒私有的。那麼,也就是說,對於每一個傳送事件的執行緒其都有一個唯一的 PostingThreadState 來記錄事件傳送的佇列以及狀態。

下圖是 ThreadLocal 在 Thread 中的資料結構描述,而這裡的 PostingThreadState 就是下圖中的 Value。

ThreadLocal.jpg

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        if (eventInheritance) {
            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 {
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        ......
複製程式碼

postSingleEvent 所做的事情主要就是根據要傳送的事情收集事件,然後逐個傳送。收集的什麼事情呢,很簡單,就是收集它的父類事件。也就是說,當我們傳送一個事件時,它的所有父類事件也同時會被髮送,這裡的父類還包括了 Object 在內。所以,這裡需要開發者們非常注意了:

事件是具有向上傳遞性質的

接下來的 postSingleEventForEventType 就是從 subscriptionsByEventType 找出事件所訂閱的訂閱者以及訂閱者方法,即 CopyOnWriteArrayList。 然後再通過方法 postToSubscription() 將事件逐個逐個向訂閱者進行傳送。在 postToSubscription() 方法中會根據不同的 ThreadMode 來決定不同的執行緒排程策略,具體在下面講。而不管採用何種策略,最終都將會通過invokeSubscriber() 進行方法的反射呼叫來進行訂閱方法的呼叫。

void invokeSubscriber(Subscription subscription, Object event) {
       ......
       subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
       ......
    }

複製程式碼

至此,事件的傳送就完成了。很簡單,其真實的面目就是一個方法的反射呼叫。而對於事件的傳送,這裡還應該關注一下執行緒的排程策略。

3.2.3 執行緒排程策略。

上面說到執行緒的排程策略在 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);
        }
    }
複製程式碼

針對每個不同的策略,下面來簡單看一看。 POSTING 傳送者執行緒直接傳送,也是 ThreadMode 的預設值。 MAIN 如果傳送者是主執行緒,則直接傳送。如果不是則交給 mainThreadPoster 來傳送。mainThreadPoster 是 HandlerPoster 型別,該類繼承了 Handler 並實現了 Poster 介面,其例項是由 AndroidHandlerMainThreadSupport 所建立的。AndroidHandlerMainThreadSupport 包含了主執行緒的 Looper ,而 HandlerPoster 包含了一個連結串列結構的佇列 PendingPostQueue,事件也將插入到 PendingPostQueue 中。每一個事件都會以一個空 Message 傳送到主執行緒的訊息佇列,訊息處理完再取出下一個事件,同樣以 Message 的形式來執行,如此反覆執行。直到佇列為空。 MAIN_ORDERED 一般來說也是通過 mainThreadPoster 來傳送,異常情況下才通過傳送者執行緒來傳送。從程式碼側來看,它主要就是屬於 MAIN 策略的第二種情況。 BACKGROUND 如果當前為主執行緒,則將其投遞到 backgroundPoster,否則直接傳送。這裡的 backgroundPoster 就是一個子執行緒。 ASYNC 非同步模式下,最終就是通過 ExecutorService ,也即執行緒池來傳送的。asyncPoster 即 AsyncPoster,其關於執行緒排程的關鍵程式碼為:

eventBus.getExecutorService().execute(this);
複製程式碼

3.2.4 登出訂閱者

EventBus unregister.jpg

登出就比較簡單了,如下程式碼,主要就是從 typesBySubscriber 這個 map 中將訂閱者移除掉,並且解決訂閱者和事件的關聯。

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());
        }
    }
複製程式碼

四、總結

從整個分析過程來看,EventBus 是一個比較簡單的框架,其涉及到的核心知識是 ThreadLocal 以及方法的反射呼叫,而基於此為我們封裝了一套完整的事件 分發-傳遞-響應 機制,當然也是一個非常優秀的框架。總結一下,其主要的特點:

  1. 事件的定義是由使用者自己來定義的,只要是 POJO 就可以。且需要注意的是,事件具有向上傳遞的特性,即傳送一個事件會連同它的父類事件一起傳送。
  2. 關於 ThreadMode,預設則傳送者執行緒傳送,並且是同步的。對於主執行緒則通過主執行緒的 MainLooper 來實現,非同步事件則由執行緒池來實現。
  3. EventBus 的例項可以通過 getDefault() 的單例來獲取,也可以通過 EventBusBuilder 的 builder() 方法來獲取,但要注意它們不是同一個 EventBus 的例項,也不能互相傳送事件。

最後,感謝你能讀到並讀完此文章,如果分析的過程中存在錯誤或者疑問都歡迎留言討論。如果我的分享能夠幫助到你,還請記得幫忙點個贊吧,謝謝。

相關文章