EventBus 源碼解析

1. 功能介紹

1.1 EventBus

EventBus 是一個 Android 事件發佈/訂閱框架,通過解耦發佈者和訂閱者簡化 Android 事件傳遞,這裏的事件可以理解爲消息,本文中統一稱爲事件。事件傳遞既可用於 Android 四大組件間通訊,也可以用戶異步線程和主線程間通訊等等。
傳統的事件傳遞方式包括:Handler、BroadCastReceiver、Interface 回調,相比之下 EventBus 的優點是代碼簡潔,使用簡單,並將事件發佈和訂閱充分解耦。

1.2 概念

事件(Event):又可稱爲消息,本文中統一用事件表示。其實就是一個對象,可以是網絡請求返回的字符串,也可以是某個開關狀態等等。事件類型(EventType)指事件所屬的 Class。
事件分爲一般事件和 Sticky 事件,相對於一般事件,Sticky 事件不同之處在於,當事件發佈後,再有訂閱者開始訂閱該類型事件,依然能收到該類型事件最近一個 Sticky 事件。

訂閱者(Subscriber):訂閱某種事件類型的對象。當有發佈者發佈這類事件後,EventBus 會執行訂閱者的 onEvent 函數,這個函數叫事件響應函數。訂閱者通過 register 接口訂閱某個事件類型,unregister 接口退訂。訂閱者存在優先級,優先級高的訂閱者可以取消事件繼續向優先級低的訂閱者分發,默認所有訂閱者優先級都爲 0。

發佈者(Publisher):發佈某事件的對象,通過 post 接口發佈事件。

2. 總體設計

本項目較爲簡單,總體設計請參考3.1 訂閱者、發佈者、EventBus 關係圖4.1 類關係圖

3. 流程圖

3.1 訂閱者、發佈者、EventBus 關係圖

eventbus img
EventBus 負責存儲訂閱者、事件相關信息,訂閱者和發佈者都只和 EventBus 關聯。

3.2 事件響應流程

eventbus img
訂閱者首先調用 EventBus 的 register 接口訂閱某種類型的事件,當發佈者通過 post 接口發佈該類型的事件時,EventBus 執行調用者的事件響應函數。

4. 詳細設計

4.1 類關係圖

eventbus img
以上是 EventBus 主要類的關係圖,從中我們也可以看出大部分類都與 EventBus 直接關聯。上部分主要是訂閱者相關信息,中間是 EventBus 類,下面是發佈者發佈事件後的調用。具體類的功能請看下面的詳細介紹。

4.2 類詳細介紹

4.2.1 EventBus.java

EventBus 類負責所有對外暴露的 API,其中的 register()、post()、unregister() 函數配合上自定義的 EventType 及事件響應函數即可完成核心功能,見 3.2 圖。
EventBus 默認可通過靜態函數 getDefault 獲取單例,當然有需要也可以通過 EventBusBuilder 或 構造函數新建一個 EventBus,每個新建的 EventBus 發佈和訂閱事件都是相互隔離的,即一個 EventBus 對象中的發佈者發佈事件,另一個 EventBus 對象中的訂閱者不會收到該訂閱。
EventBus 中對外 API,主要包括兩類:
(1) register 和 unregister
分別表示訂閱事件和取消訂閱。register 最底層函數有三個參數,分別爲訂閱者對象、是否是 Sticky 事件、優先級。

private synchronized void register(Object subscriber, boolean sticky, int priority)

PS:在此之前的版本 EventBus 還允許自定義事件響應函數名稱,這版本中此功能已經被去除。
register 函數流程圖如下: eventbus img
register 函數中會先根據訂閱者類名去subscriberMethodFinder中查找當前訂閱者所有事件響應函數,然後循環每一個事件響應函數,依次執行下面的 subscribe 函數:

(2) subscribe
subscribe 函數分三步
第一步:通過subscriptionsByEventType得到該事件類型所有訂閱者信息隊列,根據優先級將當前訂閱者信息插入到訂閱者隊列subscriptionsByEventType中;
第二步:在typesBySubscriber中得到當前訂閱者訂閱的所有事件隊列,將此事件保存到隊列typesBySubscriber中,用於後續取消訂閱;
第三步:檢查這個事件是否是 Sticky 事件,如果是則從stickyEvents事件保存隊列中取出該事件類型最後一個事件發送給當前訂閱者。

(3) post、cancel 、removeStickyEvent
post 函數用於發佈事件,cancel 函數用於取消某訂閱者訂閱的所有事件類型、removeStickyEvent 函數用於刪除 sticky 事件。
post 函數流程圖如下: eventbus img
post 函數會首先得到當前線程的 post 信息PostingThreadState,其中包含事件隊列,將當前事件添加到其事件隊列中,然後循環調用 postSingleEvent 函數發佈隊列中的每個事件。

postSingleEvent 函數會先去eventTypesCache得到該事件對應類型的的父類及接口類型,沒有緩存則查找並插入緩存。循環得到的每個類型和接口,調用 postSingleEventForEventType 函數發佈每個事件到每個訂閱者。

