1. Flow 基础与 collect 生命周期

1.1 Flow 的创建方式

Kotlin Flow 提供了三种核心创建方式,每种方式对应不同的数据生产场景:

创建方式语法适用场景
flow { }flow { emit(data) }通用异步数据流,可调用挂起函数
flowOf()flowOf(a, b, c)固定已知值的发射
asFlow()list.asFlow()集合/Sequence 转 Flow
// flow { } —— 最常用的冷流创建方式
val userFlow: Flow<User> = flow {
    val users = userRepository.fetchUsers()  // 挂起函数
    for (user in users) {
        emit(user)  // 发射数据到下游
    }
}

// flowOf() —— 静态数据
val numberFlow = flowOf(1, 2, 3, 4, 5)

// asFlow() —— 集合转换
val nameFlow = listOf("Alice", "Bob", "Charlie").asFlow()

1.2 collect 挂起原理

collect 是冷流的终端操作,其本质是一个挂起函数。调用 collect 时,协程会被挂起,直到 Flow 生产者完成所有数据发射或协程被取消。每次 collect 都会独立触发生产者的执行,这就是冷流“每次订阅都重新开始”的核心机制。

// collect 是挂起函数,调用它的协程会被阻塞(挂起)直到 Flow 结束
lifecycleScope.launch {
    flow {
        emit("A")
        delay(100)  // 挂起 100ms
        emit("B")
    }.collect { value ->
        // 此 lambda 在每次 emit 时被调用
        Log.d("Flow", "收到: $value")
    }
    // 代码执行到这里时,Flow 已经结束
    Log.d("Flow", "collect 完成,协程恢复")
}
⚠️ 常见误解:不要认为 collect 是“监听”,它实际上是“等待并消费”。冷流在 collect 调用时才会真正激活生产者代码,每次 collect 都对应一次全新的数据生产周期。

1.3 冷流与热流的本质区别

冷流和热流是 Flow 体系中最重要的概念区分,直接影响 collect 的耗电行为:

特性冷流 (Cold Flow)热流 (Hot Flow)
数据生产时机每次 collect 时重新执行生产者独立于 collect 持续生产
订阅者数量一对一(每个 collect 独立)一对多(共享数据流)
是否缓存数据无缓存可通过 replay 缓存最近值
典型类型flow { }、flowOf()StateFlow、SharedFlow
collect 副作用触发重复计算(耗电风险)不触发生产,仅接收

💡 性能启示:冷流每次 collect 都会重新执行生产者,如果生产者包含网络请求、数据库查询或传感器读数,就会产生不必要的电量消耗。因此,在 UI 层使用 StateFlow 替换冷流,是减少耗电的第一道防线。

1.4 冷流与热流结构对比图

下面这张 SVG 图清晰地展示了冷流和热流在订阅关系上的核心差异:

冷流 vs 热流:订阅模型差异 冷流生产者 (flow { }) collect #1 → 重新执行生产者 collect #2 → 重新执行生产者 ⚠️ 每次 collect 都重复计算 热流生产者 (StateFlow) collect #1 → 接收已有值 collect #2 → 接收已有值 ✅ 不触发生产,仅消费 箭头:订阅关系

1.5 调试 collect 回调的基本手段

在分析和优化耗电问题前,需要先能观察到 collect 的调用次数和执行上下文:

// 调试手段1:使用 onStart / onCompletion 追踪生命周期
flow {
    emit(1)
    delay(50)
    emit(2)
}.onStart {
    Log.d("FlowDebug", "collect 开始,当前线程: ${Thread.currentThread().name}")
}.onCompletion {
    Log.d("FlowDebug", "collect 结束")
}.collect {
    Log.d("FlowDebug", "收到值: $it")
}

// 调试手段2:通过协程上下文判断 collect 所在作用域
lifecycleScope.launch(Dispatchers.IO) {
    // collect 在 IO 线程执行,但依然会挂起当前协程
    someFlow.collect { /* ... */ }
}

1.6 常见错误:重复 collect 导致多倍耗电

典型反模式:在 LaunchedEffect 内多次调用 collect,或错误地使用 repeatOnLifecycle 包裹了不该重复激活的冷流。

// ❌ 错误:每次 recomposition 都创建新的 collect
@Composable
fun BadScreen() {
    val data = remember { MutableStateFlow("") }
    LaunchedEffect(Unit) {
        // 注意:如果这里使用冷流,每次 collect 都会触发网络请求
        repository.fetchDataFlow().collect { data.value = it }
    }
}

// ✅ 改进:使用 StateFlow 共享数据流,避免重复生产
@Composable
fun GoodScreen(viewModel: MyViewModel) {
    val data by viewModel.uiState.collectAsState()
    // collectAsState 内部使用 StateFlow,不会重复触发生产者
}

1.7 性能要点小结

  • 冷流:适合单次操作或不确定是否会被消费的场景;每次 collect 都有完整的数据生产开销。
  • 热流:适合 UI 层持续观察状态;collect 只消费已有数据,不触发额外计算。
  • 选择策略:数据层返回冷流(保留惰性),UI 层通过 stateIn/shareIn 转为热流,是减少不必要的 collect 耗电的标准做法。