一、Kotlin的简单概念
Kotlin Flow 是 Kotlin 协程库(kotlinx.coroutines)中的一个核心组件,用于处理异步数据流。它类似于 RxJava 的 Observable 或 Java 的 Stream,但更紧密集成于 Kotlin 协程,强调冷流(cold flow)的惰性执行和结构化并发。Flow 的设计目标是简化异步序列数据的处理,如网络请求、数据库查询、传感器数据或UI事件流。
- 部分核心要点提前了解:
1、Flow与协程 :Flow基于协程,收集collect()需要在协程中进行,而emit只能在协程或挂起函数中调用
2、避免runBlocking :runBlocking会阻塞线程,仅适用测试或调试,本文为测试方便,使用了它。推荐使用 lifecycleScope、viewModelScope 或自定义 CoroutineScope 替代。
3、生命周期集成 :在Android中,使用 lifecycleScope 和 repeatOnLifecycle 绑定 Flow 收集到页面生命周期,避免内存泄漏。
4、热流与冷流 :StateFlow和SharedFlow都是热流。普通Flow冷流通过 shareIn 或 stateIn 转换为热流(如 SharedFlow 或 StateFlow),以实现数据共享或状态管理。
5、与框架集成 :Flow 可无缝结合 Retrofit 和 Room,支持响应式数据处理。请看本文章后续的网络获取用户名(实例二)。
二、如何创建Flow
以下是创建 Flow 的几种常见方法:
kotlin
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
//1、简单的flow创建
val simpleFlow = flow {
emit(1)
emit(2)
emit(3)
}
//2、集合创建
val flowList = listOf(1, 2, 3).asFlow()
//3、序列创建
val flowSequence = sequenceOf(1, 2, 3).asFlow()
//4、使用flowOf
val flowOf = flowOf(1, 2, 3)
//5、动态创建
val flow = flow {
for (i in 1..5) {
delay(1000) // 模拟异步延迟
emit(i * 2) // 发出值
}
}
三、高级Flow操作符
- 转换操作符
kotlin
Kotlin
class AdvancedFlowExamples {
// 1. map操作符 - 数据转换
fun mapExample(): Flow<String> = flowOf(1, 2, 3)
.map { number ->
"Number: $number"
}
// 2. transform操作符 - 更灵活的转换
fun transformExample(): Flow<String> = flowOf(1, 2, 3)
.transform { number ->
emit("Start: $number")
emit("End: $number")
}
// 3. filter操作符 - 数据过滤
fun filterExample(): Flow<Int> = flowOf(1, 2, 3, 4, 5)
.filter { it % 2 == 0 }
// 4. take操作符 - 限制数量
fun takeExample(): Flow<Int> = flowOf(1, 2, 3, 4, 5)
.take(3)
// 5. zip操作符 - 合并两个流
fun zipExample(): Flow<String> {
val flow1 = flowOf("A", "B", "C")
val flow2 = flowOf(1, 2, 3)
return flow1.zip(flow2) { letter, number ->
"$letter$number"
}
}
// 6. combine操作符 - 最新数据组合
fun combineExample(): Flow<String> {
val flow1 = MutableStateFlow("A")
val flow2 = MutableStateFlow(1)
CoroutineScope(Dispatchers.Default).launch {
delay(1000)
flow1.value = "B" // 更新 flow1
delay(1000)
flow2.value = 2 // 更新 flow2
}
return flow1.combine(flow2) { letter, number ->
"$letter$number"
}
}
// 7.onEach - 执行副作用操作,不改变数据
flowOf(1, 2, 3)
.onEach { println("Processing: $it") } // 打印但不修改流
.collect { println("Received: $it") } // 最终收集到的还是 1, 2, 3
}
四、收集和消费
Flow是冷流,只有使用collect进行收集时才会执行
,这句话是重点。每次收集都是从头开始发送数据。
用法1:基础收集
kotlin
CoroutineScope(Dispatchers.Main).launch {
flowOf(1, 2, 3, 4, 5).collect {
println(it)
}
}
用法2 :限定收集(take / drop)
实际应用:从无限流中截取前n条消息
kotlin
val flow = flow {
var i = 0
while (true) {
emit(i++)
}
}
runBlocking {
flow.take(3).collect { println(it) } // 输出: 0 1 2
}
用法3 :数据聚合或计算(reduce / fold)
实际应用:计算购物车总价,这个例子个人觉得有点重要,因为我曾经干过收银软件,泪啊。
kotlin
val flow = flow {
for (i in 1..10) {
emit(i)
}
}
runBlocking {
//数据总和计算
val total = flow.reduce { acc, value ->
acc + value
}
//fold 用以带初始值的计算
val total2 = flow.fold(100) { acc, value ->
acc + value
}
}
用法4 :线程切换
实际应用:在IO线程处理数据,在主线程刷新UI
kotlin
val ioFlow = flow {
emit(1)
}.flowOn(Dispatchers.IO) // 指定上游上下文
runBlocking {
ioFlow.collect { /* 主线程 */ }
}
五、热流和冷流
什么叫热流?什么叫冷流?小伙伴们先理解下这两个概念。热流相当于足球比赛直播,你只能接受订阅后发出的数据。之所以称为热流,是因为它与收集者之间是独立的。冷流只有在收集时才生成,收集结束时上游停止,互相依存。
基本概念:
无论是否有收集者,都会发送数据
,注意与冷流的区别。- 多个收集者可以同时收集同一个SharedFlow
- SharedFlow 默认通过 replay 参数缓存数据(replay=0 表示不缓存),而 StateFlow 始终只保留最新的单个值。
冷流vs热流
特性 | 冷流 | 热流(StateFlow/SharedFlow) |
---|---|---|
数据发送 | 随collect开始、结束 | 独立于收集,可以持续发送 |
收集端管理 | 自动(随collect结束) | 自动(使用repeatOnLifecycle跟生命周期绑定) |
发送端管理 | 自动 | 自动(由作用域控制,如 viewModelScope 自动取消) |
典型场景 | 按需获取收据(API数据、数据库获取) | 全局通知、状态管理 |
用法1:SharedFlow 实际应用:事件总线,如全局通知(登录状态变化)。
kotlin
val shared = MutableSharedFlow<String>()
runBlocking {
launch { shared.collect { println("Collector1: $it") } }
launch { shared.collect { println("Collector2: $it") } }
shared.emit("Event") // 两个收集者都收到
}
用法2 :StateFlow
实际应用:记录ViewModel中的UI状态,如加载中、成功、错误。可以看做livedata的替代者
kotlin
val state = MutableStateFlow("loading")
runBlocking {
launch { state.collect { println(it) } } // 输出 loading, Success
state.value = "Success" // 更新状态,所有收集者收到
}
runBlocking一般为测试使用,还是写个Viewmodel中的例子吧。其他地方使用的时候,可参考此处调整。
kotlin
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState = _uiState.asStateFlow()
fun loadData() {
viewModelScope.launch {
// 加载数据
_uiState.value = UiState.Success
}
}
}
在Activity中接受数据
kotlin
lifecycleScope.launch {
//页面在前台才开始收集
repeatOnLifecycle(Lifecycle.State.RESUMED) {
mediaVM.uiState.collect() { state ->
when(state) {
is UiState.Sucess -> {
//更新UI
}
}
}
}
}
也可以尝试以下用法,更简洁方便,此处是扩展方法,本质是一样的。
mediaVM.uiState.onEach {
when(state) {
is UiState.Success -> {
//更新UI
}
}
}.launchIn(lifecycleScope)
StateFlow和SharedFlow 的区别
特性 | StateFlow | SharedFlow |
---|---|---|
初始值 | 必须有 | 可有可无 |
重播 | 最新值 | 可设置 |
应用 | 状态管理 | 事件广播 |
去重 | 自动去重 | 不去重 |
SharedFlow 特别适用于需要发送一次性事件
(如 Toast 消息、跳转事件等)的场景,与 StateFlow 的持续状态管理
形成互补。 当然你也可以使用SharedFlow编写一个事件总线,具体实现可参照文末的实例三
六、错误处理和重试
用法1 :捕获错误
实际应用:网络请求(数据库操作)失败时显示错误
kotlin
val flow2 = flow {
emit(1)
emit(2)
emit(3)
throw Exception("错误")
}
runBlocking {
flow2.catch {
println(it)
}.collect { e ->
println(e)
}
}
//输出1、2、3、错误
用法2:重试- retry/retryWhen 实际应用:网络请求失败后重试操作
kotlin
fun retryExample() {
var attempt = 0
val flow = flow {
attempt++
println("尝试次数: $attempt")
emit(1)
emit(2)
if (attempt < 3) {
throw RuntimeException("模拟失败")
}
emit(3)
}
runBlocking {
flow
.retry(3) { e -> // 最多重试3次
println("重试因为异常: ${e.message}")
true // 返回true表示需要重试
}
.catch { e ->
println("最终失败: ${e.message}")
}
.collect { value ->
println("retry: $value")
}
}
runBlocking {
flow
.retryWhen { cause, attempt ->
if (attempt < 3 && cause is RuntimeException) {
delay(1000) // 延迟1秒后重试
true // 继续重试
} else {
false // 不再重试
}
}
.catch { e ->
println("处理最终异常: ${e.message}")
}
.collect { value ->
println("retryWhen: $value")
}
}
}
retryWhen和retry的主要区别在于:retryWhen更加灵活,除了设置重试次数外,还可以设置其他条件。
七、背压和生命周期集成
数据的上游生产速度大于下游消费速度。一般处理方案都是丢弃部分数据。不建议缓存全部数据,极端情况下可能会导致OOM。
- 背压处理:使用 buffer/conflate/collectLatest。
实际应用:conflate 丢弃中间值,只处理最新。collectLatest()新值到达时取消对旧值的处理,替换例子中的collect()即可,不再单独举例。
kotlin
fun conflateExample() {
val fastProducer = flow {
(1..5)
.forEach {
emit(it);
delay(100)
}
}
runBlocking {
fastProducer.conflate().collect {
delay(300);
println(it)
} // 可能输出 1 3 5 (输出结果不一定,丢弃中间数据)
}
}
kotlin
//buffer的应用,缓冲区,建议设置缓存区满策略
fun bufferExample() {
val fastProducer = flow {
(1..20)
.forEach {
emit(it);
//生产速度100ms
delay(100)
}
}
runBlocking {
//背压处理,缓存五条,超出后丢弃旧的数据,保留最新数据
fastProducer.buffer(capacity = 5, onBufferOverflow = BufferOverflow.DROP_OLDEST).collect {
//消费速度500ms,模拟生产速度大于消费速度的场景,数据积压场景
delay(500);
println(it)
}
}
}
- 与 Lifecycle 集成(Android):使用 repeatOnLifecycle 避免内存泄漏
实际应用: 页面处于活跃状态才刷新数据更新UI
kotlin
lifecycleScope.launch {
// repeatOnLifecycle 函数确保代码块只在指定的生命周期状态下执行
// Lifecycle.State.STARTED 表示组件处于 STARTED 或 RESUMED 状态
// 这样可以避免在后台时执行不必要的操作,节省资源
repeatOnLifecycle(Lifecycle.State.STARTED) {
// 开始收集 Flow 数据流
// collect 是一个挂起函数,会持续监听数据变化
viewModel.uiState.collect {
// 每当 uiState 发出新值时,这里的代码就会执行
// 可以安全地更新 UI,因为此时组件处于活跃状态
// 更新 UI
updateUI(data)
}
}
// 当组件进入 STOPPED 状态时,collect 会暂停
// 当组件重新进入 STARTED 状态时,会从上次中断的地方继续执行
}
八、实时聊天(实例一)
数据模型定义。
kotlin
data class ChatMessage(
val id: String,
val chatId: String,
val text: String,
val senderId: String,
val timestamp: Long,
val status: MessageStatus
)
enum class MessageStatus { SENDING, SENT, FAILED }
enum class UserStatus { ONLINE, OFFLINE }
interface ChatDao {
fun observeMessages(chatId: String): Flow<List<ChatMessage>>
suspend fun insertMessage(message: ChatMessage)
}
interface ApiService {
suspend fun sendMessage(message: ChatMessage): Response
}
data class SendMessageResponse(val messageId: String)
interface SocketManager {
fun observeUserStatus(userId: String): Flow<UserStatus>
}
主要代码实现:
kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import java.util.UUID
/**
* 聊天数据仓库类,负责处理聊天相关的数据逻辑
* 包括消息的获取、发送和用户状态监听等功能
*/
class ChatRepository {
/**
* 观察指定聊天室的消息流
*
* @param chatId 聊天室ID
* @return 返回按时间戳排序的消息列表Flow
*/
fun observeMessages(chatId: String): Flow<List<ChatMessage>> =
database.chatDao()// 获取聊天数据访问对象
.observeMessages(chatId)// 观察数据库中的消息变化
.mapLatest { messages -> // 使用mapLatest处理最新数据,如果有新数据到达会取消之前的处理
messages.sortedBy { it.timestamp } // 按时间戳升序排序消息
}
.distinctUntilChanged()//去重,避免重复数据
.flowOn(Dispatchers.Default)
/**
* 发送消息并监听发送状态
*
* @param message 要发送的聊天消息对象
* @return 返回消息状态变化的Flow
*/
fun sendMessage(message: ChatMessage): Flow<MessageStatus> = flow {
// 1. 先发送"发送中"状态
emit(MessageStatus.SENDING)
// 2. 尝试发送消息
try {
// 调用API服务发送消息
val response = apiService.sendMessage(message)
if (response.isSuccessful) {
// 3. 发送成功后,保存到本地数据库
database.chatDao().insertMessage(message.copy(
//优先使用服务器返回ID,否则使用本地构建的id
id = response.body()?.messageId ?: message.id,
status = MessageStatus.SENT //更新消息状态为已发送
))
//发射消息发送成功状态
emit(MessageStatus.SENT)
} else {
//发射失败状态
emit(MessageStatus.FAILED)
}
} catch (e: Exception) {
//发射失败状态
emit(MessageStatus.FAILED)
}
}
.retryWhen { cause, attempt -> //当flow出现异常时,是否重试
//最多重试三次且仅在IOException进行重试
if (attempt < 3 && cause is IOException) {
//延迟重试,指数退避,根据重试次数来决定。
delay(2_000 * attempt)
true //返回true表示需要重试
} else {
false //不需要重试
}
}
.flowOn(Dispatchers.IO) //在IO调度器上执行网络和数据库操作
/**
* 监听用户在线状态
*
* @param userId 用户ID
* @return 返回用户状态变化的Flow
*/
fun observeUserStatus(userId: String): Flow<UserStatus> =
socketManager.observeUserStatus(userId) // 通过Socket管理器观察用户状态
.distinctUntilChanged()// 过滤掉重复的状态变化
.debounce(2000) //防抖动处理,2秒内无新状态变化时发射最后的值
}
/**
* 聊天界面的ViewModel类,负责处理UI逻辑和数据绑定
*
* @param repository 聊天数据仓库实例
* @param chatId 当前聊天室ID
*/
class ChatViewModel(
private val repository: ChatRepository,
private val chatId: String
) : ViewModel() {
//消息列表的状态流,用于在UI中观察消息变化
private val _messages = MutableStateFlow<List<ChatMessage>>(emptyList())
// 暴露只读的状态流, 防止外部发送数据
val messages: StateFlow<List<ChatMessage>> = _messages.asStateFlow()
/**
*
* 启动消息观察协程,将数据库中的消息同步到UI状态
*/
init {
repository.observeMessages(chatId)
.onEach { messages ->
_messages.value = messages
}
.launchIn(viewModelScope)
}
/**
* 发送消息的方法
*
* @param text 消息文本内容
*/
fun sendMessage(text: String) {
// 构造聊天消息对象
val message = ChatMessage(
id = UUID.randomUUID().toString(), // 生成唯一消息I
chatId = chatId,
text = text,
senderId = currentUserId,// 假设 currentUserId 为 ViewModel 中的属性或从外部传入
timestamp = System.currentTimeMillis(),
status = MessageStatus.SENDING // 初始状态为发送中
)
//发送消息,并监听状态变化,收集状态
repository.sendMessage(message)
.onEach { status ->
// 更新消息状态
updateMessageStatus(message.id, status)
}
.launchIn(viewModelScope)
}
}
九、网络查询用户(实例二)
下面是一个完整的查询用户的接口实例
- 数据模型定义
kotlin
/**
* 用户数据类,用于表示系统中的用户信息
*
* @property id 用户唯一标识符
* @property name 用户姓名
* @property email 用户邮箱地址
*/
data class User(
val id: Int,
val name: String,
val email: String
)
/**
* 用于封装网络请求的返回结果
*
* @param T 泛型参数,表示响应中数据的具体类型
* @property data 接口返回的具体数据内容
* @property status 响应状态码
* @property message 错误信息,可选
*/
data class ApiResponse<T>(
val data: T,
val status: String,
val message: String? = null
)
- Repository层实现
kotlin
/**
* 定义用户相关操作接口
* 使用Flow作为返回类型,方便响应式数据流处理
*/
interface UserRepository {
/**
* 获取所有用户列表
*
* @return 返回包含用户列表的Flow,支持实时更新
*/
fun getUsers(): Flow<List<User>>
/**
* 根据ID获取用户
*
* @param id 用户ID
* @return 返回包含用户信息的Flow,用户不存在时返回null
*/
fun getUserById(id: Int): Flow<User?>
/**
* 根据查询条件搜索用户
*
* @param query 搜索关键字
* @return 返回匹配搜索条件的用户列表Flow
*/
fun searchUsers(query: String): Flow<List<User>>
}
/**
* 用户数据仓库实现类,负责具体的用户数据获取
* 结合网络API和本地数据库提供统一的数据访问接口
*
* @param apiService 网络API服务接口
* @param database 本地数据库访问对象
*/
class UserRepositoryImpl(
private val apiService: UserApiService,
private val database: UserDatabase
) : UserRepository {
// 场景1:从网络获取数据并缓存到本地
/**
* 获取用户列表,实现先显示缓存后更新的策略
*
* @return 返回用户列表Flow,首先发送缓存数据,然后再发送网络最新数据
*/
override fun getUsers(): Flow<List<User>> = flow {
// 1. 先从本地数据库获取缓存数据
val cachedUsers = database.userDao().getAllUsers()
if (cachedUsers.isNotEmpty()) {
emit(cachedUsers) // 先发射缓存数据,提升用户体验
}
// 2. 从网络获取最新数据
try {
val response = apiService.getUsers()
if (response.isSuccessful) {
val users = response.body()?.data ?: emptyList()
// 3. 将最新数据保存到本地数据库
database.userDao().insertAll(users)
// 4. 发送最新数据给前台观察者
emit(users)
}
} catch (e: Exception) {
// 5. 网络错误时,如果无缓存数据则抛出异常,否则使用缓存数据
if (cachedUsers.isEmpty()) {
throw e //无缓存数据抛出异常
}
}
}
.catch { e ->
// 全局异常处理,捕获上游异常并发送空列表
println("Error fetching users: ${e.message}")
emit(emptyList())
}
.flowOn(Dispatchers.IO) // 指定在IO线程执行耗时操作
// 场景2:使用Flow进行搜索优化(防抖)
/**
* 搜索用户功能,包含防抖和去重优化
*
* @param query 搜索关键字
* @return 返回搜索结果Flow,具有防抖和去重特性
*/
override fun searchUsers(query: String): Flow<List<User>> = flow {
if (query.isBlank()) {
emit(emptyList())
return@flow
}
// 调用API搜索用户
val response = apiService.searchUsers(query)
emit(response.data)//发送搜索结果
}
.debounce(300) // 防抖,300ms内只执行最后一次搜索请求
.distinctUntilChanged() // 避免重复搜索请求,相同查询不再执行
.flowOn(Dispatchers.IO) //在io线程执行网络请求
// 场景3:实时数据更新
/**
* 根据用户ID获取用户信息
*
* @param id 用户ID
* @return 返回用户信息Flow,数据库变化时自动更新
*/
override fun getUserById(id: Int): Flow<User?> = database.userDao()
.observeUserById(id) // 从数据库观察指定ID的用户,返回Flow<User?>
.map { user ->
// 可以在这里进行数据转换,将用户名首字母大写(示例转换)
user?.copy(name = user.name.replaceFirstChar { it.uppercase() })
}
.onEach { user ->
// 其他操作,比如更新UI状态
println("User updated: ${user?.name}")
}
}
- ViewModel层实现
kotlin
/**
* 用户界面ViewModel类,负责处理用户界面的业务逻辑和状态管理
*
* @param repository 用户数据仓库实例,用于获取用户数据
*/
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
// 状态管理,用于状态内部更新
private val _uiState = MutableStateFlow(UsersUiState())
//状态只读状态流,对外暴露给UI层观察刷新
val uiState: StateFlow<UsersUiState> = _uiState.asStateFlow()
// 搜索查询流,用于管理搜索内容
private val searchQuery = MutableStateFlow("")
init {
//观察用户和搜索结果
observeUsers()
observeSearchResults()
}
/**
* 观察用户数据变化
* 实现加载状态管理、错误处理和数据更新
*/
private fun observeUsers() {
repository.getUsers() //从仓库中获取用户数据flow
.onStart {
//Flow 开始设置加载状态
_uiState.value = _uiState.value.copy(isLoading = true)
}
.onCompletion {
//flow 完成时取消加载状态
_uiState.value = _uiState.value.copy(isLoading = false)
}
.catch { e ->
//捕获异常
_uiState.value = _uiState.value.copy(
error = e.message,
isLoading = false //取消加载状态
)
}
.onEach { users ->
// 处理每个发射的用户列表数据
_uiState.value = _uiState.value.copy(
users = users, //更新用户列表
isLoading = false //取消加载状态
)
}
.launchIn(viewModelScope) //在Viewmodel协程中启动收集
}
/**
* 观察搜索结果变化
* 实现搜索防抖、过滤和结果更新
*/
private fun observeSearchResults() {
searchQuery
.debounce(300) 300ms防抖
.filter { it.length >= 2 } //只处理搜索内容大于2的查询
.flatMapLatest { query -> //关键用法:将每个查询转换为搜索结果Flow
repository.searchUsers(query) // 根据查询获取搜索结果
}
.catch { e ->
println("Search error: ${e.message}")
}
.onEach { searchResults ->
// 处理每个发射的搜索结果, // 更新搜索结果
_uiState.value = _uiState.value.copy(searchResults = searchResults)
}
.launchIn(viewModelScope) // 在ViewModel作用域中启动收集
}
fun onSearchQueryChanged(query: String) {
searchQuery.value = query // 更新搜索查询状态
}
/**
* 刷新用户数据
* 与observeUsers类似,但使用刷新状态而不是加载状态
*/
fun refreshUsers() {
repository.getUsers() // 从仓库获取用户数据Flow
.onStart {
// Flow开始时设置刷新状态
_uiState.value = _uiState.value.copy(isRefreshing = true)
}
.onCompletion {
// Flow完成时取消刷新状态
_uiState.value = _uiState.value.copy(isRefreshing = false)
}
.catch { e ->
// 捕获并处理刷新异常
_uiState.value = _uiState.value.copy(
error = e.message,
isRefreshing = false // 取消刷新状态
)
}
.onEach { users ->
// 处理每个发射的用户列表数据
_uiState.value = _uiState.value.copy(
users = users, // 更新用户列表
isRefreshing = false // 取消刷新状态
)
}
.launchIn(viewModelScope)
}
}
/**
* 用户界面状态数据类,用于管理用户相关界面的所有状态
*
* @property users 用户列表数据
* @property searchResults 搜索结果列表
* @property isLoading 是否正在加载数据
* @property isRefreshing 是否正在刷新数据
* @property error 错误信息,无错误时为null
*/
data class UsersUiState(
val users: List<User> = emptyList(),
val searchResults: List<User> = emptyList(),
val isLoading: Boolean = false,
val isRefreshing: Boolean = false,
val error: String? = null
)
十、全局事件总线
注:要是想运用到实际项目中,请完善并做好测试工作。
- 定义消息类型
kotlin
/**
* 应用事件密封类
*/
sealed class AppEvent {
/**
* 用户登录事件
* @param userId 登录用户的唯一标识符
*/
data class UserLogin(val userId: String) : AppEvent()
/**
* 用户登出事件
*/
object UserLogout : AppEvent()
/**
* 网络状态变化事件
* @param isConnected 表示当前网络是否连接的状态
*/
data class NetworkStatus(val isConnected: Boolean) : AppEvent()
}
- 创建事件总线
kotlin
/**
* FlowEventBus 是一个基于 SharedFlow 实现的事件总线对象,用于在应用内发布和订阅事件。
*/
object FlowEventBus {
// 配置 SharedFlow,replay=0 表示不缓存历史消息,订阅者只收到新消息
private val _events = MutableSharedFlow<AppEvent>(replay = 0, extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST)
val events = _events.asSharedFlow() // 暴露只读 SharedFlow,防止外部发送数据
/**
* 发布事件到事件总线
*
* @param event 要发布的应用事件对象
*/
suspend fun post(event: AppEvent) {
_events.emit(event)
}
/**
* 发布用户登录事件的静态方法,可在 Java 代码中直接调用
* 该方法会在 IO 线程中异步发布事件
*
* @param userId 用户唯一标识符
*/
@JvmStatic
fun postLoginEvent(userId: String) {
CoroutineScope(Dispatchers.IO).launch {
post(AppEvent.UserLogin(userId))
}
}
}
- 在Activity、Fragment中接收数据。当然你也可以在Viewmodel中接收,这边不再举例
kotlin
lifecycleScope.launch {
FlowEventBus.events.collect() { event ->
when(event) {
is AppEvent.UserLogin -> {
showToast(event.userId)
}
is AppEvent.NetworkStatus -> {
showToast("网络变化)
}
is AppEvent.UserLogout -> {
showToast("登出")
}
}
}
}
- 发送数据
kotlin
FlowEventBus.postLoginEvent("user123")
总结
- 创建:flowOf、asFlow(无需协程),flow {}、channelFlow(需协程)。
- 操作:map、filter、reduce 等支持异步处理。
- 收集:需要协程,Android 用 lifecycleScope/repeatOnLifecycle。
- 冷流特性:每次收集从头开始,收集结束上游停止。
- 转换:冷流可转为热流(shareIn/stateIn)
- 场景:数据库查询、API 分页、UI 事件处理。