postSingleEventForEventType 函數在subscriptionsByEventType查找該事件訂閱者訂閱者隊列,調用 postToSubscription 函數向每個訂閱者發佈事件。

postToSubscription 函數中會判斷訂閱者的 ThreadMode,從而決定在什麼 Mode 下執行事件響應函數。ThreadMode 共有四類:

  1. PostThread:默認的 ThreadMode,表示在執行 Post 操作的線程直接調用訂閱者的事件響應方法,不論該線程是否爲主線程(UI 線程)。當該線程爲主線程時,響應方法中不能有耗時操作,否則有卡主線程的風險。適用場景:對於是否在主線程執行無要求,但若 Post 線程爲主線程,不能耗時的操作
  2. MainThread:在主線程中執行響應方法。如果發佈線程就是主線程,則直接調用訂閱者的事件響應方法,否則通過主線程的 Handler 發送消息在主線程中處理——調用訂閱者的事件響應函數。顯然,MainThread類的方法也不能有耗時操作,以避免卡主線程。適用場景:必須在主線程執行的操作
  3. BackgroundThread:在後臺線程中執行響應方法。如果發佈線程不是主線程,則直接調用訂閱者的事件響應函數,否則啓動唯一的後臺線程去處理。由於後臺線程是唯一的,當事件超過一個的時候,它們會被放在隊列中依次執行,因此該類響應方法雖然沒有PostThread類和MainThread類方法對性能敏感,但最好不要有重度耗時的操作或太頻繁的輕度耗時操作,以造成其他操作等待。適用場景:操作輕微耗時且不會過於頻繁,即一般的耗時操作都可以放在這裏;
  4. Async:不論發佈線程是否爲主線程,都使用一個空閒線程來處理。和BackgroundThread不同的是,Async類的所有線程是相互獨立的,因此不會出現卡線程的問題。適用場景:長耗時操作,例如網絡訪問

(4) 主要成員變量含義
1.defaultInstance默認的 EventBus 實例,根據EventBus.getDefault()函數得到。
2.DEFAULT_BUILDER默認的 EventBus Builder。
3.eventTypesCache事件對應類型及其父類和實現的接口的緩存,以 eventType 爲 key,元素爲 Object 的 ArrayList 爲 Value,Object 對象爲 eventType 的父類或接口。 4.subscriptionsByEventType事件訂閱者的保存隊列,以 eventType 爲 key,元素爲Subscription的 ArrayList 爲 Value,其中Subscription爲訂閱者信息,由 subscriber, subscriberMethod, priority 構成。
5.typesBySubscriber訂閱者訂閱的事件的保存隊列,以 subscriber 爲 key,元素爲 eventType 的 ArrayList 爲 Value。
6.stickyEventsSticky 事件保存隊列,以 eventType 爲 key,event 爲元素,由此可以看出對於同一個 eventType 最多隻會有一個 event 存在。
7.currentPostingThreadState當前線程的 post 信息,包括事件隊列、是否正在分發中、是否在主線程、訂閱者信息、事件實例、是否取消。
8.mainThreadPosterbackgroundPosterasyncPoster事件主線程處理者、事件 Background 處理者、事件異步處理者。
9.subscriberMethodFinder訂閱者響應函數信息存儲和查找類。
10.executorService異步和 BackGround 處理方式的線程池。
11.throwSubscriberException當調用事件處理函數異常時是否拋出異常,默認爲 false,建議通過

EventBus.builder().throwSubscriberException(true).installDefaultEventBus()

打開。
12.logSubscriberExceptions當調用事件處理函數異常時是否打印異常信息,默認爲 true。
13.logNoSubscriberMessages當沒有訂閱者訂閱該事件時是否打印日誌,默認爲 true。
14.sendSubscriberExceptionEvent當調用事件處理函數異常時是否發送 SubscriberExceptionEvent 事件,若此開關打開,訂閱者可通過

public void onEvent(SubscriberExceptionEvent event)

訂閱該事件進行處理,默認爲 true。
15.sendNoSubscriberEvent當沒有事件處理函數對事件處理時是否發送 NoSubscriberEvent 事件,若此開關打開,訂閱者可通過

public void onEvent(NoSubscriberEvent event)

訂閱該事件進行處理,默認爲 true。
16.eventInheritance是否支持事件繼承,默認爲 true。

4.2.2 EventBusBuilder.java

跟一般 Builder 類似,用於在需要設置參數過多時構造 EventBus。包含的屬性也是 EventBus 的一些設置參數,意義見4.2.1 EventBus.java的介紹,build 函數用於新建 EventBus 對象,installDefaultEventBus 函數將當前設置應用於 Default EventBus。

4.2.3 SubscriberMethodFinder.java

