跳到主要内容

设计事件总线

问题

如何设计 Android 事件总线?有哪些替代方案?

答案

事件总线演进

方案原理现状
EventBus(greenrobot)反射/注解处理器仍可用,但不推荐新项目
LocalBroadcastBroadcastReceiver已废弃
LiveData EventLiveData 包装一次性事件有粘性问题
SharedFlowKotlin Flow推荐

SharedFlow 实现事件总线

// 全局事件总线
object EventBus {
// MutableSharedFlow: replay=0 表示不保留历史事件
private val _events = MutableSharedFlow<Event>(
replay = 0,
extraBufferCapacity = 64,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val events: SharedFlow<Event> = _events.asSharedFlow()

suspend fun emit(event: Event) {
_events.emit(event)
}
}

// 事件定义
sealed class Event {
data class UserLoggedIn(val userId: String) : Event()
data object UserLoggedOut : Event()
data class NetworkChanged(val isOnline: Boolean) : Event()
}
// 发送事件
viewModelScope.launch {
EventBus.emit(Event.UserLoggedIn("123"))
}

// 接收事件
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
EventBus.events
.filterIsInstance<Event.UserLoggedIn>()
.collect { event ->
// 处理登录事件
}
}
}

带类型过滤的事件总线

object TypedEventBus {
private val _events = MutableSharedFlow<Any>(
extraBufferCapacity = 64,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)

suspend fun <T : Any> emit(event: T) {
_events.emit(event)
}

// 类型安全的订阅
inline fun <reified T : Any> on(): Flow<T> =
_events.filterIsInstance<T>()
}

// 使用
TypedEventBus.on<UserLoggedIn>().collect { /* ... */ }

设计要点

  • 生命周期安全:结合 repeatOnLifecycle,页面不可见时自动暂停收集
  • 线程安全:SharedFlow 天然线程安全
  • 无粘性replay = 0 新订阅者不会收到历史事件
  • 性能:无反射开销,编译期类型检查

常见面试问题

Q1: 为什么不推荐 EventBus(greenrobot)?

答案

  1. 编译期安全差:事件注册用反射或注解处理器,类型错误只在运行时发现
  2. 难以追踪:全局发布-订阅,代码中难以追踪事件的发送和接收路径
  3. 生命周期问题:需手动注册/注销,忘记注销导致内存泄漏
  4. 被官方方案替代:SharedFlow / StateFlow 配合协程和 Lifecycle 提供了更好的替代方案

相关链接