深入剖析 Android 開源庫 EventBus 的源碼詳解

文章目錄

  • 前言
  • 一、EventBus 簡介
    • EventBus 三要素
    • EventBus 線程模型
  • 二、EventBus 使用
    • 1.添加依賴
    • 2.EventBus 基本使用
      • 2.1 定義事件類
      • 2.2 注冊 EventBus
      • 2.3 EventBus 發起通知
  • 三、EventBus 源碼詳解
    • 1.Subscribe 注解
    • 2.注冊事件訂閱方法
      • 2.1 EventBus 實例
      • 2.2 EventBus 注冊
        • 2.2.1 SubscriberMethodFinder#findSubscriberMethods()
          • 2.2.1.1 SubscriberMethodFinder#findUsingInfo()
          • 2.2.1.2 SubscriberMethodFinder#findUsingReflectionInSingleClass()
          • 2.2.1.3 SubscriberMethodFinder#FindState#checkAdd()
        • 2.2.2 EventBus#subscribe()
    • 3.EventBus 取消注冊
      • 3.1 EventBus#unregister()
      • 3.2 EventBus#unsubscribeByEventType()
    • 4.EventBus 發布、處理事件
      • 4.1 EventBus#post()
      • 4.2 EventBus#postSingleEvent()
      • 4.3 EventBus#postSingleEventForEventType()
      • 4.4 EventBus#postToSubscription()
        • 4.4.1 EventBus#invokeSubscriber()
        • 4.4.2 HandlerPoster#enqueue()
        • 4.4.3 EventBus#invokeSubscriber()
        • 4.4.4 BackgroundPoster#enqueue()
        • 4.4.5 AsyncPoster#enqueue()
      • **EventBus** 發布事件(包括粘性事件)及處理流程
    • 5.EventBus 粘性事件
      • 5.1 EventBus#postSticky()
      • 5.2 EventBus#subscribe()
      • 5.3 EventBus#checkPostStickyEventToSubscription()
    • 6.EventBus 之 Subscriber Index
      • 6.1 EventBusIndex
      • 6.2 EventBusBuilder#addIndex()
      • 6.2 EventBusBuilder#installDefaultEventBus()
      • 6.3 SubscriberMethodFinder#findUsingInfo()
        • 6.3.1 SubscriberMethodFinder#getSubscriberInfo()
        • 6.3.2 SimpleSubscriberInfo#getSubscriberMethods()
      • 小結
  • 總結
  • 參考


前言

Android 項目開發的時候,經常會遇到組件與組件之間、組件與后臺線程之間的通信, 比如:子線程中進行數據請求,請求數據成功后,通過 HandlerRxJava 等來通知 UI 主線程進行更新操作;兩個 Fragment 之間可以通過 Listener 進行通信,簡單的通信通過上述的技術手段也是可以滿足需求的,但隨著項目的不斷迭代更新,程序越來越龐大時,就會要寫很多的代碼,從而導致代碼嚴重的耦合問題。為了優化該問題,EventBus 事件總線應運而生。


一、EventBus 簡介

EventBus 事件總線,是一款由 GreenRobot 開源的在 Android 開發中使用的發布/訂閱事件總線框架,基于觀察者模式,將事件的接收者和發送者分開解耦。用來替代廣播 BroadCastReceiverstartActivityForResultHandler異步回調等來實現各組件間、組件與后臺線程間的通信。

EventBus 優點:

  • 簡化組件之間的通訊方式;
  • 對通信雙方進行解藕;
  • 通過 ThreadMode 靈活切換工作線程;
  • 速度快、性能好、庫比較小、不占內存;

首先看一下官方給出的 EventBus 原理圖:
EventBus 原理圖
Publisher 使用 post() 函數發出一個 Event 事件,SubscriberonEvent() 函數中接收事件、進行后續的處理。

EventBus 三要素

使用 EventBus 時的三個重要參與者:EventPublisherSubscriber

  • Event:事件,它可以是任意類型;
  • Publisher:事件的發布者,可以在任意線程里發布事件,一般情況下,使用 EventBus.getDefault() 方法就可以得到一個 EventBus 對象,然后再調用其 post(Object) 方法即可;
  • Subscriber:事件訂閱者,在 EventBus3.0 之前我們必須定義以 onEvent 開頭的那幾個方法,分別是:onEventonEventMainThreadonEventBackgroundThreadonEventAsync,而在 3.0 之后事件處理的方法名可以隨意取,不過需要加上注解 @subscribe(),并且要指定線程模型,默認是 POSTING

EventBus 線程模型

ThreadMode 線程模式,通過 threadMode 設置 onReceiveMsg() 方法將在哪個線程環境下被調用。其中 threadMode 屬性有如下幾個可選值:

  • ThreadMode.POSTING:默認的線程模式,訂閱者的訂閱方法將在發布事件的同一線程中被調用,避免了線程切換,效率高;但是可能發布事件的線程是主線程,所以需要避免在訂閱方法中處理耗時操作;
  • ThreadMode.MAIN:訂閱者的訂閱方法將在 UI 線程被調用,如在 UI 主線程發布事件,則直接在主線程處理事件;如果在子線程發送事件,則先將事件入隊列,然后通過 Handler 切換到主線程,依次處理事件;
  • ThreadMode.MAIN_ORDERED:無論在那個線程發送事件,都先將事件入隊列,然后通過 Handler 切換到主線程,依次處理事件(注意:該模式下可以確保 post() 的調用是非阻塞的);
  • ThreadMode.BACKGROUND:表示訂閱者的訂閱方法在后臺線程。如果發布事件的線程是 UI 主線程,那么將開啟一個后臺線程執行訂閱方法;如果發布事件的線程是在后臺線程,那么事件處理函數就使用該線程;
  • ThreadMode.ASYNC:表示無論發布事件的線程是哪一個,訂閱者的訂閱方法始終會新建一個子線程來執行。所以這種情況下可以做耗時操作,但是需要避免在同一時間進行大量的異步訂閱,控制并發線程的數量。

說了這么多,最后再來看一下 EventBus 有何缺點:

  • 使用的時需定義很多 Event 類;
  • 需要自己注冊和反注冊,如果忘了反注冊就會導致內存泄漏;
  • Event 在注冊的時候會通過反射去遍歷注冊對象的方法,并在其中找出帶有 @subscriber 標簽的方法,性能不高(可以通過編譯時解析注解,優化運行時反射帶來的性能損耗)。

二、EventBus 使用

1.添加依賴

app 或底層 base 庫中的 builde.gradle 文件中導入依賴庫:

imlementation ‘org.greenrobot:eventbus:3.2.0

2.EventBus 基本使用

通過 EventBus 的三個重要參與者:EventSubscriberPublisher 來一步步學習 EventBus 的基本使用,

2.1 定義事件類

public class EventMessage<T> {private int code;	// 事件類型private T data;		// 數據public EventMessage(int code, T data){this.code=code;this.data=data;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public T getData() {return data;}public void setData(T data) {this.data = data;}@Overridepublic String toString() {return "EventMessage{" + "code=" + code + ", data=" + data + '}';}
}

2.2 注冊 EventBus