訂閱者響應函數信息存儲和查找類,由 HashMap 緩存,以 ${subscriberClassName} 爲 key,SubscriberMethod 對象爲元素的 ArrayList 爲 value。findSubscriberMethods 函數用於查找訂閱者響應函數,如果不在緩存中,則遍歷自己的每個函數並遞歸父類查找,查找成功後保存到緩存中。遍歷及查找規則爲:
a. 遍歷 subscriberClass 每個方法;
b. 該方法不以java.javax.android.這些 SDK 函數開頭,並以onEvent開頭,表示可能是事件響應函數繼續,否則檢查下一個方法;
c. 該方法是否是 public 的,並且不是 ABSTRACT、STATIC、BRIDGE、SYNTHETIC 修飾的,滿足條件則繼續。其中 BRIDGE、SYNTHETIC 爲編譯器生成的一些函數修飾符;
d. 該方法是否只有 1 個參數,滿足條件則繼續;
e. 該方法名爲 onEvent 則 threadMode 爲ThreadMode.PostThread
該方法名爲 onEventMainThread 則 threadMode 爲ThreadMode.MainThread
該方法名爲 onEventBackgroundThread 則 threadMode 爲ThreadMode.BackgroundThread
該方法名爲 onEventAsync 則 threadMode 爲ThreadMode.Async
其他情況且不在忽略名單 (skipMethodVerificationForClasses) 中則拋出異常。
f. 得到該方法唯一的參數即事件類型 eventType,將這個方法、threadMode、eventType 一起構造 SubscriberMethod 對象放到 ArrayList 中。
g. 回到 b 遍歷 subscriberClass 的下一個方法,若方法遍歷結束到 h;
h. 回到 a 遍歷自己的父類,若父類遍歷結束回到 i;
i. 若 ArrayList 依然爲空則拋出異常,否則會將 ArrayList 做爲 value,${subscriberClassName} 做爲 key 放到緩存 HashMap 中。 對於事件函數的查找有兩個小的性能優化點:

a. 第一次查找後保存到了緩存中,即上面介紹的 HashMap  
b. 遇到 java. javax. android. 開頭的類會自動停止查找

類中的 skipMethodVerificationForClasses 屬性表示跳過哪些類中非法以 onEvent 開頭的函數檢查,若不跳過則會拋出異常。
PS:在此之前的版本 EventBus 允許自定義事件響應函數名稱,緩存的 HashMap key 爲 ${subscriberClassName}.${eventMethodName},這版本中此功能已經被去除。

4.2.4 SubscriberMethod.java

訂閱者事件響應函數信息,包括響應方法、線程 Mode、事件類型以及一個用來比較 SubscriberMethod 是否相等的特徵值 methodString 共四個變量,其中 methodString 爲 ${methodClassName}#${methodName}(${eventTypeClassName}。

4.2.5 Subscription.java

訂閱者信息,包括 subscriber 對象、事件響應方法 SubscriberMethod、優先級 priority。

4.2.6 HandlerPoster.jva

事件主線程處理,對應ThreadMode.MainThread。繼承自 Handler,enqueue 函數將事件放到隊列中,並利用 handler 發送 message,handleMessage 函數從隊列中取事件,invoke 事件響應函數處理。

4.2.7 AsyncPoster.java

事件異步線程處理,對應ThreadMode.Async,繼承自 Runnable。enqueue 函數將事件放到隊列中,並調用線程池執行當前任務,在 run 函數從隊列中取事件,invoke 事件響應函數處理。

4.2.8 BackgroundPoster.java

事件 Background 處理,對應ThreadMode.BackgroundThread,繼承自 Runnable。enqueue 函數將事件放到隊列中,並調用線程池執行當前任務,在 run 函數從隊列中取事件,invoke 事件響應函數處理。與 AsyncPoster.java 不同的是,BackgroundPoster 中的任務只在同一個線程中依次執行,而不是併發執行。

4.2.9 PendingPost.java

訂閱者和事件信息實體類,並含有同一隊列中指向下一個對象的指針。通過緩存存儲不用的對象,減少下次創建的性能消耗。

4.2.10 PendingPostQueue.java

通過 head 和 tail 指針維護一個PendingPost隊列。HandlerPoster、AsyncPoster、BackgroundPoster 都包含一個此隊列實例,表示各自的訂閱者及事件信息隊列,在事件到來時進入隊列,處理時從隊列中取出一個元素進行處理。

4.2.11 SubscriberExceptionEvent.java

當調用事件處理函數異常時發送的 EventBus 內部自定義事件,通過 post 發送,訂閱者可自行訂閱這類事件進行處理。

4.2.12 NoSubscriberEvent.java

當沒有事件處理函數對事件處理時發送的 EventBus 內部自定義事件,通過 post 發送,訂閱者可自行訂閱這類事件進行處理。

4.2.13 EventBusException.java

封裝於 RuntimeException 之上的 Exception,只是覆蓋構造函數,相當於一個標記,標記是屬於 EventBus 的 Exception。

4.2.14 ThreadMode.java

線程 Mode 枚舉類,表示事件響應函數執行線程信息,包括ThreadMode.PostThreadThreadMode.MainThreadThreadMode.BackgroundThreadThreadMode.Async四種。

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