英文文档 English Document
library-android 是 FlowBus 的 Android 适配模块。
Gradle 模块目录名是 library-android,对外发布的依赖坐标是:
implementation("io.github.logan0817:flowbus:<latest-version>")如果你是 Android 项目接入 FlowBus,通常就从这个模块开始,不需要先研究 flowbus-core。
FlowBus 更适合处理“事件广播”问题,也就是:
- 某个地方发出一个通知,多个地方都可能关心
- 发送方和接收方不想直接互相持有引用
- 事件天然是异步的,更适合用
Flow收 - 你想按页面、Activity、NavBackStackEntry 或全局维度组织事件
Android 项目里最常见的使用方式是:
- 页面 A 通知页面 B 刷新
- Fragment 通知 Activity 执行某个 UI 动作
- ViewModel / Repository / Worker 通知 UI 弹 Toast、刷新列表、重新拉数据
- 某个作用域里广播一次事件,让多个观察者同时响应
它不适合下面这些场景:
- 页面内部状态管理:优先
StateFlow - 明确的一对一调用:优先直接方法调用 / use case
- 严格请求-响应:优先返回值、挂起函数、专用通道
- 长期共享状态:优先状态容器,而不是事件总线
postEvent(...)/emitEvent(...)发的是全局事件,整个 app 都能订阅。postEventTo(owner, ...)/owner.postEvent(...)发的是局部事件,只在这个owner对应的总线里流动。- 默认按“事件类型”分发;如果同一类型要拆成多个业务通道,就用
eventChannel<T>("name")。 post*是立即尝试发送,tryPost*会额外返回是否已被当前总线接收,emit*是挂起直到成功写入。onEvent(...)是推荐的 UI 最短路径;collectEvent(flow)更适合你已经拿到了Flow。
implementation("io.github.logan0817:flowbus:<latest-version>")如果只是一个简单动作,直接一个 data class 或 data object 就够了:
data class RefreshHomeEvent(val source: String)postEvent(RefreshHomeEvent(source = "login"))最短写法:
viewLifecycleOwner.onEvent<RefreshHomeEvent> { event ->
viewModel.refreshFrom(event.source)
}如果你更喜欢先拿到 Flow 再自己组合:
viewLifecycleOwner.collectEvent(eventFlow<RefreshHomeEvent>()) { event ->
viewModel.refreshFrom(event.source)
}看到这里就已经可以开始用了:
- 发送:
postEvent(...) - 接收:
onEvent<T> { ... } - 如果要限制在某个 Activity / Fragment / NavBackStackEntry 作用域内,再用
owner版本
适合:
- 登录成功后让多个页面刷新
- 某个后台任务完成后让多个观察者同步更新
- 全局消息、埋点通知、刷新指令
data class SyncFinishedEvent(val successCount: Int)
postEvent(SyncFinishedEvent(successCount = 3))
viewLifecycleOwner.onEvent<SyncFinishedEvent> { event ->
showResult(event.successCount)
}这类事件没有 owner 限制,谁订阅谁就能收到。
适合:
- Fragment 通知 Activity 刷新标题栏
- Fragment 请求 Activity 执行导航、弹窗、权限流程
- 某个页面树只想在当前 Activity 内部广播,不想影响全局
data object ReloadToolbarEvent
requireActivity().postEvent(ReloadToolbarEvent)
viewLifecycleOwner.onEvent<ReloadToolbarEvent>(from = requireActivity()) {
renderToolbar()
}这里的 requireActivity() 不是监听者,而是“事件总线挂在哪个作用域上”的意思。
同理,你也可以把事件挂在 NavBackStackEntry 或自定义 ViewModelStoreOwner 上。
如果你更喜欢 owner 在前的写法,也可以这样:
requireActivity().postEvent(ReloadToolbarEvent)
viewLifecycleOwner.collectEvent(requireActivity().eventFlow<ReloadToolbarEvent>()) {
renderToolbar()
}适合:
Toast、SnackBar、导航命令这类“值类型简单,但语义不同”的事件- 同一个类型需要拆成多个明确的业务 channel
- 你不想在业务代码里散落
"ui.toast"、"page.reload"这类裸字符串
推荐写法:
val toastChannel = eventChannel<String>("ui.toast")
toastChannel.post("保存成功")
viewLifecycleOwner.onEvent(toastChannel) { message ->
showToast(message)
}如果这个事件只想在当前 Activity 范围内传播:
val activityCommand = eventChannel<String>("activity.command")
activityCommand.postTo(requireActivity(), "reload")
viewLifecycleOwner.onEvent(from = requireActivity(), channel = activityCommand) { command ->
handleActivityCommand(command)
}你当然也可以继续直接写 eventName:
postEvent(event = "保存成功", eventName = "ui.toast")
viewLifecycleOwner.collectEvent(eventFlow<String>(eventName = "ui.toast")) {
showToast(it)
}但对外公开或复用频繁的通道,优先推荐 eventChannel(...),更清楚,也更不容易写错。
适合:
- 后台同步完成后提示 UI 刷新
- Repository 完成某个动作后发通知给多个页面
- Worker 执行完任务后,页面恢复时自动拿到结果
data class UploadFinishedEvent(val taskId: String)
class UploadViewModel : ViewModel() {
fun onUploadSuccess(taskId: String) {
postEvent(UploadFinishedEvent(taskId = taskId))
}
}
class UploadFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewLifecycleOwner.onEvent<UploadFinishedEvent> { event ->
showToast("任务 ${event.taskId} 上传完成")
}
}
}如果你只想通知当前 Activity 相关页面,而不是全 app,就把发送改成:
requireActivity().postEvent(UploadFinishedEvent(taskId = taskId))接收时改成:
viewLifecycleOwner.onEvent<UploadFinishedEvent>(from = requireActivity()) { event ->
showToast("任务 ${event.taskId} 上传完成")
}| 需求 | 推荐 API |
|---|---|
| 全局发送 | postEvent(...) |
| 需要返回是否接收 | tryPostEvent(...) |
| 发到 owner 作用域 | postEventTo(owner, ...) |
| 命名通道 | eventChannel<T>("name") + channel.post(...) |
| 最短监听 | onEvent(...) |
| 先拿 Flow 再组合 | eventFlow(...) + collectEvent(...) |
| 保证写入成功 | emitEvent(...) |
最常用:
postEvent(MyEvent(...))如果你想知道这次 best-effort 发送是否被当前总线接收:
val accepted = tryPostEvent(MyEvent(...))需要挂起直到真正写入成功:
viewModelScope.launch {
emitEvent(MyEvent(...))
}这两种写法等价,按你更顺手的风格选一种:
postEventTo(owner = requireActivity(), event = ReloadToolbarEvent)requireActivity().postEvent(ReloadToolbarEvent)val toastChannel = eventChannel<String>("ui.toast")
toastChannel.post("保存成功")
toastChannel.postTo(requireActivity(), "保存成功")viewLifecycleOwner.onEvent<RefreshHomeEvent> { event ->
render(event)
}或者监听命名 channel:
viewLifecycleOwner.onEvent(toastChannel) { message ->
showToast(message)
}viewLifecycleOwner.collectEvent(eventFlow<RefreshHomeEvent>()) { event ->
render(event)
}owner 版本:
viewLifecycleOwner.collectEvent(requireActivity().eventFlow<ReloadToolbarEvent>()) {
renderToolbar()
}命名 channel 版本:
viewLifecycleOwner.collectEvent(toastChannel.flow()) { message ->
showToast(message)
}简单理解:
onEvent(...):最短、最直接,适合大多数 UI 监听eventFlow(...):适合你还要map、filter、debounce或组合多个流collectEvent(flow):把任意Flow绑定到LifecycleOwner安全收集,不只限于 FlowBus
特点:
- 非挂起
- 调用最轻便
- best-effort
- 当底层缓冲无法立刻接收时,可能发送失败
适合:
- UI 层一次性通知
- 可以容忍极端情况下丢失的轻量事件
- 你就是想快速广播一下
特点:
- 挂起函数
- 遵循背压
- 会等待直到成功写入
适合:
- 你不希望事件悄悄丢掉
- 发送时机比“调用手感”更重要
- Repository / Worker / ViewModel 内的关键通知
经验上可以这样选:
- UI 点击、轻量提示:先用
postEvent - 关键业务链路、必须送达:用
emitEvent
sticky event 的含义是:
- 事件发出去后,会保留最近一次值
- 后来才开始订阅的人,也能立刻拿到最近一次值
适合:
- “当前配置已准备好”
- “最近一次会话信息”
- “初始化结果”
- “页面恢复时需要读到最近一次成功状态”
不适合:
- Toast
- 导航
- 一次性点击事件
- 只该消费一次的瞬时动作
示例:
data class SessionReadyEvent(val userId: Long)
postStickyEvent(SessionReadyEvent(userId = 42L))
viewLifecycleOwner.onEvent<SessionReadyEvent>(isSticky = true) { event ->
bindSession(event.userId)
}不再需要时可以清掉:
clearStickyEvent<SessionReadyEvent>()
removeStickyEvent<SessionReadyEvent>()简单理解:
clearStickyEvent:清 replay 缓存,但保留通道removeStickyEvent:把这个 sticky 通道整体移除
推荐顺序:
- 单个动作,用一个明确的
data class/data object - 同一业务域的多个动作,用
sealed interface/sealed class - 只有在值真的非常简单而且语义明确时,才直接发
String/Int
data object ReloadToolbarEvent
data class ShowToastEvent(val message: String)sealed interface MainUiEvent {
data object Refresh : MainUiEvent
data class ShowToast(val message: String) : MainUiEvent
data class OpenDetail(val id: Long) : MainUiEvent
}发送时记得指定父类型,这样这些子事件才会进入同一个事件通道:
postEvent<MainUiEvent>(MainUiEvent.Refresh)
postEvent<MainUiEvent>(MainUiEvent.ShowToast(message = "success"))
viewLifecycleOwner.onEvent<MainUiEvent> { event ->
when (event) {
MainUiEvent.Refresh -> refresh()
is MainUiEvent.ShowToast -> showToast(event.message)
is MainUiEvent.OpenDetail -> openDetail(event.id)
}
}如果你不显式写父类型,Kotlin 可能会把通道推断成具体子类型,这不是 FlowBus 的限制,而是类型推断的自然结果。
postEvent(RefreshHomeEvent(source = "login"))
viewLifecycleOwner.onEvent<RefreshHomeEvent> { render(it) }requireActivity().postEvent(ReloadToolbarEvent)
viewLifecycleOwner.onEvent<ReloadToolbarEvent>(from = requireActivity()) { renderToolbar() }val toastChannel = eventChannel<String>("ui.toast")
toastChannel.post("保存成功")
viewLifecycleOwner.onEvent(toastChannel) { showToast(it) }如果你想改底层缓冲、日志、错误处理,请在第一次调用任何总线 API 之前配置:
FlowBusAndroid.configure(
FlowBusConfig(
logger = MyFlowBusLogger,
errorHandler = FlowBusErrorHandler.Rethrow
)
)当你有这些需求时,再看 core 文档就够了:
- 需要自己管理
FlowBus实例,而不是使用 Android 默认入口 - 需要多个 bus 实例隔离
- 需要非 Android 环境复用同一套事件模型
- 需要
FlowBusScope、EventKey、DefaultFlowBus这些更底层能力
文档入口:
- 本地文档:
../flowbus-core/README.md - GitHub 地址:flowbus-core README
- 仓库主页:FlowBus
- Core 模块:flowbus-core
- Demo 模块:app