public class EventBusActivity extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);}@Overrideprotected void onStart() {super.onStart();// 注冊 EventBusEventBus.getDefault().register(this);}// 接收事件、線程模式為 ThreadMode.POSTING@Subscribe(threadMode = ThreadMode.POSTING, priority = 1)public void onReceiveMsg(EventMessage message){Log.e("EventBus_Subscriber", "onReceiveMsg_POSTING: " + message.toString());}// 接收事件、線程模式為 ThreadMode.MAIN@Subscribe(threadMode = ThreadMode.MAIN, priority = 1)public void onReceiveMsg1(EventMessage message){Log.e("EventBus_Subscriber", "onReceiveMsg_MAIN: " + message.toString());}// 接收事件、線程模式為 ThreadMode.MAIN_ORDERED@Subscribe(threadMode = ThreadMode.MAIN_ORDERED, priority = 1)public void onReceiveMsg2(EventMessage message){Log.e("EventBus_Subscriber", "onReceiveMsg_MAIN_ORDERED: " + message.toString());}// 接收事件、線程模式為 ThreadMode.BACKGROUND@Subscribe(threadMode = ThreadMode.BACKGROUND, priority = 1)public void onReceiveMsg3(EventMessage message){Log.e("EventBus_Subscriber", "onReceiveMsg_BACKGROUND: " + message.toString());}// 接收事件、線程模式為 ThreadMode.ASYNC、同時設置 sticky 為 true,表示粘性事件@Subscribe(threadMode = ThreadMode.ASYNC, sticky = true, priority = 1)public void onReceiveMsg4(EventMessage message){Log.e("EventBus_Subscriber", "onReceiveMsg__ASYNC: " + message.toString());}@Overrideprotected void onDestroy() {super.onDestroy();// 解除注冊的 EventBusEventBus.getDefault().unregister(this);}
}

2.3 EventBus 發起通知

通過 EventBus#post(eventMessage) 方法或者 EventBus#postSticky(eventMessage) 方法來發起事件:

@OnClick(R2.id.send_common_event)
public void clickCommon(){EventMessage message = new EventMessage(1, "發送普通事件");EventBus.getDefault().post(message);
}@OnClick(R2.id.send_sticky_event)
public void clickSticky(){EventMessage message = new EventMessage(1, "發送黏性事件");EventBus.getDefault().postSticky(message);
}

至此,通過 EventBus 的 post() 方法發起的事情,在 EventBusActivity 中就可以收到并做后續的處理。postSticky() 方法最終也是調用的 EventBus 的 post() 方法,后續通過分析源碼來進行剖析其具體過程。


三、EventBus 源碼詳解

EventBus 的實現原理主要包括如下幾個方面的內容:

  • Subscribe 注解
  • 注冊事件訂閱方法
  • 取消注冊
  • 發布、處理事件
  • 粘性事件
  • Subscriber Index

1.Subscribe 注解

EventBus3.0 開始用 Subscribe 注解配置事件訂閱方法,其共有三個參數(可選):threadModeboolean stickyint priority。 完整的寫法如下:

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true, priority = 1)
public void onReceiveMsg(EventMessage message) {Log.e(TAG, "onReceiveMsg: " + message.toString());
}
  • threadMode:用來設置 onReceiveMsg() 方法將在哪個線程環境下被調用,共有五種模式,參考上面的簡介;
  • sticky:一個 Boolean 類型的變量,默認值為 false,即不開啟黏性 sticky 特性。其作用是訂閱者可以先不進行注冊,如果 post() 事件已經發出,再注冊訂閱者,同樣可以接收到事件,并進行處理;
  • priority:優先級,是一個 int 類型的變量,默認值為 0。值越大,優先級越高,越優先接收到事件。注意:只有在 post() 事件和事件接收處理處于同一個線程環境的時候,才有意義。

具體看下 Subscribe 注解的實現:

@Documented 
@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.METHOD}) 
public @interface Subscribe { // 指定事件訂閱方法的線程模式,即在哪個線程執行事件訂閱方法處理事件,默認為 POSTING ThreadMode threadMode() default ThreadMode.POSTING; // 是否支持粘性事件,默認為 false boolean sticky() default false; // 指定事件訂閱方法的優先級,默認為0,如果多個事件訂閱方法可以接收相同事件的,則優先級高的先接收到事件 int priority() default 0; 
} 

在使用 Subscribe 注解時可以根據需求指定 threadModestickypriority 三個屬性值。

2.注冊事件訂閱方法

EventBus 注冊訂閱事件流程
結合 EventBus 注冊事件流程圖,便于更好的理解源碼執行流程,首先 EventBus 注冊事件的方式如下:

EventBus.getDefault().register(this);

EventBus#getDefault() 是一個單例方法,保證當前只有一個 EventBus 實例對象:

2.1 EventBus 實例

public class EventBus {static volatile EventBus defaultInstance;// 默認的 EventBusBuilder 實例對象private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();// 通過 double check 雙重校驗獲取 EventBus 單例對象public static EventBus getDefault() {if (defaultInstance == null) {synchronized (EventBus.class) {if (defaultInstance == null) {defaultInstance = new EventBus();}}}return defaultInstance;}// 創建一個新的EventBus實例;每個實例都是一個單獨的作用域,事件在其中傳遞。// 要使用中央總線,請考慮{@link#getDefault()}public EventBus() {// 繼續調用 EventBus 的另一個有參構造函數,傳入默認的 EventBusBuilder 來完成它相關屬性的初始化this(DEFAULT_BUILDER);}EventBus(EventBusBuilder builder) {subscriptionsByEventType = new HashMap<>();typesBySubscriber = new HashMap<>();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);logSubscriberExceptions = builder.logSubscriberExceptions;logNoSubscriberMessages = builder.logNoSubscriberMessages;sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;sendNoSubscriberEvent = builder.sendNoSubscriberEvent;throwSubscriberException = builder.throwSubscriberException;eventInheritance = builder.eventInheritance;executorService = builder.executorService;}
}

如果 defaultInstancenull,則新建一個 EventBus 實例對象賦值給 defaultInstance。最終通過調用 EventBus 的另一個有參構造函數,傳入默認的 EventBusBuilder 來完成其相關屬性的初始化。

可以通過 Builder 模式來支持用 EventBusBuilderEventBus 進行一些屬性的配置,例如用如下方式注冊事件:

EventBus.builder().eventInheritance(false).logSubscriberExceptions(false).build().register(this);

2.2 EventBus 注冊

public class EventBus {private final SubscriberMethodFinder subscriberMethodFinder;// 注冊訂閱者 subscriber 以接收事件,訂閱者一旦對接收事件不再感興趣,就必須調用{@link #unregister(Object)}public void register(Object subscriber) {// 獲取當前要注冊類的 Class 對象 Class<?> subscriberClass = subscriber.getClass();// 根據 Class 查找當前類中訂閱了事件的方法集合,即使用了 Subscribe 注解、有 public 修飾符、一個參數的方法 // SubscriberMethod類主要封裝了符合條件方法的相關信息:Method對象、線程模式、事件類型、優先級、是否是粘性事等 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);synchronized (this) {// 循環遍歷訂閱了事件的方法集合,以完成注冊for (SubscriberMethod subscriberMethod : subscriberMethods) {subscribe(subscriber, subscriberMethod);}}}
}

EventBus#register() 方法的執行流程如下:

  • 通過 SubscriberMethodFinder#findSubscriberMethods() 方法,根據當前要注冊類的 Class 對象查找當前類中訂閱了事件的方法集合 List,即找到使用了 Subscribe 注解、有 public 修飾符、一個參數的方法,其中 SubscriberMethod 類主要封裝了符合條件方法的相關信息:Method 對象、線程模式、事件類型、優先級、是否是粘性事等;
  • 循環遍歷訂閱了事件的方法集合,通過 EventBus#subscribe() 方法完成注冊。
2.2.1 SubscriberMethodFinder#findSubscriberMethods()
class SubscriberMethodFinder {// METHOD_CACHE是一個ConcurrentHashMap,保存了subscriberClass和對應SubscriberMethod的集合,以提高注冊效率,避免重復查找private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {// 獲取 subscriberClass 類對應的 SubscriberMethod 的集合List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);if (subscriberMethods != null) { // 獲取到且不為 null 則直接返回找到的 SubscriberMethod 的集合return subscriberMethods;}// ignoreGenereatedIndex 是用于標記是否忽略由 Builder 傳入的 SubscriberInfoIndex// 由于使用了默認的 EventBusBuilder,則 ignoreGeneratedIndex 屬性默認為 falseif (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;}}
}

