標籤:
轉載:http://p.codekk.com/blogs/detail/54cfab086c4761e5001b25381. 功能介紹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 負責儲存訂閱者、事件相關資訊,訂閱者和發行者都只和 EventBus 關聯。
3.2 事件響應流程
訂閱者首先調用 EventBus 的 register 介面訂閱某種類型的事件,當發行者通過 post 介面發布該類型的事件時,EventBus 執行調用者的事件響應函數。
4. 詳細設計4.1 類別關係圖
以上是 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 函數流程圖如下:
register 函數中會先根據訂閱者類名去subscriberMethodFinder中尋找當前訂閱者所有事件響應函數,然後迴圈每一個事件響應函數,依次執行下面的 subscribe 函數:
(2) subscribe
subscribe 函數分三步
第一步:通過subscriptionsByEventType得到該事件類型所有訂閱者資訊隊列,根據優先順序將當前訂閱者資訊插入到訂閱者隊列subscriptionsByEventType中;
第二步:在typesBySubscriber中得到當前訂閱者訂閱的所有事件隊列,將此事件儲存到隊列typesBySubscriber中,用於後續取消訂閱;
第三步:檢查這個事件是否是 Sticky 事件,如果是則從stickyEvents事件儲存隊列中取出該事件類型最後一個事件發送給當前訂閱者。
(3) post、cancel 、removeStickyEvent
post 函數用於發布事件,cancel 函數用於取消某訂閱者訂閱的所有事件類型、removeStickyEvent 函數用於刪除 sticky 事件。
post 函數流程圖如下:
post 函數會首先得到當前線程的 post 資訊PostingThreadState,其中包含事件隊列,將當前事件添加到其事件隊列中,然後迴圈調用 postSingleEvent 函數發布隊列中的每個事件。
postSingleEvent 函數會先去eventTypesCache得到該事件對應類型的的父類及介面類型,沒有緩衝則尋找並插入緩衝。迴圈得到的每個類型和介面,調用 postSingleEventForEventType 函數發布每個事件到每個訂閱者。
postSingleEventForEventType 函數在subscriptionsByEventType尋找該事件訂閱者訂閱者隊列,調用 postToSubscription 函數向每個訂閱者發布事件。
postToSubscription 函數中會判斷訂閱者的 ThreadMode,從而決定在什麼 Mode 下執行事件響應函數。ThreadMode 共有四類:
PostThread:預設的 ThreadMode,表示在執行 Post 操作的線程直接調用訂閱者的事件回應程式法,不論該線程是否為主線程(UI 線程)。當該線程為主線程時,回應程式法中不能有耗時操作,否則有卡主線程的風險。適用情境:對於是否在主線程執行無要求,但若 Post 線程為主線程,不能耗時的操作;
MainThread:在主線程中執行回應程式法。如果發布線程就是主線程,則直接調用訂閱者的事件回應程式法,否則通過主線程的 Handler 發送訊息在主線程中處理——調用訂閱者的事件響應函數。顯然,MainThread類的方法也不能有耗時操作,以避免卡主線程。適用情境:必須在主線程執行的操作;
BackgroundThread:在後台線程中執行回應程式法。如果發布線程不是主線程,則直接調用訂閱者的事件響應函數,否則啟動唯一的後台線程去處理。由於後台線程是唯一的,當事件超過一個的時候,它們會被放在隊列中依次執行,因此該類回應程式法雖然沒有PostThread類和MainThread類方法對效能敏感,但最好不要有重度耗時的操作或太頻繁的輕度耗時操作,以造成其他動作等待。適用情境:操作輕微耗時且不會過於頻繁,即一般的耗時操作都可以放在這裡;
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.mainThreadPoster、backgroundPoster、asyncPoster事件主線程處理者、事件 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.PostThread、ThreadMode.MainThread、ThreadMode.BackgroundThread、ThreadMode.Async四種。
5. 與 Otto 對比
等 Otto 分析完成
76.Android之EventBus源碼解析