Kotlin Flow 快速入门

如果你熟悉 RxJava,那么 Kotlin Flow 就是 Kotlin 协程世界的 RxJava;如果你不熟悉,没关系,你可以把 Flow 想象成一条可以暂停、恢复的"数据流水线"


1. 为什么需要 Flow?

在 Flow 出现之前,我们处理多个连续数据(比如每秒更新一次的倒计时,或者数据库的实时变化)通常很麻烦:

  • List/Sequence: 只能一次性返回所有数据,不能异步返回(会阻塞)。
  • LiveData: 只能在主线程运行,操作符支持有限,且主要用于 UI 层。
  • RxJava: 功能强大但学习曲线陡峭,且容易造成"回调地狱"。

Flow 是基于 Kotlin 协程 构建的,它天生支持挂起(Suspend),这意味着它可以在不阻塞线程的情况下,异步地生成和消费数据。


2. 流水线三部曲

使用 Flow 就像搭建一条工厂流水线:

  1. 上游 (Upstream) : 生产者,负责制造数据(发射 emit)。
  2. 中游 (Intermediaries) : 工人,负责加工数据(操作符 map, filter 等)。
  3. 下游 (Downstream) : 消费者,负责接收成品(收集 collect)。

2.1 最简单的例子

Kotlin 复制代码
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    // 1. 上游:创建一个 Flow
    flow {
        println("开始生产...")
        emit(1) // 发射数据
        delay(100) // 模拟耗时,不会阻塞线程
        emit(2)
        emit(3)
    }
    // 2. 中游:操作符处理
    .filter { it % 2 != 0 } // 只保留奇数
    .map { "数字: $it" }     // 转换成字符串
    // 3. 下游:收集数据(末端操作符)
    .collect { value ->
        println("收到: $value")
    }
}

输出:

Kotlin 复制代码
开始生产...
收到: 数字: 1
收到: 数字: 3

3. 冷流 vs 热流 (Cold vs Hot)

这是面试和实战中最关键的概念。

3.1 普通 Flow 是"冷"的 (Cold)

特点 :只有当你调用 collect() 时,上游的代码才会执行。如果不收集,它就什么都不做。
比喻:像一张 CD 唱片。你不播放(collect),音乐就不会响;你每播放一次,音乐就从头开始放一遍。

3.2 StateFlow / SharedFlow 是"热"的 (Hot)

特点 :无论有没有人收集,它都在那里。通常用于状态管理
比喻:像直播电台。不管有没有听众,电台都在广播;你晚进来了,就只能听到之后的内容(或者最新的那一条)。

StateFlow (状态流)
  • 用途 :专门用于替代 LiveData,存储 UI 状态(如 UiState)。
  • 特性粘性 (Sticky)。新订阅者会立即收到当前最新的值。
  • 去重:如果新设置的值和旧值一样,不会触发更新。
Kotlin 复制代码
// ViewModel 中
private val _uiState = MutableStateFlow(LoginState()) // 必须有初始值
val uiState = _uiState.asStateFlow()

// 更新状态
_uiState.value = newState 
// 或者
_uiState.update { it.copy(isLoading = true) }
SharedFlow (共享流)
  • 用途:用于一次性事件(如 Toast、导航、SnackBar)。
  • 特性 :默认非粘性。事件发出去,如果当时没人在听,这事就过去了。
Kotlin 复制代码
// ViewModel 中
private val _events = MutableSharedFlow<String>()
val events = _events.asSharedFlow()

// 发送事件
viewModelScope.launch {
    _events.emit("登录成功")
}

4. 常用操作符实战

Flow 的强大在于丰富的操作符。

4.1 线程切换:flowOn

RxJava 里的 subscribeOn,在 Flow 里极其简单。

Kotlin 复制代码
flow {
    // 这里在 IO 线程执行(如下载文件)
    emit(downloadFile()) 
}
.flowOn(Dispatchers.IO) // 指定上游在 IO 线程
.collect { 
    // 这里在主线程执行(如果是在 UI 作用域启动的)
    updateUi(it) 
}

4.2 异常捕获:catch

不要用 try-catch 包裹 collect,而应该使用声明式的 catch 操作符。

Kotlin 复制代码
flow {
    emit(1)
    throw RuntimeException("出错啦")
}
.catch { e -> 
    emit(-1) // 发生错误时发射一个兜底值
    println("捕获到异常: ${e.message}") 
}
.collect { println(it) }

4.3 UI 防抖:debounce

防止用户疯狂点击搜索按钮,造成网络拥堵。

Kotlin 复制代码
searchQueryFlow
    .debounce(300) // 300ms 内没有新输入,才发送请求
    .flatMapLatest { query -> 
        // 自动取消旧的搜索请求,只处理最新的
        api.search(query) 
    }
    .collect { results -> show(results) }

5. Android / Compose 中的最佳实践

在 Android UI 中收集 Flow 时,必须注意生命周期安全。否则,当应用退到后台时,Flow 还在后台一直更新 UI,会导致崩溃或耗电。

在 Compose 中(推荐)

使用 collectAsStateWithLifecycle(需要引入 androidx.lifecycle:lifecycle-runtime-compose 库)。

Kotlin 复制代码
@Composable
fun UserScreen(viewModel: UserViewModel) {
    // 自动感知生命周期:APP 切后台时暂停收集,切回来恢复
    val state by viewModel.uiState.collectAsStateWithLifecycle()

    if (state.isLoading) {
        LoadingView()
    } else {
        ContentView(state.data)
    }
}

在传统 View 系统中

使用 repeatOnLifecycle

Kotlin 复制代码
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { state ->
            // 更新 View
        }
    }
}

总结

  1. Flow 是处理异步数据流的利器,支持挂起函数。
  2. Cold Flow (普通 Flow)像 CD,按需启动;Hot Flow(StateFlow)像直播,状态常驻。
  3. ViewModel 中使用 StateFlow 管理状态,SharedFlow 管理事件。
  4. UI 层 收集时务必使用 collectAsStateWithLifecyclerepeatOnLifecycle 保证安全。
相关推荐
嗷o嗷o15 分钟前
Android BLE 里,MTU、分包和长数据发送到底该怎么处理
android
胡致和30 分钟前
配置变更后,弹窗为什么飞到了最左边?
kotlin
Gary Studio2 小时前
Android AIDL HAL工程结构示例
android
y = xⁿ2 小时前
MySQL八股知识合集
android·mysql·adb
andr_gale3 小时前
04_rc文件语法规则
android·framework·aosp
祖国的好青年4 小时前
VS Code 搭建 React Native 开发环境(Windows 实战指南)
android·windows·react native·react.js
黄林晴4 小时前
警惕!AGP 9.2 别只改版本号,R8 规则与构建链路全线收紧
android·gradle
小米渣的逆袭5 小时前
Android ADB 完全使用指南
android·adb
儿歌八万首5 小时前
Jetpack Compose Canvas 进阶:结合 animateFloatAsState 让自定义图形动起来
android·动画·compose