SubscriberMethodFinder#findSubscriberMethods() 方法的執行流程如下:

  • 先從緩存集合 Map<Class<?>, List> METHOD_CACHE 中查找、獲取 subscriberClass 類對應的 SubscriberMethod 的集合,如果找到則直接返回;
  • 如果查找不到,則根據條件判斷進行下一步的查找過程,由于使用了默認的 EventBusBuilder,因此 ignoreGeneratedIndex 屬性默認為 false,即忽略注解生成器,所以調用 SubscriberMethodFinder#findUsingInfo() 方法進行查找,最后將查找到的訂閱事件的方法集合緩存到 METHOD_CACHE 中。
2.2.1.1 SubscriberMethodFinder#findUsingInfo()
class SubscriberMethodFinder {private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {// 通過 SubscriberMethodFinder#prepareFindState() 方法從 FindState 池中獲取到非空的 FindState 并返回FindState findState = prepareFindState();findState.initForSubscriber(subscriberClass); // 初始化 FindState// 初始狀態下 findState.clazz 就是 subscriberClass while (findState.clazz != null) {// 由于在 FindState.initForSubscriber() 方法初始化時 subscriberInfo 賦值為 null// 且沒有通過 EventBusBuilder 向 List<SubscriberInfoIndex> subscriberInfoIndexes 集合中添加 SubscriberInfoIndexfindState.subscriberInfo = getSubscriberInfo(findState);if (findState.subscriberInfo != null) { // findState.subscriberInfo 為 nullSubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();for (SubscriberMethod subscriberMethod : array) {if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {findState.subscriberMethods.add(subscriberMethod);}}} else { // 通過反射查找訂閱事件的方法 findUsingReflectionInSingleClass(findState);}// 修改 findState.clazz 為 subscriberClass 的父類 Class,即需要遍歷父類findState.moveToSuperclass();}// 查找到的方法保存在了 FindState 實例的 subscriberMethods 集合中// 使用 FindState.subscriberMethods 構建一個新的 List<SubscriberMethod>,然后釋放掉 FindStatereturn getMethodsAndRelease(findState);}
}

通過注釋可知在 SubscriberMethodFinder#findUsingInfo() 方法會在當前要注冊的類以及其父類中查找訂閱事件的方法,FindState 類是 SubscriberMethodFinder 的內部類,用來輔助查找訂閱事件的方法,通過條件判斷可知,接下來將通過 SubscriberMethodFinder#findUsingReflectionInSingleClass() 方法通過反射查找訂閱事件的方法。

SubscriberMethodFinder 類中維護了一個 FindState 池,是一個默認大小為 4 的數組,通過 SubscriberMethodFinder#prepareFindState() 方法遍歷該數組找到非 nullFindState 進行返回。而在 SubscriberMethodFinder#getMethodsAndRelease(findState) 方法中則是將搜尋的結果取出后,對 FindState 進行 recycle,之后再將其放回 FindState 池中。

2.2.1.2 SubscriberMethodFinder#findUsingReflectionInSingleClass()
class SubscriberMethodFinder {private void findUsingReflectionInSingleClass(FindState findState) {Method[] methods;try {// 獲取訂閱事件的 Method 列表,使用 getDeclaredMethods() 方法其實是比 getMethods() 方法的效率更高的// 尤其是對于較復雜龐大的類,如 Activity 類methods = findState.clazz.getDeclaredMethods();} catch (Throwable th) {// 但有時會導致 NoClassDefFoundError,此時采取備用方案,使用 getMethods() 進行獲取methods = findState.clazz.getMethods();findState.skipSuperClasses = true;}// 循環遍歷當前注冊類的 methods 數組,篩選出符合條件的:public、non-static、non-abstract 的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) { // 檢查其參數個數是否符合 1 的要求Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);if (subscribeAnnotation != null) { // 如果當前方法使用了 Subscribe 注解 Class<?> eventType = parameterTypes[0]; // 得到該參數的類型// FindState.checkAdd()方法用來判斷 FindState 的 anyMethodByEventType map 是否// 已經添加過以當前 eventType 為 key 的鍵值對,沒添加過則返回 true if (findState.checkAdd(method, eventType)) {// 得到 Subscribe 注解的 threadMode 屬性值,即線程模式ThreadMode threadMode = subscribeAnnotation.threadMode();// 創建一個 SubscriberMethod 對象,并添加到 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");}}}
}

SubscriberMethodFinder#findUsingReflectionInSingleClass() 方法的執行流程如下:

  1. 獲取訂閱事件的 Method 列表,注意:使用 getDeclaredMethods() 方法其實是比 getMethods() 方法的效率更高的,但有時會導致 NoClassDefFoundError,此時采取備用方案,再使用 getMethods() 進行獲取;
  2. 循環遍歷當前注冊類的 methods 數組,篩選出符合條件:publicnon-staticnon-abstract 的,然后獲取符合條件的方法的所有參數類型,如果參數個數符合 1 的要求且使用了 Subscribe 注解,則通過 FindState.checkAdd() 方法來判斷 FindState 的 Map<Class, Object> anyMethodByEventType 集合中是否已經添加過以當前參數的類型 eventTypekey 的鍵值對,如沒添加過則返回 true,隨后創建一個 SubscriberMethod 對象,并添加到 FindState 的 List<SubscriberMethod> subscriberMethods 集合中。
2.2.1.3 SubscriberMethodFinder#FindState#checkAdd()
class SubscriberMethodFinder {static class FindState {final Map<Class, Object> anyMethodByEventType = new HashMap<>();boolean checkAdd(Method method, Class<?> eventType) {// 2級檢查:第一級通過事件類型(較快速),第二級檢查需具有完整簽名。通常,訂閱者不會監聽相同事件類型的方法Object existing = anyMethodByEventType.put(eventType, method);if (existing == null) { // existing 為 null,說明之前的集合,沒有當前要加入的訂閱方法return true; // 直接返回 true} else {if (existing instanceof Method) {if (!checkAddWithMethodSignature((Method) existing, eventType)) {// Paranoia checkthrow new IllegalStateException();}// Put any non-Method object to "consume" the existing MethodanyMethodByEventType.put(eventType, this);}return checkAddWithMethodSignature(method, eventType);}}}
}

FindState#checkAdd() 方法中,將訂閱事件的方法 Method 以方法的參數類型為 key 保存到 HashMap<Class, Object> anyMethodByEventType 集合中;同時還會調用 FindState#checkAddWithMethodSignature() 方法將方法以方法的簽名(形式為:方法名>Event 類型名)為 key 保存到 HashMap<String, Class> subscriberClassByMethodKey 集合中。

2.2.2 EventBus#subscribe()

在 EventBus#register() 方法中通過 SubscriberMethodFinder#findSubscriberMethods() 方法,查找到當前要注冊類及其父類中訂閱了事件的方法集合 List subscriberMethods,隨后循環遍歷該方法集合,再通過 EventBus#subscribe() 方法完成注冊:

public class EventBus {// 保存以訂閱方法的參數類型 eventType 為 key,Subscription 對象集合為 value 的鍵值對的集合 HashMapprivate final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;// 保存以當前要注冊類的對象為 key,注冊類中訂閱事件的方法的參數類型的集合為 value 的鍵值對的集合 HashMapprivate final Map<Object, List<Class<?>>> typesBySubscriber;// 必須在同步塊中調用,EventBus 為方法的訂閱過程進行了加鎖,保證了線程安全private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {Class<?> eventType = subscriberMethod.eventType; // 得到當前訂閱了事件的方法的參數類型// Subscription 類保存了要注冊的類對象以及當前訂閱的 subscriberMethod Subscription newSubscription = new Subscription(subscriber, subscriberMethod);// 查找集合 subscriptionsByEventType 中是否存在以當前 eventType 為 key 的值 CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);if (subscriptions == null) { // 如果不存在,則創建一個 subscriptions 集合,并以當前訂閱方法的參數類型為 key 保存到 subscriptionsByEventTypesubscriptions = new CopyOnWriteArrayList<>();subscriptionsByEventType.put(eventType, subscriptions);} else {if (subscriptions.contains(newSubscription)) {throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType);}}// 將新創建的 newSubscription 對象按照優先級 priority 的順序添加到 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,并以當前要注冊類的對象為 key 保存到 typesBySubscribersubscribedEvents = 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);}}}
}

EventBus#subscribe() 方法中,新建 Subscription 實例對象保存要注冊的類對象以及當前類中訂閱的 subscriberMethod,將新建的 Subscription 實例對象保存到 Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType 集合中,同時將當前訂閱事件方法的參數類型添加到 Map<Object, List<Class<?>>> typesBySubscriber 集合中。流程至此,EventBus 注冊的核心流程的源碼已經分析完。

3.EventBus 取消注冊

在這里插入圖片描述
結合 EventBus 取消注冊流程圖,再來分析源碼,首先 EventBus 取消注冊的方式如下:

EventBus.getDefault().unregister(this);

3.1 EventBus#unregister()

public class EventBus {// 保存以當前要注冊類的對象為 key,注冊類中訂閱事件的方法的參數類型的集合為 value 的鍵值對的集合 HashMapprivate final Map<Object, List<Class<?>>> typesBySubscriber;// 取消給定訂閱者所注冊訂閱的所有事件public synchronized void unregister(Object subscriber) {// 獲取當前注冊類對象對應的訂閱事件方法的參數類型的集合 List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);if (subscribedTypes != null) {// 遍歷訂閱事件方法的參數類型集合,釋放之前保存的當前注冊類中的 Subscriptionfor (Class<?> eventType : subscribedTypes) {unsubscribeByEventType(subscriber, eventType);}// 集合中刪除以 subscriber 為 key 的鍵值對typesBySubscriber.remove(subscriber);} else {Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());}}
}

EventBus#unregister() 方法,首先獲取當前注冊類對象對應的訂閱事件方法的參數類型的集合,隨后遍歷訂閱事件方法的參數類型集合,調用 EventBus#unsubscribeByEventType() 方法釋放之前保存的當前注冊類中的 Subscription。最后從 Map<Object, List<Class<?>>> typesBySubscriber 集合中刪除以當前 subscriberkey 的鍵值對。

3.2 EventBus#unsubscribeByEventType()

public class EventBus {// 保存以訂閱方法的參數類型 eventType 為 key,Subscription 對象集合為 value 的鍵值對的集合 HashMapprivate final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;// 只更新 subscriptionsByEventType 集合,不更新 typebysubscriber 集合,調用者必須更新 typebysubscriber 集合private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {// 獲取當前訂閱方法的參數類型所對應的 Subscription 集合 List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);if (subscriptions != null) {int size = subscriptions.size();for (int i = 0; i < size; i++) { // 遍歷 Subscription 集合 Subscription subscription = subscriptions.get(i);// 如果當前 subscription 對象對應的注冊類對象和要取消注冊的注冊類對象相同// 則從 Subscription 集合中刪除當前 subscription 對象 if (subscription.subscriber == subscriber) {subscription.active = false;subscriptions.remove(i);i--;size--;}}}}
}

EventBus#unsubscribeByEventType() 方法中,獲取當前訂閱方法的參數類型所對應的 Subscription 集合,遍歷 Subscription 集合,如果當前 subscription 對象所對應的注冊類對象和要取消注冊的注冊類對象相同,則從 Subscription 集合中刪除。流程至此,EventBus 取消注冊的源碼已經分析完。

4.EventBus 發布、處理事件

EventBus 發布事件的方式如下:

EventBus.getDefault().post(new EventMessage(1, "事件 1"));

4.1 EventBus#post()

public class EventBus {// currentPostingThreadState 是一個 PostingThreadState 類型的 ThreadLocal // PostingThreadState 類保存了事件隊列和線程模式等信息 private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {@Overrideprotected PostingThreadState initialValue() {return new PostingThreadState();}};    // 將給定的事件 event 發布到事件總線 EventBuspublic void post(Object event) {// 獲取 ThreadLocal 中保存的 PostingThreadState 實例對象PostingThreadState postingState = currentPostingThreadState.get();List<Object> eventQueue = postingState.eventQueue;eventQueue.add(event); // 將要發送的事件添加到事件隊列if (!postingState.isPosting) { // isPosting 默認為 false postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper(); // 是否為主線程postingState.isPosting = true; // isPosting 置為 true,使得事件 post 的過程中 當前線程的其他 post 事件無法被響應if (postingState.canceled) {throw new EventBusException("Internal error. Abort state was not reset");}try {while (!eventQueue.isEmpty()) { // 遍歷事件隊列 // EventBus.postSingleEvent() 發送單個事件 // eventQueue.remove(0),從事件隊列移除事件 postSingleEvent(eventQueue.remove(0), postingState);}} finally {postingState.isPosting = false; // 當 post 過程結束后,再將 isPosting 置為 falsepostingState.isMainThread = false;}}}
}

EventBus#post() 方法,獲取 ThreadLocal 中保存的 PostingThreadState 實例對象,將要發送的事件添加到事件隊列 PostingThreadState.eventQueue 中,隨后遍歷事件隊列,調用 EventBus#postSingleEvent() 方法發送單個事件。

4.2 EventBus#postSingleEvent()

public class EventBus { private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {Class<?> eventClass = event.getClass();boolean subscriptionFound = false;if (eventInheritance) { // eventInheritance默認為true,表示是否向上查找事件的父類// 查找當前事件類型 Class 及其父類、接口等保存到集合 Map<Class<?>, List<Class<?>>> eventTypesCachList<Class<?>> eventTypes = lookupAllEventTypes(eventClass);int countTypes = eventTypes.size();for (int h = 0; h < countTypes; h++) { // 遍歷 eventTypesCach 集合,繼續處理事件 Class<?> clazz = eventTypes.get(h);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) {post(new NoSubscriberEvent(this, event));}}}
}

EventBus#postSingleEvent() 方法中,根據 eventInheritance 屬性,決定是否向上遍歷事件的父類型,將獲取到的當前事件類型 Class 及其父類、接口等保存到 Map<Class<?>, List<Class<?>>> eventTypesCach 集合中,然后遍歷剛獲取的集合,對集合中的每一個 Class 調用 EventBus#postSingleEventForEventType() 方法進一步處理。

4.3 EventBus#postSingleEventForEventType()

public class EventBus { // 保存以訂閱方法的參數類型 eventType 為 key,Subscription 對象集合為 value 的鍵值對的集合 HashMapprivate final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {CopyOnWriteArrayList<Subscription> subscriptions;synchronized (this) {// 獲取事件類型對應的 Subscription 集合subscriptions = subscriptionsByEventType.get(eventClass);}// 如果已有訂閱者訂閱了對應類型的事件if (subscriptions != null && !subscriptions.isEmpty()) {for (Subscription subscription : subscriptions) {postingState.event = event; // PostingThreadState 記錄事件postingState.subscription = subscription; // PostingThreadState 記錄對應的 subscriptionboolean aborted = false;try {// EventBus.postToSubscription() 方法對事件進行處理 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;}
}

EventBus#postSingleEventForEventType() 方法中,首先同步加鎖獲取事件類型對應的 Subscription 集合,如果獲得的集合不為 null,表示已有訂閱者訂閱了對應類型的事件,則遍歷 Subscription 集合,為每一個 Subscription 調用 EventBus#postToSubscription() 方法來處理事件。

4.4 EventBus#postToSubscription()

public class EventBus { private final HandlerPoster mainThreadPoster;private final BackgroundPoster backgroundPoster;private final AsyncPoster asyncPoster;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 {// 如果是在子線程發送事件,則將事件入隊列,通過 Handler 切換到主線程執行處理事件 // mainThreadPoster 不為空mainThreadPoster.enqueue(subscription, event);}break;case MAIN_ORDERED: // 無論在那個線程發送事件,都先將事件入隊列,然后通過 Handler 切換到主線程,依次處理事件if (mainThreadPoster != null) { // mainThreadPoster 不為空,將事件入隊列,然后通過 Handler 切換到主線程,依次處理事件mainThreadPoster.enqueue(subscription, event); } else { // 否者直接在主線程通過反射處理事件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);}}
}

EventBus#postToSubscription() 方法,根據訂閱事件方法的線程模式、以及發送事件的線程來判斷如何處理事件,處理方式主要有兩種:

