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 保证安全。
相关推荐
召钱熏7 小时前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
杉氧7 小时前
深度解析:Jetpack Compose 核心架构与底层原理 —— 十年安卓老兵的“破茧重生”
android·架构·android jetpack
通玄8 小时前
Jetpack Compose 入门系列(七):ViewModel 与界面状态管理
android
落魄Android在线炒饭8 小时前
Android Framework 开发技巧:android.jar 生成与系统快速编译验证
android
如此风景9 小时前
Kotlin Flow操作符学习
android·kotlin
plainGeekDev9 小时前
GreenDAO → Room
android·java·kotlin
weiggle10 小时前
第八篇:ViewModel + Compose——生产级状态管理实践
android
恋猫de小郭15 小时前
Amper 正式转正 Kotlin Toolchain ,Gradle 未来何去何从
android·前端·flutter
plainGeekDev16 小时前
ButterKnife → ViewBinding
android·java·kotlin
成都大菠萝1 天前
Android Car CarProperty 车辆信号链路
android