一、前言
從 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 是一個基於釋出/訂閱的設計模式的事件匯流排,其架構圖如下。
從架構圖上來看,還是很簡單的,跟我們所最熟悉的觀察者模式是類似的。大概工作過程就是: (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 註冊訂閱者
如上面的時序圖,事件的訂閱大概可以分成 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 的關聯,具體怎麼關聯的呢,請看圖。
這裡分別用了 2 個 HashMap 來描述。subscriptionsByEventType 記錄了一個事件應該呼叫哪個訂閱者的訂閱方法來處理。而 typesBySubscriber 記錄了訂閱者訂閱了哪些事件。
至此,訂閱者的註冊完成之後,也就相當於是 EventBus 的初始化完成了,那麼接下來就可以愉快傳送事件了。
3.2.2 事件傳送
事件的傳送看起來好像也比較簡單。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。
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 登出訂閱者
登出就比較簡單了,如下程式碼,主要就是從 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 以及方法的反射呼叫,而基於此為我們封裝了一套完整的事件 分發-傳遞-響應 機制,當然也是一個非常優秀的框架。總結一下,其主要的特點:
- 事件的定義是由使用者自己來定義的,只要是 POJO 就可以。且需要注意的是,事件具有向上傳遞的特性,即傳送一個事件會連同它的父類事件一起傳送。
- 關於 ThreadMode,預設則傳送者執行緒傳送,並且是同步的。對於主執行緒則通過主執行緒的 MainLooper 來實現,非同步事件則由執行緒池來實現。
- EventBus 的例項可以通過 getDefault() 的單例來獲取,也可以通過 EventBusBuilder 的 builder() 方法來獲取,但要注意它們不是同一個 EventBus 的例項,也不能互相傳送事件。
最後,感謝你能讀到並讀完此文章,如果分析的過程中存在錯誤或者疑問都歡迎留言討論。如果我的分享能夠幫助到你,還請記得幫忙點個贊吧,謝謝。