  1. 在相應線程直接通過 EventBus#invokeSubscriber() 方法,通過反射來執行訂閱事件的方法,此時發送出去的事件就被訂閱者接收并做相應處理;
  2. 通過不同的 Poster 將事件入隊,然后采用隊列的方式做進一步處理。
4.4.1 EventBus#invokeSubscriber()
public class EventBus { 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#invokeSubscriber() 方法中,由 Subscription 實例保存的事件訂閱方法,通過反射來執行訂閱者的事件訂閱方法,此時發布的事件就被訂閱者接收并做相應處理。

4.4.2 HandlerPoster#enqueue()
final class HandlerPoster extends Handler {private final PendingPostQueue queue;private final int maxMillisInsideHandleMessage;private final EventBus eventBus;private boolean handlerActive;HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {super(looper);this.eventBus = eventBus;this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;queue = new PendingPostQueue();}void enqueue(Subscription subscription, Object event) {// 通過 PendingPost.obtainPendingPost() 方法從 pendingPostPool 緩存中獲取 PendingPost 并賦值// 如果緩存中沒有,則由 subscription 和 event 新建一個 PendingPost 對象PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);synchronized (this) {queue.enqueue(pendingPost); // 通過 PendingPostQueue.enqueue() 入隊if (!handlerActive) {handlerActive = true;// 發送開始處理事件的消息,handleMessage() 方法將被執行,完成從子線程到主線程的切換if (!sendMessage(obtainMessage())) {throw new EventBusException("Could not send handler message");}}}}@Overridepublic void handleMessage(Message msg) {boolean rescheduled = false;try {long started = SystemClock.uptimeMillis();while (true) { // 死循環遍歷隊列PendingPost pendingPost = queue.poll(); // 出隊獲取 PendingPostif (pendingPost == null) {synchronized (this) {// 再檢查一次,這次是同步的pendingPost = queue.poll();if (pendingPost == null) {handlerActive = false; // 經過兩次獲取仍然為 null 則直接返回并置 handlerActive 為 falsereturn;}}}// 通過 EventBus.invokeSubscriber() 方法進一步處理 pendingPosteventBus.invokeSubscriber(pendingPost);long timeInMethod = SystemClock.uptimeMillis() - started;if (timeInMethod >= maxMillisInsideHandleMessage) {if (!sendMessage(obtainMessage())) {throw new EventBusException("Could not send handler message");}rescheduled = true;return;}}} finally {handlerActive = rescheduled;}}
}

HandlerPoster#enqueue() 方法,首先通過 PendingPost.obtainPendingPost() 方法從 pendingPostPool 緩存中獲取 PendingPost 并賦值傳入的 subscriptionevent 對象,如果緩存中獲取不到,則由 subscriptionevent 新建一個 PendingPost 對象,并將 PendingPost 添加到 PendingPostQueue 隊列中,隨后通過 Handler 切換到主線程,在 Handler#handleMessage() 方法中將 PendingPost 對象循環出隊列,交給 EventBus#invokeSubscriber() 方法進行處理。

4.4.3 EventBus#invokeSubscriber()
public class EventBus {void invokeSubscriber(PendingPost pendingPost) {Object event = pendingPost.event;Subscription subscription = pendingPost.subscription;PendingPost.releasePendingPost(pendingPost); // 釋放 PendingPost 引用的資源if (subscription.active) {// 最終,通過反射來執行訂閱事件的方法invokeSubscriber(subscription, event);}}
}

EventBus#invokeSubscriber() 方法,主要就是從 PendingPost 中取出之前保存的 eventsubscription,然后通過反射來執行訂閱事件的方法,又回到了第一種處理方式。所以 HandlerPoster#enqueue(subscription, event) 方法的核心就是先將將事件入隊列,然后通過 Handler 從子線程切換到主線程中去處理事件。

4.4.4 BackgroundPoster#enqueue()
final class BackgroundPoster implements Runnable {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.obtainPendingPost() 方法從 pendingPostPool 緩存中獲取 PendingPost 并賦值// 如果緩存中沒有,則由 subscription 和 event 新建一個 PendingPost 對象PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);synchronized (this) {queue.enqueue(pendingPost); // 通過 PendingPostQueue.enqueue() 入隊if (!executorRunning) {executorRunning = true;// 通過線程池來執行當前 BackgroundPoster 的 run() 方法來進一步處理事件eventBus.getExecutorService().execute(this);}}}@Overridepublic void run() {try {try {while (true) { // 死循環遍歷隊列PendingPost pendingPost = queue.poll(1000); // 出隊獲取 PendingPostif (pendingPost == null) {synchronized (this) {// 再檢查一次,這次是同步的pendingPost = queue.poll();if (pendingPost == null) {// 經過兩次獲取仍然為 null 則直接返回并置 handlerActive 為 falseexecutorRunning = false;return;}}}// 通過 EventBus.invokeSubscriber() 方法進一步處理 pendingPosteventBus.invokeSubscriber(pendingPost);}} catch (InterruptedException e) {Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);}} finally {executorRunning = false;}}
}

BackgroundPoster#enqueue() 方法跟 HandlerPoster#enqueue() 方法的功能差不多,核心也是先將事件加入到 PendingPostQueue 隊列,然后再出隊列。不同之處是 BackgroundPoster 是通過線程池來執行其 run() 方法,最后交給 EventBus#invokeSubscriber() 方法進行處理。

4.4.5 AsyncPoster#enqueue()
class AsyncPoster implements Runnable {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.obtainPendingPost() 方法從 pendingPostPool 緩存中獲取 PendingPost 并賦值// 如果緩存中沒有,則由 subscription 和 event 新建一個 PendingPost 對象PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);queue.enqueue(pendingPost); // 通過 PendingPostQueue.enqueue() 入隊// 通過線程池來執行當前 AsyncPoster 的 run() 方法來進一步處理事件eventBus.getExecutorService().execute(this);}@Overridepublic void run() {PendingPost pendingPost = queue.poll();if(pendingPost == null) {throw new IllegalStateException("No pending post available");}// 通過 EventBus.invokeSubscriber() 方法進一步處理 pendingPosteventBus.invokeSubscriber(pendingPost);}
}

AsyncPoster#enqueue() 方法跟 HandlerPoster#enqueue() 方法差不多,核心也是先將事件加入到 PendingPostQueue 隊列,然后再出隊列,隨后通過線程池來執行其 run() 方法,最后交給 EventBus#invokeSubscriber() 方法進行處理。流程至此,EventBus 發布事件與處理事件的源碼已經分析完。

EventBus 發布事件(包括粘性事件)及處理流程

EventBus 發布事件及處理流程

5.EventBus 粘性事件

一般情況,使用 EventBus 都是準備好訂閱事件的方法,然后注冊事件,最后在發布事件,即要先有事件的接收者(訂閱者)。但粘性事件卻恰恰相反,可以先發布事件,后續再準備訂閱事件的方法、以及注冊事件。

發布粘性事件通過如下方式:

EventBus.getDefault().postSticky(new EventMessage(2, "粘性事件"));

5.1 EventBus#postSticky()

public class EventBus {// 發布粘性事件時,保存事件的類型和對應事件本身private final Map<Class<?>, Object> stickyEvents;// 將給定的事件發布到 EventBus 并保持該事件(因為它具有粘性)。// 相同事件類型、最近的粘性事件保存在內存中,供訂閱者使用 Subscribe#sticky() 訪問public void postSticky(Object event) {synchronized (stickyEvents) {// 將發布的事件和事件類型保存到 stickyEventsstickyEvents.put(event.getClass(), event);}// 保存后再調用 EventBus#post(event) 方法,以防訂閱者想立即刪除post(event);}
}

EventBus#postSticky() 方法執行流程如下:

