EventBus 是什么?
EventBus 是一個基于發布/訂閱(Publish/Subscribe) 模式的開源庫(主要由 greenrobot 開發維護)。它的核心目的是簡化 Android 應用中不同組件(如 Activity, Fragment, Service, Thread 等)之間的通信。它通過一個中央事件總線(Central Event Bus)來傳遞事件(Event
對象),允許組件訂閱它們關心的事件類型,并在事件發生時自動接收通知。這顯著降低了組件間的耦合度。
核心概念
- 事件 (
Event
): 一個普通的 Java 對象(POJO),代表需要傳遞的消息或通知。事件本身不包含邏輯,只是數據的載體。可以是任何類,比如MessageEvent
,DataUpdateEvent
,UserLoggedInEvent
等。 - 發布者 (
Publisher
): 任何需要通知其他組件發生了某事的對象。它創建一個事件對象并通過 EventBus 實例post(event)
發布該事件到總線上。 - 訂閱者 (
Subscriber
): 對特定類型事件感興趣的對象。它包含一個或多個用@Subscribe
注解標記的方法(稱為事件處理方法)。這些方法定義了當特定事件被發布時應該執行的邏輯。 - 事件總線 (
EventBus
): 單例(通常通過EventBus.getDefault()
獲取)或自定義實例。它負責:- 維護所有訂閱者及其感興趣的事件類型的注冊表。
- 接收發布者發送的事件。
- 根據事件類型查找所有匹配的訂閱者。
- 在正確的線程(根據訂閱方法指定的 ThreadMode)上調用訂閱者的事件處理方法。
一、 使用方法 (非常詳細)
1. 添加依賴 (以 Gradle 為例)
在 app 模塊的 build.gradle
文件中添加最新版本的 EventBus 依賴(請查看 Maven Central 獲取最新版本):
dependencies {implementation 'org.greenrobot:eventbus:3.3.1' // 檢查最新版本
}
2. 定義事件 (Event)
創建一個簡單的 Java 類來表示你想要傳遞的數據或通知。
public class MessageEvent {public final String message;public MessageEvent(String message) {this.message = message;}
}
3. 準備訂閱者 (Subscriber)
在需要接收事件的組件(如 Activity、Fragment、Service 或任何普通對象)中:
- 注冊/注銷: 組件必須在開始接收事件前向 EventBus 注冊自己,并在不再需要接收事件(或生命周期結束時)注銷自己,以避免內存泄漏和無效調用。
- 注冊: 通常在
onStart()
或onResume()
中進行。 - 注銷: 通常在
onStop()
或onPause()
中進行(選擇與注冊對稱的生命周期方法)。對于 Fragment,onCreateView
/onDestroyView
也是常見選擇。
- 注冊: 通常在
public class MyActivity extends AppCompatActivity {@Overrideprotected void onStart() {super.onStart();EventBus.getDefault().register(this); // 注冊當前 Activity 作為訂閱者}@Overrideprotected void onStop() {super.onStop();EventBus.getDefault().unregister(this); // 注銷當前 Activity}// ... 其他代碼 ...
}
- 聲明事件處理方法: 使用
@Subscribe
注解標記一個公共方法(方法名任意)。該方法的參數類型決定了它訂閱哪種事件。注解可以指定threadMode
和sticky
屬性。threadMode
(必選): 指定事件處理方法在哪個線程執行。這是 EventBus 的核心優勢之一。ThreadMode.POSTING
(默認): 在事件發布所在的線程調用。最快,避免線程切換開銷。小心:如果發布者在主線程發布,你在這里不能執行耗時操作;如果發布者在后臺線程發布,你在這里不能更新UI。ThreadMode.MAIN
: 在 Android 的主線程 (UI 線程) 調用。安全更新 UI。如果事件是在主線程發布的,方法會立即執行;否則,事件會被放入主線程隊列等待處理。ThreadMode.MAIN_ORDERED
: 類似于MAIN
,但事件在隊列中有序執行(MAIN
可能在某些情況下插隊)。通常使用MAIN
即可。ThreadMode.BACKGROUND
: 在后臺線程調用。如果事件是在非主線程發布的,就在該線程直接調用;如果是在主線程發布的,EventBus 會使用一個單一線程的后臺線程池來調用該方法。適合執行輕量級后臺操作。ThreadMode.ASYNC
: 在獨立的、非主線程、非發布者線程調用。EventBus 使用一個線程池來調用這些方法。適合執行耗時操作(如網絡請求、數據庫大查詢),不會阻塞發布線程或主線程。
sticky
(可選,默認false
): 是否處理粘性事件 (Sticky Event)。如果設為true
,那么即使事件是在該訂閱者注冊之前發布的,只要該粘性事件還在總線上,訂閱者注冊后會立即收到該事件的最新一次發布。非常適用于需要獲取“最新狀態”的場景(如當前登錄用戶信息、網絡狀態)。
// 示例1:在主線程處理 MessageEvent
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {// 安全更新UItextView.setText(event.message);
}// 示例2:在后臺線程處理 DataLoadedEvent
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void handleDataLoaded(DataLoadedEvent event) {// 處理數據,比如保存到數據庫(輕量級操作)saveDataToDatabase(event.data);
}// 示例3:處理粘性事件 UserLoggedInEvent
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onUserLoggedIn(UserLoggedInEvent event) {// 更新UI顯示當前登錄用戶信息updateUserProfile(event.user);
}
4. 發布事件 (Publisher)
在任何需要通知其他組件的地方(任何類中),獲取 EventBus 實例并調用 post(Object event)
方法。event
對象就是你要傳遞的事件實例。
// 在某個按鈕點擊事件或網絡請求回調中
public void someMethodThatTriggersEvent() {// 創建事件對象MessageEvent event = new MessageEvent("Hello EventBus!");// 發布事件到總線EventBus.getDefault().post(event);
}// 發布粘性事件 (會一直保留在總線上直到被覆蓋或手動移除)
public void postStickyEvent() {UserLoggedInEvent event = new UserLoggedInEvent(currentUser);EventBus.getDefault().postSticky(event);
}
5. 處理粘性事件 (Sticky Events) 的額外操作
- 獲取最新粘性事件: 使用
EventBus.getStickyEvent(Class<T> eventType)
可以在訂閱者注冊前或任何地方手動獲取特定類型的最新粘性事件。 - 移除粘性事件: 使用
EventBus.removeStickyEvent(T event)
或EventBus.removeStickyEvent(Class<T> eventType)
手動移除粘性事件。
// 在注冊訂閱者之前,檢查是否有最新的登錄信息
UserLoggedInEvent stickyEvent = EventBus.getDefault().getStickyEvent(UserLoggedInEvent.class);
if (stickyEvent != null) {// 立即使用最新用戶信息更新UI或狀態updateUserProfile(stickyEvent.user);
}
6. 事件繼承
EventBus 支持事件繼承。如果一個訂閱者訂閱了父類事件類型(如 BaseEvent
),那么當發布者發布任何該父類的子類事件(如 SpecificEvent extends BaseEvent
)時,該訂閱者也會接收到通知。這可以用于創建更通用的事件處理器。
7. 優先級 (priority
)
在 @Subscribe
注解中可以設置 priority
屬性(整數,默認 0)。數值越大,優先級越高。優先級高的訂閱者方法會在優先級低的之前接收到事件。如果優先級相同,順序不確定。注意: 只有在同一個 ThreadMode 下,優先級才生效。優先級通常用于攔截或修改事件(高優先級訂閱者可以調用 EventBus.cancelEventDelivery(event)
來阻止事件繼續傳遞給低優先級訂閱者)。
@Subscribe(threadMode = ThreadMode.MAIN, priority = 1)
public void onHighPriorityEvent(MyEvent event) {// 高優先級處理if (shouldCancel(event)) {EventBus.getDefault().cancelEventDelivery(event); // 取消事件傳遞}
}
8. 混淆配置 (Proguard/R8)
如果使用代碼混淆,需要在 Proguard 規則文件 (proguard-rules.pro
) 中添加以下配置,以確保 @Subscribe
注解方法在運行時能被正確找到(如果使用索引加速則可能不需要,但加上更保險):
-keepattributes *Annotation*
-keepclassmembers class * {@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# 如果使用了索引加速 (EventBusAnnotationProcessor)
-keep class org.greenrobot.eventbus.** { *; }
9. 索引加速 (EventBusAnnotationProcessor) - 推薦
EventBus 3 引入了索引加速,在編譯時通過 APT (Annotation Processing Tool) 生成一個索引類,列出所有 @Subscribe
方法及其信息(事件類型、線程模式、優先級等)。這避免了在 App 首次運行時使用反射掃描所有類查找訂閱方法,大大提高了注冊速度和啟動性能,并減少了運行時方法查找的開銷。
啟用步驟:
-
添加注解處理器依賴:
dependencies {implementation 'org.greenrobot:eventbus:3.3.1'annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.3.1' // 與 eventbus 版本一致 }
-
配置索引選項 (可選但推薦): 在 app 模塊的
build.gradle
中指定生成的索引類名:android {defaultConfig {javaCompileOptions {annotationProcessorOptions {arguments = [ eventBusIndex : 'com.yourpackage.MyEventBusIndex' ]}}} }
-
在 Application 中設置索引: 在自定義
Application
類的onCreate()
中配置 EventBus 使用生成的索引:public class MyApp extends Application {@Overridepublic void onCreate() {super.onCreate();// 使用索引構建 EventBus 實例 (替代默認反射查找)EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();// 現在 EventBus.getDefault() 已經使用了索引} }
或者在需要使用 EventBus 的地方手動創建帶索引的實例:
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
二、 應用場景
EventBus 特別適合解決以下通信難題:
- Fragment 間通信: 兩個 Fragment 之間沒有直接引用。Fragment A 發布事件,Fragment B 訂閱并處理。比通過 Activity 中轉或接口回調更簡潔。
- Activity 與 Service 通信: Service 后臺完成任務(如下載完成、播放狀態改變)后發布事件,Activity 訂閱并更新UI。
- 后臺線程與 UI 線程通信: 在后臺線程(如網絡請求、數據庫操作)中完成任務后發布事件,訂閱者指定
ThreadMode.MAIN
安全更新UI。 - 跨層通信: 深度嵌套的組件(如 Adapter 中的 ViewHolder)需要通知頂層的 Activity 或 Fragment 執行某些操作(如打開新界面)。
- 廣播全局狀態變化: 用戶登錄/登出、網絡連接狀態變化、語言切換、主題更改等全局狀態變更。使用粘性事件尤其方便,新啟動的組件能立即獲取最新狀態。
- 替代部分 Intent/BroadcastReceiver: 對于應用內部通信,EventBus 比系統廣播更輕量、更快速、類型更安全(避免 Intent 的 key 字符串硬編碼)。
- 解耦業務邏輯與 UI 更新: 業務邏輯模塊(如 Presenter, ViewModel, Interactor)處理完邏輯后發布事件,UI 層(Activity/Fragment)訂閱事件并只負責展示,實現更好的關注點分離。
- 組件化/模塊化通信: 不同模塊之間通過定義和發布/訂閱公共事件接口進行通信,減少模塊間的直接依賴。
三、 底層原理 (深入解析)
EventBus 的核心在于高效地管理訂閱關系和在正確線程上派發事件。以下是其核心機制:
-
訂閱者注冊 (
register(Object subscriber)
):- 方法查找 (運行時 / 編譯時):
- 運行時 (默認,無索引): 使用反射遍歷
subscriber
對象的所有方法,查找所有被@Subscribe
注解標記的公共方法。提取方法參數類型(即訂閱的事件類型)、ThreadMode、優先級、是否粘性等信息。 - 編譯時 (使用索引): 編譯期間
EventBusAnnotationProcessor
掃描所有類,找出所有@Subscribe
方法,生成一個索引類(如MyEventBusIndex
)。注冊時直接從這個索引類中查找subscriber
的類名對應的所有訂閱方法信息,避免了耗時的運行時反射掃描。
- 運行時 (默認,無索引): 使用反射遍歷
- 構建訂閱關系映射: EventBus 內部維護一個核心數據結構:
Map<Class<?>, CopyOnWriteArrayList<Subscription>>
。- Key (
Class<?>
): 事件類型(event.getClass()
)。 - Value (
CopyOnWriteArrayList<Subscription>
): 一個線程安全的列表,存儲了所有訂閱了該事件類型的Subscription
對象。 Subscription
對象封裝了:訂閱者對象 (subscriber
)、訂閱者方法 (SubscriberMethod
- 包含方法對象、ThreadMode、優先級、是否粘性)。
- Key (
- 對于找到的每個訂閱方法,根據其訂閱的事件類型,將封裝好的
Subscription
對象添加到上述映射表中對應事件類型的列表中。列表根據Subscription
的優先級(priority
)排序。
- 方法查找 (運行時 / 編譯時):
-
事件發布 (
post(Object event)
):- 獲取當前線程狀態:
post()
方法首先獲取當前線程的PostingThreadState
對象(一個 ThreadLocal 變量)。PostingThreadState
包含:eventQueue
:當前線程待處理的事件隊列。isPosting
:標識當前是否正在派發事件。isMainThread
:標識當前線程是否是主線程。subscription
:當前正在處理的事件對應的訂閱信息(用于cancelEventDelivery
)。
- 入隊: 將傳入的
event
對象加入到當前線程的eventQueue
中。 - 事件派發循環: 如果當前線程不在派發過程中 (
!isPosting
),則開始處理隊列:- 設置
isPosting = true
。 - 循環從隊列頭部取出事件。
- 調用
postSingleEvent(event, postingState)
處理單個事件。 - 處理完隊列所有事件后,重置狀態
isPosting = false
。
- 設置
- 獲取當前線程狀態:
-
單個事件處理 (
postSingleEvent
):- 查找訂閱者: 根據事件的運行時類 (
event.getClass()
),從訂閱關系映射表中查找對應的Subscription
列表。 - 處理事件繼承 (可選): 如果啟用了事件繼承(默認啟用),還會查找該事件類的所有父類和接口對應的訂閱者列表,并將所有找到的訂閱者合并到一個總的列表中(去重)。
- 遍歷訂閱者并派發 (
postToSubscription
): 對于找到的每一個Subscription
:- 檢查訂閱者是否已注銷(弱引用失效)。
- 根據 ThreadMode 決定執行方式:
POSTING
:直接調用。在當前發布線程直接通過反射(或 MethodHandle)調用訂閱者方法。最快,但需注意線程安全。MAIN
/MAIN_ORDERED
:- 如果當前是主線程:直接調用(
MAIN_ORDERED
會確保按入隊順序)。 - 如果當前不是主線程:將調用包裝成一個
Runnable
,通過mainThreadPoster
(通常是HandlerPoster
,它內部持有一個關聯到主線程 Looper 的Handler
)發送到主線程的消息隊列中排隊執行。
- 如果當前是主線程:直接調用(
BACKGROUND
:- 如果當前不是主線程:直接調用(在當前后臺線程)。
- 如果當前是主線程:將調用包裝成
Runnable
,通過backgroundPoster
(通常是BackgroundPoster
,它內部使用一個單線程的線程池ExecutorService
)提交到后臺線程執行。
ASYNC
:總是將調用包裝成Runnable
,通過asyncPoster
(通常是AsyncPoster
,它內部使用一個通用的線程池ExecutorService
)提交執行,與發布線程和當前線程無關。
- 優先級處理: 在同一個 ThreadMode 下,列表已經按優先級排序,高優先級的
Subscription
會先被處理。高優先級訂閱者可以通過cancelEventDelivery(event)
取消事件,阻止后續低優先級訂閱者收到該事件。cancelEventDelivery
只能在POSTING
模式下調用,因為它需要直接操作當前派發流程。
- 查找訂閱者: 根據事件的運行時類 (
-
粘性事件 (
postSticky(Object event)
):- 存儲: EventBus 內部維護一個
Map<Class<?>, Object>
用于存儲粘性事件。Key 是事件類型,Value 是該類型最新的粘性事件對象。發布粘性事件時,會先更新這個 Map(覆蓋舊事件)。 - 發布: 然后像普通事件一樣調用
post(event)
進行派發。 - 新訂閱者處理: 當一個新的訂閱者調用
register()
時,如果它聲明了sticky=true
的事件處理方法:- 注冊過程完成后,EventBus 會檢查粘性事件 Map。
- 對于該訂閱者每個聲明為
sticky=true
的事件類型,從 Map 中取出最新的粘性事件(如果存在)。 - 然后按照正常的派發邏輯(根據 ThreadMode)調用該訂閱者的對應方法,就好像這個事件剛剛發布一樣。
- 存儲: EventBus 內部維護一個
-
注銷 (
unregister(Object subscriber)
):- 遍歷內部訂閱關系映射表 (
Map<Class<?>, CopyOnWriteArrayList<Subscription>>
)。 - 對于每個事件類型的訂閱者列表,移除所有
subscription.subscriber
等于傳入的subscriber
的Subscription
對象。 - 移除后,該訂閱者對象將不再接收任何事件。
- 遍歷內部訂閱關系映射表 (
-
線程安全:
- 核心數據結構
Map<Class<?>, CopyOnWriteArrayList<Subscription>>
使用ConcurrentHashMap
或其變體保證鍵的并發訪問安全。CopyOnWriteArrayList
保證了單個事件類型訂閱者列表的線程安全(讀無鎖,寫復制)。 - 使用 ThreadLocal (
PostingThreadState
) 管理每個線程的派發狀態。 - 不同 ThreadMode 的派發器 (
HandlerPoster
,BackgroundPoster
,AsyncPoster
) 內部使用隊列和鎖/Handler/線程池來保證任務的有序執行和線程安全。 - 注冊/注銷操作通常是同步的(內部有鎖),應盡量在主線程或確保線程安全的環境下調用。
- 核心數據結構
關鍵優化點:
- 索引加速: 極大提升注冊速度,避免首次運行時反射掃描。
CopyOnWriteArrayList
: 讀多寫少場景(事件派發是高頻讀,注冊/注銷是相對低頻寫)性能好,讀操作完全無鎖。- 線程局部變量 (
PostingThreadState
): 高效管理每個線程的事件隊列和派發狀態。 - 按需線程切換: 只在需要時才將任務派發到其他線程(通過 Handler 或 Executor)。
- 弱引用:
Subscription
持有訂閱者對象的弱引用 (WeakReference
)。這非常重要!它確保如果訂閱者對象(如 Activity)被垃圾回收(例如用戶關閉了 Activity 但忘記調用unregister()
),Subscription
中的弱引用會自動失效。EventBus 在派發事件時會檢查弱引用是否有效,無效則跳過或移除該Subscription
,從而自動防止了因忘記注銷而導致的內存泄漏。這是 EventBus 設計中的一個關鍵安全機制。(注意:雖然弱引用提供了保護,但最佳實踐仍是顯式unregister()
以保持代碼清晰和及時釋放資源)。
總結:
EventBus 通過高效的發布/訂閱機制、強大的線程模式支持、粘性事件特性以及底層精心的設計(映射表、ThreadLocal、弱引用、線程池/Handler),為 Android 開發提供了一種極其便捷、靈活且相對安全的組件間通信方式。它特別擅長解耦跨組件、跨線程的通信需求。理解其底層原理(尤其是訂閱關系管理、線程派發邏輯和弱引用機制)對于正確、高效地使用 EventBus 至關重要。務必遵循注冊/注銷的生命周期管理,合理選擇 ThreadMode,并在大型項目中使用索引加速以獲得最佳性能。