EventBus 是一个用于Android的库,通过事件总线实现发布者-订阅者模式,允许组件之间在没有直接依赖关系的情况下交换数据。由GreenRobot开发,该库简化了Activity、Fragment、Service和Background Thread之间的通信。根据GitHub数据(2025年),EventBus拥有超过25000颗星,被用于成千上万的Android应用中。主要操作包括subscribe(订阅事件)、post(发送事件)和sticky event(为新订阅者延迟事件)。
要点
EventBus 是一个用于Android的事件总线库,实现发布者-订阅者模式。它允许在应用程序组件(Activity、Fragment、Service、ViewModel)之间传递事件,而无需在它们之间创建显式依赖关系。与标准Android机制(Intent、BroadcastReceiver)不同,EventBus在进程内工作,不使用IPC。该库针对性能进行了优化,并在正确配置Subscriber Index时不使用反射。
EventBus架构由三个关键元素组成:Event(包含数据的POJO类)、Subscriber(带有@Subscribe注解方法的对象)和EventBus(中央调度器)。订阅者通过EventBus.getDefault().register(this)注册,取消订阅通过unregister(this)。事件是类型化的:处理程序订阅特定的事件类,并且仅在该类或其子类的事件被post时被调用。
// POJO事件
data class MessageEvent(
val message: String,
val timestamp: Long = System.currentTimeMillis()
)
// Activity中的订阅者
class MainActivity : AppCompatActivity() {
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: MessageEvent) {
textView.text = event.message
}
}
// 从其他组件发送事件
EventBus.getDefault().post(MessageEvent("Hello from Service"))
默认情况下,EventBus在register()时使用反射来查找@Subscribe方法。Subscriber Index通过注解处理器在编译阶段生成处理程序的索引。这消除了反射的开销并加快了注册速度。要启用,请在build.gradle中添加eventbus-annotation-processor。如果classpath中有索引,EventBus会自动使用它。没有索引时,库仍然可以工作,但性能略有下降。
当调用EventBus.getDefault().post(event)时,库确定事件的类型,找到所有已注册的具有接受此类型的@Subscribe方法的订阅者,并根据指定的ThreadMode调用它们。订阅者的查找通过注册时构建的Class → CopyOnWriteArrayList
订阅者应在onStart()中注册,并在onStop()中取消注册。如果在onCreate()中注册并在onDestroy()中取消注册,由于finish()而未调用onDestroy而被销毁的Activity可能会保留在订阅者列表中。订阅者泄漏 — EventBus的主要问题之一:留在订阅者列表中的Activity在取消注册之前不会被GC回收。始终在正确的生命周期方法中配对register/unregister。
@Subscribe注解支持priority参数(整数,默认为0)。优先级较高的处理程序被较早调用。cancelEventDelivery()允许中断事件向其他订阅者的传递。这对于优先级处理程序(日志记录、身份验证)很有用,它们可以取消较低订阅者对事件的处理。该函数仅在事件发送线程中可用。
// 带优先级的复杂示例
data class NavigationEvent(val screen: String, val data: Bundle)
class NavigationInterceptor {
@Subscribe(priority = 10, threadMode = ThreadMode.POSTING)
fun onNavigationEvent(event: NavigationEvent) {
if (event.screen == "restricted" && !isAuthorized) {
EventBus.getDefault().cancelEventDelivery(event)
}
}
}
class AnalyticsLogger {
@Subscribe(priority = 5)
fun logNavigation(event: NavigationEvent) {
analytics.logScreen(event.screen)
}
}
// 发送事件
EventBus.getDefault().post(NavigationEvent("profile", bundle))
Android提供了几种用于进程内通信的机制:EventBus、LocalBroadcastManager(已弃用)和LiveData/Flow。每种都有其优缺点。选择取决于架构方法和性能要求。Google的现代推荐倾向于LiveData和Flow,因为与Lifecycle集成且没有泄漏。
| 特性 | EventBus | LocalBroadcastManager | LiveData / Flow |
|---|---|---|---|
| 类型化 | 通过事件类 | 通过Intent filter(字符串) | 通过泛型类型 |
| 生命周期感知 | 否(手动取消注册) | 否(手动取消注册) | 是(自动) |
| Sticky | 是(postSticky) | 否 | 是(LiveData — 始终sticky) |
| ThreadMode | MAIN, POSTING, BACKGROUND, ASYNC | 仅main | 通过observe/observeOn |
| 性能 | 高(Subscriber Index) | 中等(IPC包装) | 高(观察) |
EventBus在具有遗留代码的项目以及LiveData/Flow不可用的地方(仅Java项目)很有用。EventBus的Sticky events提供了LocalBroadcastManager所缺乏的灵活性。EventBus也更简单地将事件从Service发送到Activity而无需ViewModel — 特别是在需要通知后台任务进度时。该库体积很小(约50 KB),且不添加依赖项。
LiveData和Flow是Android Jetpack的一部分,并与Lifecycle集成。它们在组件销毁时自动取消订阅,消除了内存泄漏。Flow支持协程和复杂的转换操作符。Google推荐LiveData用于UI层,Flow用于存储库。EventBus仍然用于跨模块事件,其中导航和业务逻辑不适合MVVM。
Subscribe — 通过@Subscribe注解注册事件处理程序。方法必须是public、void,并接受恰好一个参数 — 事件类型。Post — 通过EventBus.getDefault().post(event)将事件发送给所有已订阅的处理程序。post方法不返回结果,也不通知调用了多少处理程序。对于需要回复的事件,请使用带有结果字段的单独Event类。
事件 — 任何Java/Kotlin类。建议对不可变事件使用data class,对具有可变字段的事件使用普通类。事件的命名应反映操作:UserLoggedInEvent、DataLoadedEvent、NetworkErrorEvent。避免使用带有String type字段的通用Event类 — 这丧失了类型化的优势。事件层次结构(父Event)允许订阅一组相关事件。
// 事件层次结构
open class UserEvent
data class UserLoggedIn(val userId: String) : UserEvent()
data class UserLoggedOut(val reason: String) : UserEvent()
// 订阅基类
class SessionManager {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onUserEvent(event: UserEvent) {
when (event) {
is UserLoggedIn -> startSession(event.userId)
is UserLoggedOut -> endSession(event.reason)
}
}
}
// 发送
EventBus.getDefault().post(UserLoggedIn("user_123"))
调用EventBus.getDefault().register(this)通过反射或Subscriber Index扫描订阅者的类,并将找到的@Subscribe方法存储到事件映射中。Unregister从映射中移除订阅者。未取消注册就重新注册 — 错误(将出现MultipleSubscriberException)。对于Fragment,在onStart()中注册并在onStop()中取消注册。对于Service — 在onCreate()和onDestroy()中。对于ViewModel不推荐 — 请使用LiveData。
Sticky event — 发送后保留在EventBus中的事件。在postSticky()之后注册的新订阅者会立即收到相应类型的最后一个sticky事件。这便于传递初始状态:打开屏幕时,它会收到注册前发送的最新数据。删除sticky事件可以通过EventBus.getDefault().removeStickyEvent(Class)完成。
ThreadMode确定处理程序在哪个线程中被调用。POSTING(默认)— 处理程序在与调用post相同的线程中执行。MAIN — 处理程序通过Handler在主线程中执行。BACKGROUND — 处理程序在后台线程中执行;如果post在主线程中被调用,EventBus将处理程序放入后台线程队列。ASYNC — 每个处理程序在从线程池中分配的单独后台线程中执行。对于UI更新,请使用MAIN。
// Sticky Event
data class LocationEvent(val lat: Double, val lng: Double)
// 从LocationService发送sticky事件
EventBus.getDefault().postSticky(LocationEvent(55.7558, 37.6173))
// 订阅者在注册后立即收到最后一个位置
class MapFragment : Fragment() {
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
// 如果已postSticky,将立即收到LocationEvent
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onLocationEvent(event: LocationEvent) {
moveMapTo(event.lat, event.lng)
}
}
// 删除sticky事件
EventBus.getDefault().removeStickyEvent(LocationEvent::class.java)
BACKGROUND对所有处理程序使用一个后台线程 — 它们按顺序执行。ASYNC为每个处理程序从池中创建一个新线程 — 它们并行执行。BACKGROUND适用于与共享数据库的输入输出操作。ASYNC — 用于独立的长时间操作(网络请求)。两种模式都需要对共享资源进行线程安全访问。线程数:ASYNC池是无限制的。
在使用EventBus时,开发人员经常犯导致内存泄漏、意外调用和性能下降的错误。最关键的:在Activity中忘记取消注册、在onCreate中注册(而不是在onStart/onStop中)、订阅Object(所有事件)、在无限循环中发送事件。通过Android Profiler进行分析有助于发现问题。
最常见的错误 — 在onCreate()中注册Activity而未在onDestroy()中取消注册。结果:EventBus持有对Activity的引用,GC无法释放它。当屏幕旋转时,会创建新的Activity,之前的Activity保留在内存中。解决方案:始终在onStart/onStop中配对register/unregister。对于Fragment,使用相同的方案。如果Activity在finish后仍被EventBus持有,请通过Memory Profiler检查。
没有Subscriber Index,EventBus在每次register()时使用反射来查找@Subscribe方法。在Android 6-7设备上,反射运行缓慢,导致延迟可达50毫秒。Subscriber Index完全消除了反射:方法在编译阶段通过注解处理器建立索引。对于有20个以上订阅者的项目,索引是必需的。检查kapt或annotationProcessor是否已连接到build.gradle中。
// build.gradle (app) — 连接Subscriber Index
dependencies {
implementation 'org.greenrobot:eventbus:3.3.1'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.3.1'
}
// 对于Kotlin使用kapt
plugins {
id 'kotlin-kapt'
}
dependencies {
implementation 'org.greenrobot:eventbus:3.3.1'
kapt 'org.greenrobot:eventbus-annotation-processor:3.3.1'
}
// 索引配置(在defaultConfig中)
kapt {
arguments {
arg('eventBusIndex', 'com.app.EventBusIndex')
}
}
基于Kotlin和Jetpack Compose的现代项目更倾向于使用kotlinx.coroutines库中的SharedFlow和Channel。SharedFlow支持重放(sticky)、缓冲和背压。Channel — 一次性事件(toast、导航)。两种解决方案都通过repeatOnLifecycle与Lifecycle集成,并且不需要手动取消订阅。对于新项目,建议使用SharedFlow替代EventBus。对于现有项目,在重构时进行迁移是合理的。
常见问题
EventBus是一个事件总线,用于在任何组件(Activity、Fragment、Service)之间交换数据。LiveData是一个生命周期感知的数据包装器,供UI组件观察。LiveData通过Lifecycle自动管理订阅。EventBus需要手动register/unregister。LiveData推荐用于UI层,EventBus — 用于LiveData不方便的跨模块通信。
Sticky event — 发送后保留在EventBus中的事件。在postSticky()之后注册的新订阅者会立即收到最后一个sticky事件。用于初始状态:打开屏幕时,它会收到最新数据而无需重复请求。通过removeStickyEvent()或发送同类型的新sticky事件时删除。
是的,EventBus是线程安全的。可以从任何线程调用post()。事件传递给订阅者的过程在库内同步进行。ThreadMode确定处理程序的执行线程:MAIN(通过Handler在主线程)、POSTING(发送者线程)、BACKGROUND(后台任务队列)、ASYNC(单独线程)。对于UI更新使用MAIN,对于重操作使用ASYNC。
通过EventBus.builder().logNoSubscriberMessages(true).sendNoSubscriberEvent(true).install()启用日志记录。订阅NoSubscriberEvent以跟踪没有处理程序的事件。使用SubscriberExceptionEvent进行全局异常处理。Android Profiler帮助查找泄漏。对于复杂场景,编写测试:EventBus.getDefault().register(mock) + post(event) + verify(mock)。
不可以,EventBus(GreenRobot)绑定到Android SDK和JVM。对于Kotlin Multiplatform,请使用Kotlin Multiplatform SharedFlow或KMMBus — 支持共享代码的库。EventBus在KMM项目的Android端可以工作,但在commonMain中不可用。对于跨平台事件,更推荐使用平台的本地机制或通过expect/actual进行抽象。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。