  1. 將事件類型和對應事件保存到 Map<Class<?>, Object> stickyEvents 集合中,等待后續使用;
  2. 通過 EventBus#post(event) 方法繼續發布事件。因此,如果在發布粘性事件前,已經有了對應類型事件的訂閱者,即使它是非粘性的,依然可以接收到發布的粘性事件。

通過 EventBus#post(event) 方法發布粘性事件,流程在前面已經分析過,在前面分析 EventBus#subscribe() 方法時,關于粘性事件的處理過程還沒分析,下面一起來剖析一下這段代碼。

5.2 EventBus#subscribe()

public class EventBus {// 發布粘性事件時,保存事件的類型和對應事件本身private final Map<Class<?>, Object> stickyEvents;private final boolean eventInheritance; // 默認為 true,表示是否向上查找事件的父類// 必須在同步塊中調用,EventBus 為方法的訂閱過程進行了加鎖,保證了線程安全private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {......// 如果當前訂閱事件方法的 Subscribe 注解的 sticky 屬性為 true,即該方法可接受粘性事件if (subscriberMethod.sticky) {if (eventInheritance) { // 默認為 true,表示向上查找事件的父類// 須考慮 eventType 所有子類的現有粘性事件。注意:對于粘性事件較多的情況,遍歷所有事件效率不高// 因此要更改數據結構使得查找更加有效(如:存儲父類的子類集合:Class -> List<Class>)Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();for (Map.Entry<Class<?>, Object> entry : entries) {Class<?> candidateEventType = entry.getKey();// 如果 candidateEventType 是 eventType 的子類if (eventType.isAssignableFrom(candidateEventType)) {Object stickyEvent = entry.getValue(); // 獲得對應的事件// 通過 EventBus.checkPostStickyEventToSubscription() 方法處理粘性事件checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}} else {Object stickyEvent = stickyEvents.get(eventType);// 通過 EventBus.checkPostStickyEventToSubscription() 方法處理粘性事件checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}}
}

EventBus#subscribe() 方法中,如果當前訂閱事件方法的 Subscribe 注解的 sticky 屬性為 true,即該方法可接受粘性事件。遍歷 EventBus#postSticky() 方法保存到 Map<Class<?>, Object> stickyEvents 集合中的粘性事件,如果 stickyEvents 中取出事件的事件類型與當前訂閱方法接收的事件類型相同或者是其子類,則取出 stickyEvents 中對應事件類型的具體事件,然后通過 EventBus.checkPostStickyEventToSubscription() 方法處理粘性事件。

5.3 EventBus#checkPostStickyEventToSubscription()

public class EventBus {// 發布粘性事件時,保存事件的類型和對應事件本身private final Map<Class<?>, Object> stickyEvents;private final boolean eventInheritance; // 默認為 true,表示是否向上查找事件的父類private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {if (stickyEvent != null) {// 如果訂閱者試圖中止當前事件,則將失敗(在發布狀態下事件無法追蹤)postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());}}
}

EventBus#checkPostStickyEventToSubscription() 方法,在判空操作通過后,通過 EventBus#postToSubscription() 方法完成粘性事件的處理,處理流程前面已經分析過,不再重復分析。流程至此,EventBus 粘性事件的發布與處理流程已經分析完。

6.EventBus 之 Subscriber Index

通過上面幾節的分析可知,EventBus 在項目運行時默認是通過反射來查找訂閱事件的方法信息,如果項目中存在大量的訂閱事件的方法,通過反射必然會對項目運行時的性能產生影響。EventBus 也考慮到了這個問題,因此除了默認的反射之外,還提供了在項目編譯時通過注解處理器(APT 全稱:Annotation Processing Tool)查找訂閱事件方法信息的方式,在編譯期生成一個輔助的索引類 Subscriber Index 來保存這些信息。

要在項目編譯時查找訂閱事件的方法信息,首先要在 app 的 build.gradle 中加入如下配置:

android {defaultConfig {javaCompileOptions {annotationProcessorOptions {// 根據項目實際情況,指定輔助索引類的名稱和包名arguments = [ eventBusIndex : 'com.xxx.xxx.EventBusIndex' ]}}}
}
dependencies {compile 'org.greenrobot:eventbus:3.2.0'// 引入注解處理器annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.2.0'
}

然后在項目的 Application 中添加如下配置,以生成一個默認的 EventBus 單例:

public class AndroidApplication extends Application implements Application.ActivityLifecycleCallbacks {@Overridepublic void onCreate() {super.onCreate();registerActivityLifecycleCallbacks(this);EventBus.builder().addIndex(new EventBusIndex()).installDefaultEventBus();}
}

用法還是和上面的例子一樣,只是在項目編譯時會生成對應的 EventBusIndex 類(這里是在項目的底層 baselibrary 進行配置的,進行了封裝,如果是簡單使用,放到 app 模塊即可):
在這里插入圖片描述

6.1 EventBusIndex

// 這個類是由 EventBus 生成的,不要編輯
public class EventBusIndex implements SubscriberInfoIndex {// 保存當前注冊類的 Class 類型和其中事件訂閱方法的信息private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;static {SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();putIndex(new SimpleSubscriberInfo(com.xxx.xxx.baselibrary.mvp.view.BaseMVPActivity.class, true,new SubscriberMethodInfo[] {new SubscriberMethodInfo("onEvent", java.util.Map.class, ThreadMode.MAIN),}));}private static void putIndex(SubscriberInfo info) {SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);}@Overridepublic SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);if (info != null) {return info;} else {return null;}}
}

EventBusIndex 是由 EventBus 生成的,不要編輯。其內部首先在靜態代碼塊內新建 HashMap 保存當前注冊類的 Class 類型和其中事件訂閱方法的信息,然后通過 EventBusIndex#putIndex() 方法將新建的 SimpleSubscriberInfo 實例對象添加到新建的 HashMap 中保存。

接下來通過源碼剖析使用 Subscriber IndexEventBus 的注冊流程,由前面的使用代碼可知,期首先創建一個 EventBusBuilder 實例對象,然后通過其 addIndex() 方法添加索引類的實例:

6.2 EventBusBuilder#addIndex()

public class EventBusBuilder {List<SubscriberInfoIndex> subscriberInfoIndexes;/** Adds an index generated by EventBus' annotation preprocessor. */// 添加一個由 EventBus 注解處理器生成的索引 SubscriberInfoIndexpublic EventBusBuilder addIndex(SubscriberInfoIndex index) {if(subscriberInfoIndexes == null) {subscriberInfoIndexes = new ArrayList<>();}subscriberInfoIndexes.add(index);return this;}
}

EventBusBuilder#addIndex() 方法把生成的索引類的實例保存在 List<SubscriberInfoIndex> subscriberInfoIndexes 集合中。

6.2 EventBusBuilder#installDefaultEventBus()

public class EventBusBuilder {/*** 安裝由 EventBus#getDefault() 方法返回的默認 EventBus(使用 EventBusBuilder 的值)* 必須在第一次使用默認 EventBus 之前完成一次** 如果已經有一個默認的 EventBus 實例,拋出 EventBusException*/public EventBus installDefaultEventBus() {synchronized (EventBus.class) {if (EventBus.defaultInstance != null) {throw new EventBusException("Default instance already exists." +" It may be only set once before it's used the first time to ensure consistent behavior.");}EventBus.defaultInstance = build();return EventBus.defaultInstance;}}// 基于當前配置構建 EventBus 實例對象public EventBus build() {// this 代表當前 EventBusBuilder 對象return new EventBus(this);}
}

EventBusBuilder#installDefaultEventBus() 方法就是使用當前 EventBusBuilder 實例對象構建一個 EventBus 實例,然后賦值給 EventBusdefaultInstance 成員變量,這樣通過 EventBusBuilder 配置的 Subscriber Index 也就傳遞到了 EventBus 實例中。

6.3 SubscriberMethodFinder#findUsingInfo()

由于配置使用了 Subscriber Index 索引類,回頭再看 SubscriberMethodFinder#findUsingInfo() 方法,執行流程將會有所不同:

class SubscriberMethodFinder {private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {// 通過 SubscriberMethodFinder#prepareFindState() 方法從 FindState 池中獲取到非空的 FindState 并返回FindState findState = prepareFindState();findState.initForSubscriber(subscriberClass); // 初始化 FindState// 初始狀態下 findState.clazz 就是 subscriberClass while (findState.clazz != null) {// 雖然 FindState.initForSubscriber() 方法初始化時 subscriberInfo 賦值為 null// 但此時通過 EventBusBuilder 向 List<SubscriberInfoIndex> subscriberInfoIndexes 集合中添加了// SubscriberInfoIndex 的實現類 EventBusIndex 實例對象,因此,getSubscriberInfo() 方法將返回// EventBusIndex#getSubscriberInfo() 方法的返回值,這里將返回 SimpleSubscriberInfo 實例對象// 該實例對象是在 EventBusIndex 初始化時創建的,將返回的該實例對象賦值給 FindState.subscriberInfo findState.subscriberInfo = getSubscriberInfo(findState);if (findState.subscriberInfo != null) { // findState.subscriberInfo 此時不為 null// 通過 SimpleSubscriberInfo.getSubscriberMethods() 方法獲得當前注冊類中所有訂閱了事件的方法SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();for (SubscriberMethod subscriberMethod : array) {// FindState.checkAdd()方法用來判斷 FindState 的 anyMethodByEventType map 是否// 已經添加過以當前 eventType 為 key 的鍵值對,沒添加過則返回 true if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {// 將 SubscriberMethod 對象,添加到 subscriberMethods 集合 findState.subscriberMethods.add(subscriberMethod);}}} else { // 通過反射查找訂閱事件的方法 findUsingReflectionInSingleClass(findState);}// 修改 findState.clazz 為 subscriberClass 的父類 Class,即需要遍歷父類findState.moveToSuperclass();}// 查找到的方法保存在了 FindState 實例的 subscriberMethods 集合中// 使用 FindState.subscriberMethods 構建一個新的 List<SubscriberMethod>,然后釋放掉 FindStatereturn getMethodsAndRelease(findState);}
}

SubscriberMethodFinder#findUsingInfo() 方法中,由于配置使用了 Subscriber Index,此時 SubscriberMethodFinder#getSubscriberInfo() 方法的返回值不再為 null,具體來看一下該方法的執行流程。

6.3.1 SubscriberMethodFinder#getSubscriberInfo()
class SubscriberMethodFinder {private SubscriberInfo getSubscriberInfo(FindState findState) {// FindState.initForSubscriber() 方法初始化時 subscriberInfo 賦值為 null,因此該條件不成立if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();if (findState.clazz == superclassInfo.getSubscriberClass()) {return superclassInfo;}}// 通過 EventBusBuilder 向 List<SubscriberInfoIndex> subscriberInfoIndexes 集合中添加了// SubscriberInfoIndex 的實現類 EventBusIndex 實例對象if (subscriberInfoIndexes != null) { // subscriberInfoIndexes 不為 nullfor (SubscriberInfoIndex index : subscriberInfoIndexes) { // 遍歷索引類實例集合// 根據注冊類的 Class 類查找 SubscriberInfoSubscriberInfo info = index.getSubscriberInfo(findState.clazz);if (info != null) {return info;}}}return null;}
}

SubscriberMethodFinder#getSubscriberInfo() 方法,遍歷 List<SubscriberInfoIndex> subscriberInfoIndexes 集合根據注冊類的 Class 類查找 SubscriberInfo,此時將返回 EventBusIndex 初始化時創建的 SimpleSubscriberInfo 實例對象。

6.3.2 SimpleSubscriberInfo#getSubscriberMethods()
public class SimpleSubscriberInfo extends AbstractSubscriberInfo {@Overridepublic synchronized SubscriberMethod[] getSubscriberMethods() {int length = methodInfos.length;SubscriberMethod[] methods = new SubscriberMethod[length];for (int i = 0; i < length; i++) {SubscriberMethodInfo info = methodInfos[i];methods[i] = createSubscriberMethod(info.methodName, info.eventType, info.threadMode,info.priority, info.sticky);}return methods;}
}

SimpleSubscriberInfo#getSubscriberMethods() 方法的主要作用是獲取當前注冊類中所有訂閱了事件的方法。

流程至此,EventBus 使用 Subscriber Index 的主要源碼分析完畢,其它的和之前的注冊訂閱流程一樣。

小結

Subscriber Index 的核心就是項目編譯時使用注解處理器生成保存事件訂閱方法信息的索引類,然后項目運行時將索引類實例設置到 EventBus 中,這樣當注冊 EventBus 時,從索引類取出當前注冊類對應的事件訂閱方法信息,以完成最終的注冊,避免了運行時反射處理的過程,所以在性能上會有質的提高。項目中可以根據實際的需求決定是否使用 Subscriber Index


總結

  • EventBus 底層采用的是注解和反射的方式來獲取訂閱方法信息;
  • 整個 EventBus 可以看出,事件是被觀察者,訂閱者類是觀察者,當事件出現或者發布變更的時候,會通過 EventBus 通知觀察者,使得觀察者的訂閱方法能夠被自動調用;

注意:項目中根據實際需求決定是否使用,如果濫用的話,各種邏輯交叉在一起,也可能會給后期的維護帶來困難。

參考

greenrobot/EventBus:github.com/greenrobot/EventBus

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/45512.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/45512.shtml
英文地址,請注明出處:http://en.pswp.cn/web/45512.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

夢想CAD在線預覽編輯功能

1.最近有個需求&#xff0c;在web系統里進行在線進行CAD預覽和編輯&#xff0c;這里用的是夢想CAD實現此功能&#xff0c;夢想CAD官網文檔 2.CAD預覽&#xff0c;需要需要對CAD文件格式進行轉化&#xff0c;將dwg文件格式轉化為mxweb格式&#xff0c;再進行調用夢想CAD里的打開…

ipynb轉換為pdf、Markdown(.md)

Jupyter Notebook 文件&#xff08;.ipynb&#xff09;可以轉換成多種數據格式&#xff0c;以適應不同的使用場景和需求。以下是幾種常見的轉換格式及其簡潔描述&#xff1a; HTML: Jupyter Notebook可以直接導出為靜態的網頁&#xff08;HTML&#xff09;格式&#xff0c;這樣…

記一次IP數據處理過程,文本(CSV文件)處理,IP解析

個人博客&#xff1a;無奈何楊&#xff08;wnhyang&#xff09; 個人語雀&#xff1a;wnhyang 共享語雀&#xff1a;在線知識共享 Github&#xff1a;wnhyang - Overview 起因 突然接收到XX給的任務&#xff0c;要將一批IP數據處理一下&#xff0c;將IP對應的省市區解析出來…

PHP基礎語法

PHP 腳本在服務器上執行&#xff0c;然后將純 HTML 結果發送回瀏覽器。 基本的 PHP 語法 PHP 腳本可以放在文檔中的任何位置。 PHP 腳本以 <?php 開始&#xff0c;以 ?> 結束&#xff1a; <?php // PHP 代碼 ?> PHP 文件的默認文件擴展名是 .php。 PHP 文…

PHP智云物業管理平臺微信小程序系統源碼

?&#x1f3e0;智云物業管理新紀元&#xff01;微信小程序&#xff0c;讓家園管理更智慧&#x1f4f1; &#x1f3e1;【開篇&#xff1a;智慧生活&#xff0c;從物業開始】&#x1f3e1; 在快節奏的現代生活中&#xff0c;我們追求的不僅僅是家的溫馨&#xff0c;更是生活的…

基于hive數據庫的泰坦尼克號幸存者數據分析

進入 ./beeline -u jdbc:hive2://node2:10000 -n root -p 查詢 SHOW TABLES; 刪除 DROP TABLE IF EXISTS tidanic; 上傳數據 hdfs dfs -put train.csv /user/hive/warehouse/mytrain.db/tidanic 《泰坦尼克號幸存者數據分析》 1、原始數據介紹 泰坦尼克號是當時世界上…

達夢數據庫系列—28. 主備集群高可用測試

目錄 監視器關閉 監視器啟動&#xff0c;Detach備庫 主備正常&#xff0c;手動switchover 主庫故障&#xff0c;自動switchover 主庫故障&#xff0c;手動Takeover 主庫故障&#xff0c;備庫強制takeover 主庫重啟 備庫故障 公網連接異常 主庫私網異常 備庫私網異常…

實現給Nginx的指定網站開啟basic認證——http基本認證

一、問題描述 目前我們配置的網站內容都是沒有限制&#xff0c;可以讓任何人打開瀏覽器都能夠訪問&#xff0c;這樣就會存在一個問題&#xff08;可能會存在一些惡意訪問的用戶進行惡意操作&#xff0c;直接訪問到我們的敏感后臺路徑進行操作&#xff0c;風險就會很大&#xff…

云原生周刊:Score 成為 CNCF 沙箱項目|2024.7.15

開源項目 Trident Trident 是由 NetApp 維護的全面支持的開源項目。它從頭開始設計&#xff0c;旨在通過行業標準接口&#xff08;如容器存儲接口 CSI&#xff09;幫助您滿足容器化應用程序對持久性存儲的需求。 Monokle Monokle 通過提供用于編寫 YAML 清單、驗證策略和管…

淺談微服務

技術方法論&#xff1a;向微服務邁進&#xff1a; 理論&#xff1a;“軟件研發中任何一項技術、方法、架構都不可能是銀彈"—Fred Brooks 哪些場景適合用微服務&#xff0c;呢些不適用&#xff1f;&#xff08;微服務存在哪些理解誤區、應用前提&#xff09; 一些被驗證過…

Why can‘t I access GPT-4 models via API, although GPT-3.5 models work?

題意&#xff1a;為什么我無法通過API訪問GPT-4模型&#xff0c;盡管GPT-3.5模型可以工作&#xff1f; 問題背景&#xff1a; Im able to use the gpt-3.5-turbo-0301 model to access the ChatGPT API, but not any of the gpt-4 models. Here is the code I am using to tes…

【雷豐陽-谷粒商城 】【分布式高級篇-微服務架構篇】【22】【RabbitMQ】

持續學習&持續更新中… 守破離 【雷豐陽-谷粒商城 】【分布式高級篇-微服務架構篇】【22】【RabbitMQ】 Message Queue 消息隊列異步處理應用解耦流量控制 消息中間件概念RabbitMQ概念MessagePublisherExchangeQueueBindingConnectionChannelConsumerVirtual HostBroker圖…

Django prefetch_related()方法

prefetch_related的作用 prefetch_related()是 Django ORM 中用于優化查詢性能的另一個重要方法&#xff0c;尤其在處理多對多&#xff08;ManyToMany&#xff09;關系和反向關系時非常有用。它允許你預加載相關對象&#xff0c;從而減少數據庫查詢次數。 1&#xff0c;創建應…

【香橙派】Orange pi AIpro開發板使用之一鍵部署springboot項目

前言 最近有幸收到一份新款 OrangePi AIpro 開發板&#xff0c;之前手里也搗鼓過一些板子&#xff0c;這次嘗試從零開始部署一個簡單的后端服務。OrangePi AIpro 采用昇騰AI技術路線&#xff0c;具體為4核64位處理器AI處理器&#xff0c;可配16GB內存容量&#xff0c;各種復雜應…

數字化賦能,加油小程序讓出行更便捷高效

在快節奏的現代生活中&#xff0c;每一次加油不僅是車輛續航的必要步驟&#xff0c;也成為了人們日常生活中不可或缺的一環。隨著科技的飛速發展&#xff0c;傳統加油模式正逐步向智能化、便捷化轉型&#xff0c;其中&#xff0c;加油小程序作為這股浪潮中的佼佼者&#xff0c;…

el-date-picker手動輸入日期,通過設置開始時間和階段自動填寫結束時間

需求&#xff1a;根據開始時間&#xff0c;通過填寫階段時長&#xff0c;自動填寫結束時間&#xff0c;同時開始時間和節數時間可以手動輸入 代碼如下&#xff1a; <el-form ref"ruleForm2" :rules"rules2" :model"formData" inline label-po…

B樹與B+樹的區別

B樹和B樹都是用于數據庫和文件系統的平衡樹數據結構&#xff0c;但它們有一些顯著的區別&#xff1a; 節點結構&#xff1a; B樹&#xff1a;每個節點存儲數據和指向子節點的指針。葉子節點也包含數據。 B樹&#xff1a;內部節點只存儲索引值&#xff0c;不存儲實際數據。所有…

yolov5 上手

0 介紹 YOLO(You Only Look Once)是一種流行的物體檢測和圖像分割模型&#xff0c;由華盛頓大學的約瑟夫-雷德蒙&#xff08;Joseph Redmon&#xff09;和阿里-法哈迪&#xff08;Ali Farhadi&#xff09;開發。YOLO 于 2015 年推出&#xff0c;因其高速度和高精確度而迅速受到…

人工智能算法工程師(中級)課程13-神經網絡的優化與設計之梯度問題及優化與代碼詳解

大家好&#xff0c;我是微學AI&#xff0c;今天給大家介紹一下人工智能算法工程師(中級)課程13-神經網絡的優化與設計之梯度問題及優化與代碼詳解。 文章目錄 一、引言二、梯度問題1. 梯度爆炸梯度爆炸的概念梯度爆炸的原因梯度爆炸的解決方案 2. 梯度消失梯度消失的概念梯度…

vue2中父組件向子組件傳值不更新視圖問題解決

1. 由于父組件更新了props里面的值, 但是子組件第一次接收后再修改沒有監聽到. 父組件修改值的時候使用this$set解決問題. 在 Vue 2 中&#xff0c;this.$set 通常用于更新數組中的特定元素。如果你想更新整個數組&#xff0c;可以直接賦值一個新的數組&#xff0c;或者你可以…