Kotlin Flow 流全面详解
一、Flow 简介
1. 什么是 Flow
Flow(流)是 Kotlin 协程生态中用于处理异步数据流的组件,由 JetBrains 官方推出,旨在简化异步场景下的数据传递与处理逻辑。它具备以下核心特性:
-
冷流特性:默认是 “冷” 的,即只有当存在终端操作(如
collect)时,数据流才会开始发射(发送)数据;没有订阅者时,不会执行任何计算或数据生成操作。 -
协程兼容:完全基于协程构建,支持挂起函数(
suspend),可无缝结合协程的生命周期管理,避免回调地狱。 -
数据流特性:支持数据的连续发射(如多次发送 int、String 等类型数据),并可通过操作符对数据进行过滤、转换、组合等处理。
2. Flow 与其他类似组件的区别
| 组件 | 核心差异 | 适用场景 |
|---|---|---|
| Flow | 冷流、协程兼容、支持背压、数据流连续发射 | 异步数据流处理(如网络请求 + 本地缓存、实时数据更新) |
| Sequence | 同步数据流、无协程支持、不支持挂起函数 | 同步场景下的集合数据迭代 |
| Channel | 热流、支持点对点通信、无背压(需手动处理) | 协程间的消息传递 |
| LiveData | 热流、Android 生命周期感知、仅支持单次数据发射(数据覆盖)、无协程原生支持 | Android UI 状态更新(已逐步被 StateFlow 替代) |
二、Flow 的基础结构
Flow 的核心操作分为三个阶段:
-
创建流(Flow Builder):通过 Flow 构建器生成数据流(如
flowOf、asFlow、flow)。 -
中间操作(Intermediate Operators):对数据流进行处理(如过滤、转换、限流),返回新的 Flow,操作是 “惰性” 的(不触发数据发射)。
-
终端操作(Terminal Operators):触发数据流的发射与收集(如
collect、toList),是 “饥饿” 的(触发整个流的执行)。
// Flow三阶段示例
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
// 1. 创建流(Flow Builder)
val numberFlow = flow {
for (i in 1..3) {
delay(1000) // 模拟异步操作(如网络请求、数据库读取)
emit(i) // 发射数据(suspend函数,可挂起)
}
}
// 2. 中间操作(过滤偶数、转换为字符串)
val processedFlow = numberFlow
.filter { it % 2 == 1 } // 过滤奇数
.map { "奇数: $it" } // 转换为字符串
// 3. 终端操作(触发收集)
processedFlow.collect { value ->
println("收集到数据:$value")
}
}
// 输出:
// (1秒后)收集到数据:奇数: 1
// (2秒后)收集到数据:奇数: 3
三、Flow 的创建方式(Flow Builders)
Kotlin 提供了多种构建 Flow 的方式,覆盖不同场景:
1. flow 构建器(最常用)
通过 lambda 表达式自定义数据发射逻辑,支持挂起函数(如delay、网络请求),是最灵活的创建方式。
// 模拟网络请求获取用户列表
fun getUserFlow(): Flow<User> = flow {
// 模拟网络延迟
delay(1500)
// 发射多个用户数据
emit(User(id = 1, name = "Alice"))
delay(1000)
emit(User(id = 2, name = "Bob"))
}
data class User(val id: Int, val name: String)
2. flowOf 构建器
用于发射固定数量的已知数据,适用于静态数据场景。
// 发射3个字符串数据
val stringFlow = flowOf("Apple", "Banana", "Cherry")
// 终端操作:收集数据
runBlocking {
stringFlow.collect { println(it) } 
// 输出:Apple、Banana、Cherry(无延迟,同步发射)
}
3. asFlow 扩展函数
将集合(List/Set)、序列(Sequence)、范围(Range) 转换为 Flow,适用于已有数据集合的场景。
// 1. 范围转Flow
val rangeFlow = (1..5).asFlow()
// 2. 列表转Flow
val listFlow = listOf("Kotlin", "Java", "Swift").asFlow()
// 3. 序列转Flow
val sequenceFlow = sequence { yield(10); yield(20) }.asFlow()
runBlocking {
rangeFlow.collect { println(it) } // 输出:1、2、3、4、5
}
4. 其他构建器
-
emptyFlow():创建空流(不发射任何数据),适用于默认返回值场景。 -
channelFlow:基于 Channel 构建 Flow,支持多协程并发发射数据(后续 “热流” 部分详解)。 -
callbackFlow:将回调接口(如 Java 回调)转换为 Flow,解决回调地狱问题(如 Android 的 LocationCallback)。
// callbackFlow示例:将回调转换为Flow
fun getLocationFlow(): Flow<Location> = callbackFlow {
val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
// 向Flow发射位置数据
trySend(result.lastLocation) // 非挂起函数,线程安全
}
}
// 注册回调
locationManager.requestLocationUpdates(locationCallback)
// 关闭Flow时取消回调(避免内存泄漏)
awaitClose {
locationManager.removeUpdates(locationCallback)
}
}
四、Flow 的操作符
Flow 提供了丰富的操作符,用于数据处理、生命周期控制、背压管理等,分为以下几类:
1. 数据转换操作符
对发射的数据进行格式转换或结构调整。
| 操作符 | 功能描述 | 示例代码 |
|---|---|---|
map | 将数据转换为另一种类型 | flowOf(1,2,3).map { it * 2 } → 发射 2、4、6 |
flatMapConcat | 转换为新 Flow 并顺序执行(前一个 Flow 完成再执行下一个) | flowOf(1,2).flatMapConcat { flow { emit(it); delay(1000) } } → 1(延迟 1 秒)、2(延迟 1 秒) |
flatMapMerge | 转换为新 Flow 并并发执行(同时执行多个 Flow) | flowOf(1,2).flatMapMerge { flow { emit(it); delay(1000) } } → 1(1 秒后)、2(1 秒后,共 1 秒) |
flatMapLatest | 转换为新 Flow,若前一个 Flow 未完成则取消,只保留最新 Flow | flowOf(1,2).flatMapLatest { flow { delay(500); emit(it) } } → 仅发射 2 |
2. 过滤操作符
筛选符合条件的数据,剔除无效数据。
| 操作符 | 功能描述 | 示例代码 |
|---|---|---|
filter | 保留满足条件的数据 | flowOf(1,2,3,4).filter { it % 2 == 0 } → 发射 2、4 |
distinctUntilChanged | 仅保留与前一个数据不同的值(去重连续重复数据) | flowOf(1,1,2,2,3).distinctUntilChanged() → 发射 1、2、3 |
take | 仅保留前 N 个数据,之后关闭流 | flowOf(1,2,3,4).take(2) → 发射 1、2 |
drop | 跳过前 N 个数据,保留后续数据 | flowOf(1,2,3,4).drop(2) → 发射 3、4 |
3. 背压处理操作符
背压(Backpressure)指数据发射速度 > 数据收集速度时的不平衡问题,Flow 通过以下操作符解决:
| 操作符 | 功能描述 | 适用场景 |
|---|---|---|
buffer | 为流设置缓冲区,暂存发射的数据(默认容量 64) | 发射速度略快于收集速度 |
conflate | 忽略中间数据,仅保留最新数据(“合并” 数据) | 发射速度远快于收集速度(如实时日志) |
collectLatest | 取消前一次未完成的收集,仅处理最新数据 | 仅需最新结果(如搜索输入联想) |
// 背压示例:conflate(合并数据)
runBlocking {
flow {
for (i in 1..5) {
delay(100) // 发射速度快(100ms/次)
emit(i)
}
}
.conflate() // 合并中间数据
.collect {
delay(500) // 收集速度慢(500ms/次)
println("收集到:$it")
}
// 输出:1、3、5(跳过2、4,因收集时已产生新数据)
}
4. 终端操作符
触发 Flow 的数据发射,是 Flow 执行的 “开关”,必须调用终端操作 Flow 才会生效。
| 操作符 | 功能描述 | 示例代码 |
|---|---|---|
collect | 最常用,逐个收集数据(挂起函数) | flowOf(1,2).collect { println(it) } |
toList/toSet | 将数据收集为 List/Set(挂起函数) | val list = flowOf(1,2).toList() → [1,2] |
first/last | 仅获取第一个 / 最后一个数据,之后关闭流 | val first = flowOf(1,2,3).first() → 1 |
count | 统计数据总数 | val count = flowOf(1,2,3).count() → 3 |
onCompletion | 流完成(正常 / 异常)时执行回调 | flowOf(1).onCompletion { println("流结束") }.collect { } → 输出 “流结束” |
五、热流:StateFlow 与 SharedFlow
默认的 Flow 是冷流(一对一订阅,无订阅者不发射),而 StateFlow 和 SharedFlow 是热流(一对多订阅,有订阅者即可能发射数据,数据可被多个订阅者共享),主要用于状态管理和事件分发。
1. StateFlow(状态流)
核心特性
-
状态持有:持有一个 “当前状态值”,新订阅者会立即收到当前最新值(粘性)。
-
值更新触发:仅当新值与旧值不同时,才会通知订阅者(避免无效更新)。
-
生命周期感知:建议结合
repeatOnLifecycle使用(Android),避免内存泄漏。
使用步骤
-
定义
MutableStateFlow(可修改状态,内部使用)和StateFlow(只读状态,对外暴露)。 -
通过
value属性更新状态。 -
订阅
StateFlow获取状态变化。
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class CounterViewModel {
// 1. 内部可修改的MutableStateFlow(初始值0)
private val _countState = MutableStateFlow(0)
// 2. 对外暴露的只读StateFlow
val countState: StateFlow<Int> = _countState.asStateFlow()
// 更新状态的方法
fun increment() {
_countState.value += 1 // 仅当新值≠旧值时触发通知
}
// 模拟异步更新状态
suspend fun asyncIncrement() {
delay(1000)
_countState.value += 1
}
}
// 测试StateFlow
fun main() = runBlocking {
val viewModel = CounterViewModel()
// 订阅者1
launch {
viewModel.countState.collect { count ->
println("订阅者1:当前计数=$count")
}
}
// 订阅者2(延迟1秒订阅,仍能收到最新值)
delay(1500)
launch {
viewModel.countState.collect { count ->
println("订阅者2:当前计数=$count")
}
}
// 更新状态
viewModel.increment()
delay(500)
viewModel.asyncIncrement()
}
// 输出:
// 订阅者1:当前计数=0(初始值)
// 订阅者1:当前计数=1(increment触发)
// 订阅者2:当前计数=1(延迟订阅,收到最新值)
// 订阅者1:当前计数=2(asyncIncrement触发)
// 订阅者2:当前计数=2(收到更新)
2. SharedFlow(共享流)
核心特性
-
事件分发:用于发送 “一次性事件”(如弹窗提示、导航指令),默认不持有状态(非粘性,新订阅者不会收到历史事件)。
-
可配置参数:支持配置
replay(重放历史事件数)、extraBufferCapacity(额外缓冲区容量)、onBufferOverflow(缓冲区满时的策略)。 -
手动触发:通过
emit或tryEmit发射事件,无默认值。
使用步骤
-
定义
MutableSharedFlow(内部可发射)和SharedFlow(对外只读)。 -
配置参数(如
replay = 0表示不重放历史事件)。 -
发射事件与订阅事件。
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class EventViewModel {
// 1. 内部可发射的MutableSharedFlow(replay=0:不重放历史事件)
private val _eventFlow = MutableSharedFlow<String>(
replay = 0,
extraBufferCapacity = 1, // 额外缓冲区容量(避免发射时无订阅者导致事件丢失)
onBufferOverflow = MutableSharedFlow.OnBufferOverflow.DROP_OLDEST // 缓冲区满时丢弃旧事件
)
// 2. 对外暴露的只读SharedFlow
val eventFlow: SharedFlow<String> = _eventFlow.asSharedFlow()
// 发射事件
suspend fun sendEvent(message: String) {
_eventFlow.emit(message) // 挂起函数,缓冲区满时挂起
// _eventFlow.tryEmit(message) // 非挂起函数,缓冲区满时返回false
}
}
// 测试SharedFlow
fun main() = runBlocking {
val viewModel = EventViewModel()
// 订阅者1(立即订阅)
launch {
viewModel.eventFlow.collect { event ->
println("订阅者1收到事件:$event")
}
}
// 发射事件(订阅者1能收到)
viewModel.sendEvent("弹窗提示:登录成功")
delay(1000)
// 订阅者2(延迟订阅,不会收到之前的事件)
launch {
viewModel.eventFlow.collect { event ->
println("订阅者2收到事件:$event")
}
}
// 发射事件(订阅者1和2都能收到)
viewModel.sendEvent("导航至首页")
}
// 输出:
// 订阅者1收到事件:弹窗提示:登录成功
// 订阅者1收到事件:导航至首页
// 订阅者2收到事件:导航至首页
3. StateFlow 与 SharedFlow 的区别
| 特性 | StateFlow | SharedFlow |
|---|---|---|
| 状态持有 | 持有当前最新值(粘性) | 不持有状态(默认非粘性) |
| 新订阅者通知 | 立即收到最新值 | 仅收到订阅后的新事件(默认) |
| 数据更新触发 | 新值≠旧值时触发 | 调用emit/tryEmit时触发 |
| 适用场景 | 状态管理(如计数器、用户信息) | 事件分发(如弹窗、导航、通知) |
六、Flow 的异常处理
Flow 的异常处理需通过catch操作符(捕获上游异常)和onCompletion(处理流结束时的异常),避免崩溃扩散。
1. catch 操作符
捕获上游流(当前catch之前的操作) 的异常,并可发射备用数据或重新抛出异常。
runBlocking {
flow {
emit(1)
throw IllegalArgumentException("数据异常") // 抛出异常
emit(2) // 不会执行
}
.catch { e ->
println("捕获异常:${e.message}")
emit(-1) // 发射备用数据
}
.collect { println("收集到:$it") }
}
// 输出:
// 收集到:1
// 捕获异常:数据异常
// 收集到:-1
2. onCompletion 操作符
在流正常结束或异常结束时执行回调,可通过cause参数判断是否发生异常。
runBlocking {
flow {
emit(1)
throw RuntimeException("流执行失败")
}
.onCompletion { cause ->
if (cause != null) {
println("流异常结束:\${cause.message}")
} else {
println("流正常结束")
}
}
.catch { e -> println("捕获异常:\${e.message}") }
.collect { println(it) }
}
// 输出:
// 收集到:1
// 捕获异常:流执行失败
// 流异常结束:流执行失败
七、Flow 的上下文切换
Flow 的上下文(CoroutineContext)分为发射上下文(数据发射所在的线程)和收集上下文(数据收集所在的线程),通过flowOn操作符控制发射上下文。
关键规则
-
flowOn仅影响上游流:flowOn指定的上下文仅对其之前的流操作生效(即数据发射阶段)。 -
收集上下文由协程决定:
collect所在的协程上下文(如Dispatchers.Main、Dispatchers.IO)即为收集上下文,flowOn无法修改。
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.runBlocking
fun main() = runBlocking(Dispatchers.Main) { // 收集上下文:Main线程
flow {
println("发射数据线程:${Thread.currentThread().name}") // IO线程
emit(1)
}
.flowOn(Dispatchers.IO) // 发射上下文:IO线程(仅影响上游)
.collect {
println("收集数据线程:\${Thread.currentThread().name}") // Main线程
}
}
// 输出:
// 发射数据线程:DefaultDispatcher-worker-1(IO线程)
// 收集数据线程:main(Main线程)
八、Flow 在 Android 中的实践
在 Android 开发中,Flow 常与 Jetpack 组件(ViewModel、Lifecycle、Room)结合,用于数据层到 UI 层的通信。
示例:Room 数据库 + ViewModel+Flow
- Room Dao 层:返回 Flow(Room 自动支持 Flow,数据变化时自动发射新数据)
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAllUsers(): Flow<List<User>> // Room返回Flow,数据更新时自动通知
@Insert
suspend fun insertUser(user: User)
}
- ViewModel 层:持有 Flow,对外暴露只读流
class UserViewModel(
private val userDao: UserDao
) : ViewModel() {
// 对外暴露只读Flow(结合Dispatchers.IO发射数据)
val allUsers: Flow<List<User>> = userDao.getAllUsers()
.flowOn(Dispatchers.IO)
.catch { e ->
// 异常处理
_errorEvent.emit("获取用户失败:${e.message}")
}
// 插入用户(挂起函数,在ViewModelScope中执行)
fun insertUser(user: User) {
viewModelScope.launch {
userDao.insertUser(user)
}
}
// 错误事件(SharedFlow)
private val _errorEvent = MutableSharedFlow<String>()
val errorEvent: SharedFlow<String> = _errorEvent.asSharedFlow()
}
- UI 层(Activity/Fragment):订阅 Flow,结合生命周期避免内存泄漏
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 结合repeatOnLifecycle(Lifecycle 2.4.0+),在STARTED状态订阅,STOPPED状态取消
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// 订阅用户数据,更新UI
viewModel.allUsers.collect { users ->
binding.recyclerView.adapter = UserAdapter(users)
}
}
}
// 订阅错误事件
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.errorEvent.collect { errorMsg ->
Toast.makeText(this@UserActivity, errorMsg, Toast.LENGTH_SHORT).show()
}
}
}
}
}
九、Flow 的注意事项
-
冷流特性:无终端操作(如
collect)时,Flow 不会执行任何逻辑,避免无效计算。 -
上下文切换:
flowOn仅修改发射上下文,收集上下文由collect所在协程决定,避免多次切换上下文导致性能问题。 -
内存泄漏:Android 中需结合
lifecycleScope和repeatOnLifecycle,避免 Flow 订阅者持有 Activity/Fragment 引用导致内存泄漏。 -
StateFlow 粘性问题:新订阅者会收到最新值,若需 “一次性事件”(非粘性),应使用 SharedFlow(
replay=0)。 -
背压处理:发射速度远快于收集速度时,需使用
conflate或collectLatest,避免缓冲区溢出。