设计事件总线
问题
如何设计 Android 事件总线?有哪些替代方案?
答案
事件总线演进
| 方案 | 原理 | 现状 |
|---|---|---|
| EventBus(greenrobot) | 反射/注解处理器 | 仍可用,但不推荐新项目 |
| LocalBroadcast | BroadcastReceiver | 已废弃 |
| LiveData Event | LiveData 包装一次性事件 | 有粘性问题 |
| SharedFlow | Kotlin 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)?
答案:
- 编译期安全差:事件注册用反射或注解处理器,类型错误只在运行时发现
- 难以追踪:全局发布-订阅,代码中难以追踪事件的发送和接收路径
- 生命周期问题:需手动注册/注销,忘记注销导致内存泄漏
- 被官方方案替代:SharedFlow / StateFlow 配合协程和 Lifecycle 提供了更好的替代方案