第1章:初识 Kotlin Flow
1.1 Flow 是什么:官方定义与设计哲学
官方定义 :
Kotlin Flow 是 Kotlin 协程库中用于处理异步数据流的 API。它是一个基于协程构建的响应式流处理框架,专为异步、按顺序、连续的数据流设计。Flow 是 Kotlin 标准库的一部分,与协程深度集成,提供了一种声明式、可组合的方式来处理随时间变化的值序列。
设计哲学:
- 声明式编程:使用函数式操作符表达数据转换,而不是命令式控制流
- 结构化并发:与协程作用域深度绑定,自动管理资源生命周期
- 冷流优先:默认采用按需生产、零共享的冷流模型,确保资源安全
- 可组合性:操作符可链式组合,构建复杂的数据处理管道
- 协程原生:无缝集成协程上下文,支持挂起函数和结构化取消
1.2 Flow 的生态位:对比 LiveData、ShareFlow、StateFlow、Channel
| 特性 | Flow (冷流) | SharedFlow (热流) | StateFlow (热流) | LiveData | Channel |
|---|---|---|---|---|---|
| 数据模型 | 连续数据流 | 事件流/广播 | 状态容器 | 生命周期感知数据持有者 | 点对点通信管道 |
| 温度 | 冷流 | 热流 | 热流 | 热流 | 热流 |
| 生命周期 | 依赖收集者 | 独立于收集者 | 独立于收集者 | 绑定观察者生命周期 | 独立于收发者 |
| 数据共享 | 每个收集者独立数据 | 多收集者共享数据 | 多观察者共享状态 | 多观察者共享数据 | 单次消费 |
| 初始值 | 无 | 可选(通过replay) | 必须有 | 可有可无 | 无 |
| 背压处理 | 内置(协程挂起) | 可配置缓冲区策略 | 最新值替换 | 无(主线程安全) | 可配置缓冲区 |
| 适用场景 | 网络请求、数据库查询 | 事件总线、实时数据广播 | UI状态管理 | Android UI状态 | 协程间通信 |
1.3 Flow 的核心价值:为什么 Google 将其作为异步首选?
四大核心价值:
-
协程原生:
- 无缝集成协程作用域,自动处理取消和资源清理
- 支持挂起函数,可直接在流操作中使用异步操作
-
结构化并发安全:
kotlin
scssviewModelScope.launch { repository.dataFlow() .onEach { updateUi(it) } .launchIn(this) // 自动绑定到viewModelScope } -
声明式与可组合性:
kotlin
scsssearchFlow .debounce(300) .filter { it.length > 2 } .distinctUntilChanged() .flatMapLatest { query -> api.search(query).asFlow() } .catch { emit(SearchResult.Error(it)) } -
平台无关性:
- 纯 Kotlin 实现,可在 Android、iOS、后端等多平台使用
- 摆脱 Android 特定框架(如 LiveData)的依赖
1.4 Flow 的三大基石:基于协程、冷流特性、顺序执行
基石一:基于协程
- 所有 Flow 操作都在协程上下文中执行
- 支持挂起函数,可安全执行 I/O 操作
- 自动传播取消信号
基石二:冷流特性(默认)
kotlin
scss
val coldFlow = flow {
println("开始生产数据") // 只在收集时执行
emit(1)
emit(2)
emit(3)
}
// 多次收集会多次执行
coroutineScope.launch { coldFlow.collect() } // 输出:开始生产数据
coroutineScope.launch { coldFlow.collect() } // 再次输出:开始生产数据
基石三:顺序执行
- 每个元素按顺序通过整个处理链
- 操作符按声明顺序执行
- 保证数据处理的一致性
1.5 Flow 的三大定律:上下文保留、异常透明性、数据顺序保证
定律一:上下文保留
Flow 构建器中的代码运行在调用者的上下文中,除非使用 flowOn 显式切换:
kotlin
scss
flow {
// 这里的上下文是调用者的上下文
emit(1)
}.flowOn(Dispatchers.IO) // 切换上游上下文
.collect {
// 这里恢复为原始上下文
}
定律二:异常透明性
异常必须通过 catch 操作符处理,或向上游传播:
kotlin
scss
flow {
throw RuntimeException("错误")
}.catch { cause ->
// 捕获异常并恢复
emit(RecoveryValue)
}.collect()
定律三:数据顺序保证
在没有并发操作符(如 buffer)的情况下,数据按顺序处理:
kotlin
scss
flowOf(1, 2, 3)
.map { it * 2 } // 1→2, 2→4, 3→6
.filter { it > 3 } // 2被过滤, 4通过, 6通过
.collect { print(it) } // 输出:46(顺序保持不变)
第2章:冷与热:Flow 的两种形态
2.1 理解数据流的"温度"
核心差异:
- 冷流:按需生产,每个收集者获得独立的数据流
- 热流:主动生产,多个收集者共享相同的数据源
温度类比:
- 冷流 像"视频点播":每个观众独立观看,从头开始播放
- 热流 像"电视直播":所有观众同时观看相同的内容
2.2 冷流(Cold Flow)本质解析
2.2.1 惰性执行机制
kotlin
scss
val coldFlow = flow {
println("开始生产数据")
for (i in 1..3) {
delay(100)
emit(i)
}
}
// 只有调用 collect 时才开始执行
GlobalScope.launch {
coldFlow.collect { println("收集到: $it") }
}
2.2.2 点对点传输模型
kotlin
scss
val dataFlow = flow {
repeat(3) {
delay(100)
emit(System.currentTimeMillis())
}
}
// 两个收集者获得不同的时间戳序列
coroutineScope {
launch { dataFlow.collect { println("收集者1: $it") } }
launch { dataFlow.collect { println("收集者2: $it") } }
}
2.2.3 生命周期绑定
kotlin
kotlin
fun observeUserData(userId: String): Flow<UserData> = flow {
val connection = openDatabaseConnection()
try {
while (true) {
emit(fetchUserData(userId))
delay(1000)
}
} finally {
connection.close()
}
}
2.3 热流(Hot Flow)特性详解
2.3.1 独立存在性
kotlin
scss
val hotFlow = MutableSharedFlow<Int>()
// 生产者独立运行
GlobalScope.launch {
repeat(10) {
delay(100)
hotFlow.emit(it)
}
}
// 稍后开始收集
delay(300)
hotFlow.asSharedFlow().collect {
println("收集到: $it")
}
2.3.2 广播机制
kotlin
ini
val eventBus = MutableSharedFlow<Event>()
// 发送事件
eventBus.emit(Event("USER_LOGIN"))
// 多个订阅者
val subscriber1 = eventBus.asSharedFlow()
val subscriber2 = eventBus.asSharedFlow()
2.3.3 多订阅者模式
kotlin
kotlin
class SensorManager {
private val _sensorData = MutableSharedFlow<SensorData>()
val sensorData = _sensorData.asSharedFlow()
fun startMonitoring() {
GlobalScope.launch {
while (true) {
_sensorData.emit(readSensor())
delay(100)
}
}
}
}
2.4 Flow 的三元模型
kotlin
scss
// 生产者:数据源
val producer = flow { produceData().forEach { emit(it) } }
// 操作符:数据处理管道
val processor = producer
.filter { it.isValid() }
.map { it.transform() }
// 消费者:数据接收端
processor.collect { updateUI(it) }
2.5 典型场景选择策略
7个典型场景及选择建议:
- UI 状态管理 → StateFlow
- 事件总线 → SharedFlow
- 网络请求 → 普通 Flow(冷流)
- 实时数据 → SharedFlow
- 一次性操作 → 普通 Flow(冷流)
- 多订阅者 → 热流
- 资源敏感 → 冷流
2.6 转换魔法:shareIn/stateIn
2.6.1 基本使用
kotlin
kotlin
fun <T> Flow<T>.shareIn(
scope: CoroutineScope,
started: SharingStarted,
replay: Int = 0
): SharedFlow<T>
fun <T> Flow<T>.stateIn(
scope: CoroutineScope,
started: SharingStarted,
initialValue: T
): StateFlow<T>
2.6.2 配置参数
kotlin
scss
// 1. 立即开始,永不停止
SharingStarted.Eagerly
// 2. 延迟开始,有订阅者时开始,无订阅者时停止
SharingStarted.Lazily
// 3. 有订阅者时开始,订阅者离开后继续运行一段时间
SharingStarted.WhileSubscribed()
// replay 配置
.shareIn(scope, replay = 1) // 新订阅者获得最新值
2.6.3 使用建议
- UI状态 :使用
stateIn,replay=1,SharingStarted.Eagerly - 事件总线 :使用
shareIn,replay=0,SharingStarted.Eagerly - 实时数据 :使用
shareIn,replay=1,SharingStarted.WhileSubscribed - 一次性数据:保持冷流特性
2.7 快速参考表
| 选择标准 | 选择冷流 | 选择热流 |
|---|---|---|
| 数据独立性 | 每个消费者需要独立数据 | 多个消费者共享相同数据 |
| 资源消耗 | 资源敏感,需要及时释放 | 资源可共享,避免重复计算 |
| 数据实时性 | 每次需要最新计算 | 需要持续更新的实时数据 |
| 生命周期 | 需要精确控制开始/结束 | 数据生产独立于消费者 |
| 使用场景 | 网络请求、文件读取、一次性计算 | UI状态、事件总线、实时数据推送 |
| 典型实现 | flow {}, flowOf(), asFlow() | StateFlow, SharedFlow |
| 转换方法 | - | shareIn(), stateIn() |
| 性能特点 | 按需执行,避免浪费 | 共享执行,提高效率 |
| 内存占用 | 每个收集者独立占用 | 共享占用,内存友好 |
| 测试难度 | 相对简单 | 相对复杂 |
总结
Kotlin Flow 提供了完整的异步数据流处理方案,从基础的冷流到高级的热流(SharedFlow、StateFlow),覆盖了从简单的数据转换到复杂的UI状态管理等各种场景。掌握 Flow 的关键在于理解冷流和热流的本质区别,根据实际需求选择合适的类型和配置,并遵循最佳实践来保证性能和安全。
第二部分:Flow 核心操作
第3章:Flow (冷流) - 基础构建与消费
3.1 四大构建器对比表
| 构建器 | 适用场景 | 执行方式 | 内存使用 | 性能特点 |
|---|---|---|---|---|
flow{} |
自定义生产逻辑 | 惰性执行 | 低 | 基础构建,适合大部分场景 |
flowOf() |
固定值序列 | 立即计算 | 低 | 轻量级,适合静态数据 |
asFlow() |
集合转换 | 惰性/立即 | 中 | 依赖原始集合,转换开销小 |
channelFlow{} |
高性能/并发生产 | 立即执行 | 高 | 支持并发emit,适合高吞吐量 |
3.2 callbackFlow:解决回调地狱的终极方案
作用:将传统的基于回调的API转换为Flow,使得异步回调代码能够以响应式流的方式处理。
关键方法:
trySend(value):尝试发送一个值到流,非挂起函数,不会阻塞。close()/close(throwable):关闭流,可选传递异常。awaitClose():挂起函数,等待流被关闭,用于资源清理。
3.2.1 与 Retrofit 集成示例
kotlin
kotlin
// 假设有一个回调式的下载接口
interface Downloader {
fun download(url: String, callback: DownloadCallback)
}
interface DownloadCallback {
fun onProgress(percent: Int)
fun onComplete(file: File)
fun onError(error: Throwable)
}
// 使用callbackFlow包装
fun downloadFileFlow(url: String): Flow<DownloadEvent> = callbackFlow {
val callback = object : DownloadCallback {
override fun onProgress(percent: Int) {
trySend(DownloadEvent.Progress(percent))
}
override fun onComplete(file: File) {
trySend(DownloadEvent.Complete(file))
close()
}
override fun onError(error: Throwable) {
close(error)
}
}
// 开始下载
downloader.download(url, callback)
// 等待流被关闭(即下载完成或出错)
awaitClose {
// 可选:取消下载等清理操作
downloader.cancel(url)
}
}
// 使用
viewModelScope.launch {
downloadFileFlow("http://example.com/file.zip")
.collect { event ->
when (event) {
is DownloadEvent.Progress -> updateProgress(event.percent)
is DownloadEvent.Complete -> showFile(event.file)
}
}
}
3.2.2 与 Location API 集成示例
kotlin
kotlin
// 将位置监听转换为Flow
fun locationFlow(context: Context): Flow<Location> = callbackFlow {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
trySend(location) // 发送新位置
}
// 其他回调方法...
}
// 请求位置更新
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L,
1f,
locationListener
)
// 当流被取消时,移除监听
awaitClose {
locationManager.removeUpdates(locationListener)
}
}
3.2.3 与 Bluetooth 集成示例
kotlin
kotlin
// 扫描蓝牙设备
fun scanBluetoothDevices(): Flow<BluetoothDevice> = callbackFlow {
val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
if (bluetoothAdapter == null) {
close(BluetoothNotAvailableException())
return@callbackFlow
}
val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
trySend(result.device)
}
override fun onScanFailed(errorCode: Int) {
close(ScanFailedException(errorCode))
}
}
// 开始扫描
bluetoothAdapter.bluetoothLeScanner.startScan(scanCallback)
awaitClose {
bluetoothAdapter.bluetoothLeScanner.stopScan(scanCallback)
}
}
3.3 消费模式:collect 家族全解析
3.3.1 collect:基础消费
作用:启动流的收集,对流发出的每个值执行给定的操作。
kotlin
scss
flowOf(1, 2, 3).collect { value ->
println(value)
}
3.3.2 collectLatest:最新值优先
作用:当流发出新值时,如果前一个值的处理还没有完成,则取消前一个值的处理,立即处理新值。
kotlin
scss
// 模拟搜索建议场景:用户连续输入时,只处理最后一次输入
searchQueryFlow
.debounce(300)
.collectLatest { query ->
// 如果前一个搜索还没有完成,会被取消,只执行最新的搜索
fetchSuggestions(query)
}
3.3.3 launchIn:结构化并发消费
作用:在指定的协程作用域中启动流的收集,返回一个Job,可以用于取消收集。
kotlin
scss
// 将流的收集启动在viewModelScope中
flow
.onEach { value -> updateUi(value) }
.launchIn(viewModelScope) // 返回Job,通常不需要保存,因为viewModelScope会管理
// 等效于:
viewModelScope.launch {
flow.collect { value -> updateUi(value) }
}
3.3.4 自定义 Collector 实现
作用:自定义收集器的行为,例如批量处理、延迟处理等。
kotlin
kotlin
// 自定义一个批量收集器
class BatchCollector<T>(
private val batchSize: Int,
private val onBatch: suspend (List<T>) -> Unit
) : FlowCollector<T> {
private val batch = mutableListOf<T>()
override suspend fun emit(value: T) {
batch.add(value)
if (batch.size >= batchSize) {
flush()
}
}
suspend fun flush() {
if (batch.isNotEmpty()) {
onBatch(batch.toList())
batch.clear()
}
}
}
// 使用自定义收集器
flow
.collect(BatchCollector(10) { batch ->
println("处理批量数据: $batch")
})
3.4 错误处理金字塔
3.4.1 catch:局部异常捕获
作用:捕获上游操作中抛出的异常,并允许恢复流。
kotlin
scss
flow {
emit(1)
throw RuntimeException("错误")
}.catch { cause ->
// 捕获异常,并发射一个恢复值
emit(-1)
}.collect { value ->
println(value) // 输出 1, -1
}
3.4.2 retry:智能重试策略
作用:当流发生异常时,根据条件进行重试。
kotlin
scss
flow {
// 模拟不稳定的网络请求
if (Random.nextInt(3) == 0) {
emit("数据")
} else {
throw IOException("网络错误")
}
}.retry(2) { cause ->
// 只在IOException时重试,最多重试2次(一共3次)
cause is IOException
}.catch { cause ->
emit("默认数据")
}.collect { value ->
println(value)
}
3.4.3 onCompletion:最终回调
作用:在流完成(正常或异常)时执行回调,类似于finally块。
kotlin
scss
flow {
emit(1)
emit(2)
}.onCompletion { cause ->
// 如果没有异常,cause为null
if (cause == null) {
println("流正常完成")
} else {
println("流异常完成: $cause")
}
}.collect { value ->
println(value)
}
第4章:Flow 操作符全景概览
4.1 转换系操作符对比表
| 操作符 | 作用 | 输入→输出 | 适用场景 |
|---|---|---|---|
| map | 一对一转换 | 1个值 → 1个值 | 简单类型转换、属性提取 |
| transform | 灵活转换 | 1个值 → 0~N个值 | 条件发射、展开数据 |
| scan | 累积计算 | 流 → 所有中间结果 | 实时统计、进度计算 |
| mapLatest | 转换最新值 | 最新值 → 转换结果 | 取消过时的异步转换 |
简化示例:
kotlin
scss
// 1. map - 简单转换
flowOf(1, 2, 3).map { it * 2 } // 2, 4, 6
// 2. transform - 灵活转换
flowOf(1, 2).transform {
emit("值: $it")
emit("平方: ${it * it}")
} // "值: 1", "平方: 1", "值: 2", "平方: 4"
// 3. scan - 累积计算
flowOf(1, 2, 3).scan(0) { acc, v -> acc + v }
// 0, 1, 3, 6
// 4. mapLatest - 只转换最新值
searchFlow.mapLatest { query ->
api.search(query) // 新查询到来时取消之前的搜索
}
4.2 过滤系操作符对比表
| 操作符 | 作用 | 效果 | 适用场景 |
|---|---|---|---|
| filter | 条件过滤 | 保留符合条件的值 | 数据筛选 |
| take | 取前N个 | 只取前N个值后完成 | 限制数据量 |
| drop | 跳过前N个 | 跳过前N个值 | 忽略初始数据 |
| distinctUntilChanged | 去重 | 过滤连续重复值 | UI状态更新 |
| debounce | 防抖 | 稳定期后取最后一个值 | 搜索框输入 |
| sample | 采样 | 定期取最新值 | 高频数据降频 |
简化示例:
kotlin
scss
// 1. filter - 条件过滤
flowOf(1, 2, 3, 4).filter { it % 2 == 0 } // 2, 4
// 2. take - 取前N个
flowOf(1, 2, 3, 4).take(2) // 1, 2
// 3. drop - 跳过前N个
flowOf(1, 2, 3, 4).drop(2) // 3, 4
// 4. distinctUntilChanged - 去重
flowOf(1, 1, 2, 2, 1, 3)
.distinctUntilChanged() // 1, 2, 1, 3
// 5. debounce - 防抖(300ms内只取最后的值)
userInputFlow.debounce(300)
// 6. sample - 采样(每1秒取一个值)
sensorFlow.sample(1000)
4.3 组合系操作符对比表
| 操作符 | 作用 | 组合方式 | 适用场景 |
|---|---|---|---|
| zip | 一对一配对 | 等待两个值都到达 | 合并相关数据 |
| combine | 动态组合 | 任一更新就重新组合 | UI状态合并 |
| flatMapConcat | 顺序展开 | 一个完成再下一个 | 保证顺序的异步操作 |
| flatMapMerge | 并发展开 | 同时执行多个 | 提高吞吐量 |
| flatMapLatest | 最新值展开 | 取消旧的,执行新的 | 搜索建议 |
简化示例:
kotlin
javascript
// 1. zip - 一对一配对
val names = flowOf("Alice", "Bob")
val scores = flowOf(90, 85)
names.zip(scores) { n, s -> "$n: $s" }
// "Alice: 90", "Bob: 85"
// 2. combine - 动态组合
val query = MutableStateFlow("")
val filter = MutableStateFlow("all")
query.combine(filter) { q, f -> SearchParams(q, f) }
// 任一变化就重新组合
// 3. flatMapConcat - 顺序展开
flowOf(1, 2, 3).flatMapConcat { id ->
fetchUser(id).asFlow() // 顺序执行
}
// 4. flatMapMerge - 并发展开
flowOf(1, 2, 3).flatMapMerge(concurrency = 2) { id ->
fetchUser(id).asFlow() // 最多同时执行2个
}
// 5. flatMapLatest - 最新值展开
searchFlow.flatMapLatest { query ->
api.search(query).asFlow() // 新查询到来时取消旧的
}
4.4 线程调度与背压处理
线程调度操作符
| 操作符 | 作用 | 示例 |
|---|---|---|
| flowOn | 指定上游执行上下文 | .flowOn(Dispatchers.IO) |
| withContext | 临时切换上下文 | withContext(Dispatchers.Main) { } |
背压处理操作符
| 操作符 | 作用 | 策略 |
|---|---|---|
| buffer | 添加缓冲区 | 生产快于消费时缓冲数据 |
| conflate | 只保留最新值 | 跳过中间值,处理不过来时 |
| collectLatest | 取消慢的处理 | 新值到来时取消未完成的处理 |
简化示例:
kotlin
scss
// 线程调度
flow {
// 在IO线程执行
emit(fetchData())
}.flowOn(Dispatchers.IO)
.collect { data ->
// 在主线程更新UI
withContext(Dispatchers.Main) {
updateUI(data)
}
}
// 背压处理
// 1. buffer - 缓冲64个值
fastProducer.buffer(64).collect { slowConsumer(it) }
// 2. conflate - 只处理最新值
sensorFlow.conflate().collect { updateUI(it) }
// 3. collectLatest - 取消旧任务
clickFlow.collectLatest { animateButton(it) }
4.5 错误处理操作符
| 操作符 | 作用 | 执行时机 |
|---|---|---|
| catch | 捕获异常并恢复 | 上游发生异常时 |
| retry | 重试失败的操作 | 发生特定异常时 |
| onCompletion | 最终回调 | 流完成时(无论成功失败) |
| onStart | 开始前回调 | 流开始收集前 |
简化示例:
kotlin
scss
flow {
emit(fetchData()) // 可能失败
}
.onStart { showLoading() } // 开始前
.catch { e -> emit(fallbackData) } // 捕获异常
.retry(3) { e is IOException } // 网络错误重试3次
.onCompletion { hideLoading() } // 完成后
.collect { displayData(it) }
4.6 实战速查:常见场景操作符选择
场景1:搜索功能
kotlin
scss
searchQueryFlow
.debounce(300) // 防抖:避免频繁搜索
.filter { it.length >= 3 } // 过滤:至少3个字符
.distinctUntilChanged() // 去重:相同查询不重复搜索
.flatMapLatest { query -> // 最新值:取消之前的搜索
api.search(query).asFlow()
}
.catch { emit(emptyList()) } // 容错:搜索失败返回空
场景2:表单验证
kotlin
scss
combine(nameFlow, emailFlow, passwordFlow) {
name, email, pwd -> FormData(name, email, pwd)
}
.debounce(500) // 防抖:避免过度验证
.map { data -> validate(data) } // 验证逻辑
场景3:实时数据展示
kotlin
scss
sensorDataFlow
.sample(1000) // 采样:每秒更新一次UI
.flowOn(Dispatchers.IO) // 后台线程读取传感器
.collect { data ->
withContext(Dispatchers.Main) {
updateUI(data) // 主线程更新UI
}
}
场景4:批量数据处理
kotlin
scss
dataFlow
.buffer(100) // 缓冲:提高吞吐量
.batch(50) { batch -> // 自定义:每50个一批
database.insertBatch(batch)
}
4.7 操作符组合黄金法则
- 顺序很重要:操作符按声明顺序执行
- 尽早过滤:先用filter减少数据量
- 合理防抖:用户输入用debounce,实时数据用sample
- 线程优化 :IO操作用flowOn(Dispatchers.IO)
- 错误恢复:用catch在适当位置恢复,不要吞掉所有异常
- 资源清理:无限流要确保能被取消
快速参考卡片
kotlin
scss
// 转换数据
.map { } // 简单转换
.transform { } // 复杂转换
.scan { } // 累积计算
// 过滤数据
.filter { } // 条件过滤
.take(N) // 取前N个
.distinctUntilChanged() // 去重
// 时间控制
.debounce(ms) // 防抖
.sample(ms) // 采样
// 组合流
.zip { } // 一对一配对
.combine { } // 动态组合
// 展开流
.flatMapConcat { } // 顺序
.flatMapMerge { } // 并发
.flatMapLatest { } // 最新值
// 线程调度
.flowOn(Dispatchers.IO) // 指定上下文
// 错误处理
.catch { } // 捕获恢复
.retry(N) // 重试N次
第5章:SharedFlow (热流) - 事件与广播专家
5.1 SharedFlow 是什么?
核心定义 :
SharedFlow 是 Kotlin 协程库中的热流实现,它与普通 Flow 的关键区别在于:
- 数据生产与消费解耦:生产者独立运行,不依赖收集者
- 多订阅者广播:多个收集者接收相同的数据序列
- 可配置重播:新订阅者可以获取历史数据
适用场景:
- 事件总线(Event Bus)
- 实时数据推送(如股票行情、聊天消息)
- 系统状态广播(如网络状态、登录状态)
5.2 配置三要素深度解析
SharedFlow 的行为由三个关键参数控制,它们共同决定了数据流的特性和性能表现:
5.2.1 replay:重播缓冲区大小
| replay值 | 行为表现 | 适用场景 | 内存影响 | 性能特点 |
|---|---|---|---|---|
| 0 | 新订阅者收不到历史数据 | 纯事件总线 一次性事件 | 最低 | 轻量级,不保存历史 |
| 1 | 新订阅者收到最新1个数据 | UI状态同步 实时数据推送 | 低 | 平衡性能与即时性 |
| N | 新订阅者收到最近N个数据 | 聊天消息历史 操作记录 | 中等 | 保存历史,内存占用随N增加 |
| Int.MAX_VALUE | 保存所有历史数据 | 调试日志 审计追踪 | 高 | 可能内存泄漏,慎用 |
代码示例:
kotlin
ini
// replay = 0:事件总线
val eventBus = MutableSharedFlow<Event>(replay = 0)
// 新订阅者只收到订阅后的事件
// replay = 1:实时数据推送
val stockPrice = MutableSharedFlow<Double>(replay = 1)
// 新订阅者立即获得当前股价
// replay = 50:聊天室
val chatRoom = MutableSharedFlow<Message>(replay = 50)
// 新加入的用户看到最近50条消息
5.2.2 extraBufferCapacity:额外缓冲区容量
作用 :
当生产速度超过消费速度时,临时存储数据的缓冲区容量。缓冲区满时根据 onBufferOverflow 策略处理。
| 配置值 | 行为表现 | 适用场景 | 内存影响 |
|---|---|---|---|
| 0 | 无额外缓冲区 生产消费同步 | 低频率事件 重要状态更新 | 最低 |
| 10-100 | 小缓冲区 应对短暂突发 | 用户输入事件 传感器数据 | 低 |
| 100-1000 | 中缓冲区 处理生产消费速度差 | 网络数据流 日志记录 | 中等 |
| Channel.UNLIMITED | 无限制缓冲区 可能内存泄漏 | 调试场景 临时数据收集 | 高 |
代码示例:
kotlin
kotlin
// 无缓冲区:生产消费必须同步
val syncFlow = MutableSharedFlow<Int>(extraBufferCapacity = 0)
// emit() 会在消费者慢时挂起
// 有缓冲区:应对生产消费速度差
val asyncFlow = MutableSharedFlow<Int>(extraBufferCapacity = 50)
// 可以缓冲50个数据,满了才会挂起
5.2.3 onBufferOverflow:溢出处理策略
当缓冲区已满时,如何处理新数据:
| 策略 | 行为表现 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| SUSPEND | 挂起生产者,等待缓冲区空间 | 重要数据,不能丢失 | 数据完整性高 | 可能阻塞生产者 |
| DROP_OLDEST | 丢弃缓冲区中最旧的数据 | 实时数据,最新更重要 | 低延迟,不阻塞 | 可能丢失重要历史数据 |
| DROP_LATEST | 丢弃最新到达的数据 | 顺序敏感,不能跳过旧数据 | 保证顺序处理 | 可能丢失最新数据 |
代码示例:
kotlin
ini
// 重要事件:不能丢失
val criticalEvents = MutableSharedFlow<Event>(
onBufferOverflow = BufferOverflow.SUSPEND
)
// 实时传感器数据:最新值最重要
val sensorData = MutableSharedFlow<SensorData>(
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
// 顺序敏感的操作日志
val operationLog = MutableSharedFlow<LogEntry>(
onBufferOverflow = BufferOverflow.DROP_LATEST
)
5.2.4 参数组合实战演示
场景1:聊天应用消息流
kotlin
ini
// 需求:保存最近100条消息,应对网络波动,不能丢失消息
val chatMessages = MutableSharedFlow<Message>(
replay = 100, // 新用户看到最近100条
extraBufferCapacity = 50, // 应对消息突发
onBufferOverflow = BufferOverflow.SUSPEND // 消息不能丢
)
场景2:游戏实时状态同步
kotlin
ini
// 需求:同步玩家状态,只需要最新状态,高频更新,可以丢弃旧状态
val playerState = MutableSharedFlow<PlayerState>(
replay = 1, // 新订阅者获得当前状态
extraBufferCapacity = 10, // 小缓冲区
onBufferOverflow = BufferOverflow.DROP_OLDEST // 旧状态可丢弃
)
场景3:UI点击事件处理
kotlin
ini
// 需求:处理用户点击,不需要历史,避免事件堆积
val clickEvents = MutableSharedFlow<ClickEvent>(
replay = 0, // 纯事件流
extraBufferCapacity = 0, // 不缓冲,立即处理
onBufferOverflow = BufferOverflow.SUSPEND // 事件不能丢失
)
5.3 发射策略决策树
5.3.1 emit():挂起式发射
特点:
- 保证送达:如果缓冲区满,会挂起等待直到有空间
- 协程内使用:必须在协程作用域内调用
- 数据完整性:适合重要数据的发射
代码示例:
kotlin
scss
val sharedFlow = MutableSharedFlow<Data>()
// 在协程中使用emit()
viewModelScope.launch {
val data = fetchData()
sharedFlow.emit(data) // 保证数据被发射
println("数据已发送") // 这行代码保证会执行
}
// 对比:tryEmit()无法保证
val success = sharedFlow.tryEmit(data)
if (!success) {
println("数据可能丢失") // 发射失败,数据丢失
}
5.3.2 tryEmit():非阻塞发射
特点:
- 立即返回:不挂起,立即返回发射结果
- 任意线程:可以在任何线程调用,包括非协程环境
- 可能丢失:缓冲区满时发射失败,数据丢失
代码示例:
kotlin
java
// 在回调或非协程环境中使用
button.setOnClickListener {
val success = sharedFlow.tryEmit(ClickEvent())
if (!success) {
// 处理发射失败
Log.w("EventBus", "点击事件丢失")
}
}
// 高频数据发射,避免阻塞
sensorManager.registerListener { value ->
val success = sensorFlow.tryEmit(value)
// 不处理失败,可以接受数据丢失
}
5.3.3 发射策略对比表
| 特性 | emit() | tryEmit() |
|---|---|---|
| 执行方式 | 挂起函数,可能阻塞 | 非阻塞,立即返回 |
| 使用场景 | 协程作用域内 | 任意线程,包括回调 |
| 数据保证 | 保证数据被发射 | 可能发射失败 |
| 缓冲区满时 | 挂起等待 | 返回false |
| 推荐场景 | 重要数据、用户操作 | 高频数据、可丢失数据 |
5.3.4 决策树:选择正确的发射方式
text
scss
需要发射数据吗?
│
├── 数据非常重要,必须保证送达?
│ ├── 是 → 使用 emit() + SUSPEND策略
│ │ └── 注意:可能阻塞生产者
│ └── 否 → 可以接受偶尔丢失?
│ ├── 是 → 使用 tryEmit() + DROP_OLDEST
│ └── 否 → 回到上一步,数据其实重要
│
├── 在协程作用域内吗?
│ ├── 是 → 优先使用 emit()
│ └── 否 → 只能使用 tryEmit()
│
├── 数据产生频率很高?
│ ├── 是 → 使用 tryEmit() 避免阻塞
│ └── 否 → 可以使用 emit()
│
└── 需要立即知道发射结果?
├── 是 → 使用 tryEmit() 获取返回值
└── 否 → 使用 emit()
实际选择指南:
-
用户交互事件(如按钮点击):
kotlin
kotlin// 重要,不能丢失 suspend fun onButtonClick() { eventFlow.emit(ClickEvent()) } -
传感器数据:
kotlin
rust// 高频,可以丢失 sensorCallback = { value -> sensorFlow.tryEmit(value) // 不关心是否成功 } -
网络请求结果:
kotlin
scss// 重要,需要保证 viewModelScope.launch { val result = api.getData() dataFlow.emit(result) } -
性能计数器:
kotlin
kotlin// 可以丢失,不阻塞 fun incrementCounter() { counterFlow.tryEmit(count++) }
5.4 常见陷阱列表与对策
陷阱1:replay配置不当导致内存泄漏
| 陷阱表现 | 原因分析 | 解决方案 |
|---|---|---|
| 内存不断增长 | replay值过大,保存大量历史数据 | 合理设置replay值,定期清理 |
| 事件重复处理 | replay导致新订阅者收到旧事件 | 使用事件ID去重,或使用replay=0 |
| 启动时收到大量旧数据 | replay值过大,订阅时收到大量数据 | 使用WhileSubscribed策略限制重播时间 |
代码示例:
kotlin
ini
// 错误:replay过大
val flow = MutableSharedFlow<LogEntry>(replay = 10000) // 可能内存泄漏
// 正确:限制replay或使用过期策略
val flow = MutableSharedFlow<LogEntry>(
replay = 100,
extraBufferCapacity = 50
)
// 或者使用带过期时间的shareIn
val safeFlow = sourceFlow.shareIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(
replayExpirationMillis = 60000 // 60秒后过期
),
replay = 100
)
陷阱2:缓冲区溢出导致数据丢失
| 陷阱表现 | 原因分析 | 解决方案 |
|---|---|---|
| emit() 长时间挂起 | 缓冲区小,消费慢,SUSPEND策略 | 增大缓冲区或使用DROP策略 |
| tryEmit() 频繁失败 | 缓冲区满,数据丢失 | 调整缓冲区大小或发射策略 |
| 数据顺序错乱 | DROP_OLDEST策略丢弃旧数据 | 使用SUSPEND或DROP_LATEST |
代码示例:
kotlin
ini
// 错误:缓冲区太小,重要数据可能丢失
val flow = MutableSharedFlow<Data>(
extraBufferCapacity = 0,
onBufferOverflow = BufferOverflow.SUSPEND
)
// 正确:根据场景调整
// 场景A:重要数据,增大缓冲区
val importantFlow = MutableSharedFlow<Data>(
extraBufferCapacity = 100,
onBufferOverflow = BufferOverflow.SUSPEND
)
// 场景B:高频数据,使用DROP策略
val highFreqFlow = MutableSharedFlow<Data>(
extraBufferCapacity = 50,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
陷阱3:多订阅者导致重复处理
| 陷阱表现 | 原因分析 | 解决方案 |
|---|---|---|
| 同一事件处理多次 | 多个组件订阅同一个流 | 使用事件ID去重,或使用单例收集器 |
| 资源重复消耗 | 每个订阅者触发独立处理逻辑 | 共享处理结果,使用StateFlow存储状态 |
| 事件顺序不一致 | 不同订阅者处理速度不同 | 确保事件处理是幂等的 |
代码示例:
kotlin
kotlin
// 错误:多个地方收集同一个事件流
class Analytics {
init {
// 位置1
eventBus.events.collect { trackEvent(it) }
}
fun setup() {
// 位置2(可能被多次调用)
eventBus.events.collect { trackEvent(it) }
}
}
// 正确:使用单例收集器
object EventProcessor {
private val processedIds = mutableSetOf<String>()
fun startProcessing() {
CoroutineScope(Dispatchers.IO).launch {
eventBus.events.collect { event ->
if (processedIds.add(event.id)) {
processEvent(event)
}
}
}
}
}
陷阱4:生命周期管理不当
| 陷阱表现 | 原因分析 | 解决方案 |
|---|---|---|
| Activity销毁后仍接收事件 | 未取消事件收集 | 使用repeatOnLifecycle或flowWithLifecycle |
| 内存泄漏 | 持有Activity引用 | 使用ViewModel作用域,避免引用UI组件 |
| 配置变更重复订阅 | Activity重建后重新订阅 | 在ViewModel中管理订阅 |
代码示例:
kotlin
kotlin
// 错误:直接在Activity中收集,可能泄漏
class MyActivity : AppCompatActivity() {
override fun onCreate() {
lifecycleScope.launch {
eventBus.events.collect { event -> // 可能泄漏
handleEvent(event)
}
}
}
}
// 正确:使用生命周期感知的收集
class MyActivity : AppCompatActivity() {
override fun onCreate() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
eventBus.events.collect { event ->
handleEvent(event)
}
}
}
}
}
// 或者在ViewModel中收集
class MyViewModel : ViewModel() {
init {
viewModelScope.launch {
eventBus.events.collect { event ->
// ViewModel中处理,避免UI引用
processInBackground(event)
}
}
}
}
陷阱5:冷热流转换不当
| 陷阱表现 | 原因分析 | 解决方案 |
|---|---|---|
| 多次订阅触发多次网络请求 | 未正确使用shareIn/stateIn | 使用shareIn将冷流转热流 |
| 数据不同步 | 多个实例独立收集冷流 | 使用单例热流共享数据 |
| 资源浪费 | 每个UI组件独立收集相同数据 | 在ViewModel中共享热流 |
代码示例:
kotlin
scss
// 错误:每个收集者触发独立的网络请求
fun getUserFlow(userId: String): Flow<User> = flow {
emit(api.getUser(userId)) // 每次collect都会调用
}
// UI组件A
viewModel.getUserFlow("123").collect { showUser(it) }
// UI组件B
viewModel.getUserFlow("123").collect { updateProfile(it) } // 再次请求!
// 正确:使用shareIn共享结果
class UserViewModel : ViewModel() {
private val _userFlow = flow {
emit(api.getUser("123"))
}.shareIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
replay = 1
)
val userFlow: SharedFlow<User> = _userFlow
}
// 所有UI组件共享同一个流
viewModel.userFlow.collect { showUser(it) }
5.5 补充陷阱
陷阱6:忽略背压处理
问题:生产者速度远快于消费者,导致数据积压。
解决方案:
kotlin
scss
// 错误:快速生产,慢速消费,可能导致内存问题
val fastProducer = MutableSharedFlow<Int>(replay = 0)
// 生产者每秒发射1000个数据
launch {
repeat(100000) {
fastProducer.emit(it)
delay(1)
}
}
// 消费者每秒处理10个数据
launch {
fastProducer.collect {
processSlowly(it) // 处理很慢
delay(100)
}
}
// 正确:使用合适的背压策略
val safeFlow = MutableSharedFlow<Int>(
replay = 0,
extraBufferCapacity = 100, // 限制缓冲区
onBufferOverflow = BufferOverflow.DROP_OLDEST // 丢弃旧数据
)
// 或者消费者使用背压处理操作符
fastProducer
.buffer(100) // 添加缓冲区
.collectLatest { // 只处理最新值
processSlowly(it)
}
陷阱7:错误处理不当
问题:SharedFlow 发射异常会导致流终止。
解决方案:
kotlin
scss
// 错误:在发射时异常,流会终止
val flow = MutableSharedFlow<Int>()
launch {
try {
flow.emit(computeValue()) // 可能抛出异常
} catch (e: Exception) {
// 流已经终止!
}
}
// 正确:在转换层处理异常
val safeFlow = flow {
try {
emit(computeValue())
} catch (e: Exception) {
emit(ErrorResult(e))
}
}.shareIn(
scope = viewModelScope,
started = SharingStarted.Lazily
)
// 或者:使用catch操作符
sourceFlow
.catch { e -> emit(ErrorResult(e)) }
.shareIn(viewModelScope, SharingStarted.Lazily)
5.6 生命周期管理:shareIn 与 SharingStarted 策略
5.6.1 shareIn:冷流转热流的核心操作符
作用:将普通的冷流转换为 SharedFlow 热流,实现数据共享和资源优化。
工作原理:
kotlin
kotlin
fun <T> Flow<T>.shareIn(
scope: CoroutineScope, // 共享的作用域
started: SharingStarted, // 启动策略(核心)
replay: Int = 0 // 重播数量
): SharedFlow<T>
转换过程:
- 创建共享流:内部创建一个 MutableSharedFlow
- 启动收集协程:在指定作用域中启动协程收集源流
- 数据转发:将源流的数据转发到 SharedFlow
- 订阅管理:根据 started 策略管理收集协程的生命周期
5.6.2 SharingStarted 三大策略详解
策略一:Eagerly(立即开始)
行为:立即开始收集源流,永不停止。
kotlin
ini
val hotFlow = coldFlow.shareIn(
scope = viewModelScope,
started = SharingStarted.Eagerly, // 立即开始
replay = 1
)
适用场景:
- ✅ 全局配置(主题、语言)
- ✅ 需要持续运行的后台服务
- ✅ 实时监控数据
- ❌ 页面级数据(可能浪费资源)
内存生命周期:
text
markdown
创建时 → 立即开始收集 → 作用域销毁时停止
└→ 即使没有订阅者也运行
策略二:Lazily(惰性开始)
行为:等待第一个订阅者出现时开始,之后持续运行。
kotlin
ini
val hotFlow = coldFlow.shareIn(
scope = viewModelScope,
started = SharingStarted.Lazily, // 有订阅者时开始
replay = 1
)
适用场景:
- ✅ 页面数据(按需加载)
- ✅ 用户触发的数据流
- ✅ 节省资源的场景
- ❌ 需要立即可用的数据
内存生命周期:
text
markdown
创建时 → 等待订阅者 → 第一个订阅者出现时开始 → 持续运行
└→ 无订阅者时不运行
策略三:WhileSubscribed(订阅时运行)
行为:有订阅者时开始,订阅者全部离开后停止。
kotlin
ini
val hotFlow = coldFlow.shareIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(
stopTimeoutMillis = 5000, // 最后一个订阅者离开后5秒停止
replayExpirationMillis = 60000 // 重播数据60秒后过期
),
replay = 1
)
参数说明:
stopTimeoutMillis:最后一个订阅者离开后,等待多久停止收集replayExpirationMillis:重播数据在多长时间后过期(清除)
适用场景:
- ✅ 页面频繁切换的数据
- ✅ 需要保持数据新鲜但节省资源
- ✅ 平衡性能和用户体验
- ❌ 需要立即响应的全局状态
内存生命周期:
text
markdown
创建时 → 等待订阅者 → 有订阅者时开始 → 订阅者离开 → 等待超时 → 停止
└→ 无订阅者且超时后停止运行
5.6.3 策略选择指南
| 场景类型 | 推荐策略 | 理由 | 代码示例 |
|---|---|---|---|
| 全局主题设置 | Eagerly | 立即可用,持续更新 | SharingStarted.Eagerly |
| 用户个人资料 | Lazily | 按需加载,节省资源 | SharingStarted.Lazily |
| 商品详情页 | WhileSubscribed(5000) | 页面切换时保持数据新鲜 | SharingStarted.WhileSubscribed(5000) |
| 实时聊天 | WhileSubscribed(0) | 离开页面立即停止,节省资源 | SharingStarted.WhileSubscribed(0) |
| 搜索建议 | WhileSubscribed(3000, 10000) | 短暂保持,过期清除 | SharingStarted.WhileSubscribed(3000, 10000) |
5.6.4 实战示例:不同场景的配置
示例1:用户设置(全局,立即生效)
kotlin
ini
class SettingsViewModel : ViewModel() {
val themeFlow = settingsRepository.themeFlow
.shareIn(
scope = viewModelScope,
started = SharingStarted.Eagerly, // 立即开始,全局生效
replay = 1 // 新订阅者立即获得当前主题
)
}
示例2:商品详情(页面级,按需加载)
kotlin
ini
class ProductViewModel(productId: String) : ViewModel() {
val productFlow = productRepository.getProductFlow(productId)
.shareIn(
scope = viewModelScope,
started = SharingStarted.Lazily, // 有订阅者时加载
replay = 1 // 缓存最新商品信息
)
}
示例3:实时通知(智能生命周期)
kotlin
ini
class NotificationViewModel : ViewModel() {
val notifications = notificationRepository.notificationFlow
.shareIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(
stopTimeoutMillis = 3000, // 离开页面3秒后停止接收
replayExpirationMillis = 30000 // 30秒前的通知不清除
),
replay = 10 // 保留最近10条通知
)
}
5.6.5 性能优化技巧
技巧1:避免重复的 shareIn 调用
kotlin
kotlin
// 错误:每次调用都创建新的共享流
fun getUserFlow(userId: String): SharedFlow<User> {
return userRepository.getUserFlow(userId)
.shareIn(viewModelScope, SharingStarted.Lazily) // 每次调用都创建!
}
// 正确:缓存共享流
class UserViewModel : ViewModel() {
private val userFlowCache = mutableMapOf<String, SharedFlow<User>>()
fun getUserFlow(userId: String): SharedFlow<User> {
return userFlowCache.getOrPut(userId) {
userRepository.getUserFlow(userId)
.shareIn(viewModelScope, SharingStarted.Lazily)
}
}
}
技巧2:合理设置重播过期时间
kotlin
ini
// 对于频繁变化的数据,设置较短的过期时间
val stockPriceFlow = stockRepository.priceFlow
.shareIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(
replayExpirationMillis = 5000 // 5秒前的股价已过期
),
replay = 1
)
技巧3:根据设备状态调整策略
kotlin
ini
val batterySaverMode = // 检测是否在省电模式
val dataFlow = repository.dataFlow.shareIn(
scope = viewModelScope,
started = if (batterySaverMode) {
SharingStarted.Lazily // 省电模式:按需加载
} else {
SharingStarted.Eagerly // 正常模式:预加载
},
replay = 1
)
5.7 四大数据流对比表
| 特性维度 | Flow (冷流) | StateFlow (热流) | SharedFlow (热流) | LiveData (Android) |
|---|---|---|---|---|
| 温度类型 | 冷流 | 热流 | 热流 | 热流 |
| 数据共享 | 每个收集者独立数据 | 多观察者共享状态 | 多订阅者共享数据 | 多观察者共享数据 |
| 初始值要求 | 无 | 必须有初始值 | 可选(通过replay) | 可选 |
| 生命周期感知 | 无(需手动管理) | 无(需配合Lifecycle) | 无(需配合Lifecycle) | 内置生命周期感知 |
| 重播机制 | 无 | 固定重播1个最新值 | 可配置重播N个值 | 无标准重播机制 |
| 线程安全 | 依赖flowOn配置 | 高并发安全 | 高并发安全 | 主线程安全 |
| 背压处理 | 内置(挂起) | DROP_OLDEST策略 | 可配置溢出策略 | 无背压处理 |
| 平台支持 | Kotlin多平台 | Kotlin多平台 | Kotlin多平台 | 仅Android |
| 协程集成 | 深度集成 | 深度集成 | 深度集成 | 有限集成(通过扩展) |
| 使用场景 | 网络请求 数据库查询 | UI状态管理 | 事件总线 实时数据广播 | Android UI状态 |
| 性能特点 | 按需执行,资源高效 | 状态容器,自动去重 | 灵活配置,适用性广 | 简单易用,Android优化 |
选择决策树
text
markdown
需要处理什么类型的数据?
│
├── 需要管理UI状态?
│ ├── 是 → **StateFlow**(初始值 + 自动去重)
│ └── 否 →
│ ├── 需要事件总线或广播?
│ │ ├── 是 → **SharedFlow**(可配置重播)
│ │ └── 否 → 继续判断
│ └── 需要一次性数据获取?
│ ├── 是 → **Flow冷流**(按需执行)
│ └── 否 → 回到起点
│
├── 需要在Android平台使用?
│ ├── 是 →
│ │ ├── 新项目 → 优先考虑StateFlow/SharedFlow
│ │ └── 旧项目迁移 → LiveData + 逐步迁移
│ └── 否 → 排除LiveData
│
├── 需要多平台支持?
│ ├── 是 → **Flow/StateFlow/SharedFlow**
│ └── 否 → 都可以考虑
│
└── 需要简单易用?
├── 是 →
│ ├── Android UI → LiveData(简单)
│ └── 其他 → StateFlow(相对简单)
└── 否 → 根据具体需求选择
迁移指南
从 LiveData 迁移到 StateFlow:
kotlin
kotlin
// 以前:LiveData
class OldViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
}
// 现在:StateFlow
class NewViewModel : ViewModel() {
private val _data = MutableStateFlow("") // 必须有初始值
val data: StateFlow<String> = _data.asStateFlow()
}
// UI层适配
// 以前:
viewModel.data.observe(viewLifecycleOwner) { updateUI(it) }
// 现在:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.data.collect { updateUI(it) }
}
}
从回调接口迁移到 SharedFlow:
kotlin
kotlin
// 以前:回调接口
interface DataCallback {
fun onData(data: Data)
fun onError(error: Throwable)
}
// 现在:SharedFlow
class DataManager {
private val _dataFlow = MutableSharedFlow<Data>()
private val _errorFlow = MutableSharedFlow<Throwable>()
val dataFlow: SharedFlow<Data> = _dataFlow.asSharedFlow()
val errorFlow: SharedFlow<Throwable> = _errorFlow.asSharedFlow()
fun fetchData() {
viewModelScope.launch {
try {
val data = api.getData()
_dataFlow.emit(data)
} catch (e: Exception) {
_errorFlow.emit(e)
}
}
}
}
最佳实践总结
-
UI状态管理 → StateFlow
- 必须有初始值
- 使用
copy()更新数据类 - 配合
repeatOnLifecycle收集
-
事件通信 → SharedFlow (replay=0)
- 设置合适的缓冲区大小
- 重要事件用
emit(),高频事件用tryEmit() - 注意生命周期管理
-
实时数据 → SharedFlow (replay=1)
- 新订阅者立即获得最新值
- 使用
WhileSubscribed策略节省资源 - 设置合理的过期时间
-
一次性数据 → 普通Flow
- 网络请求、数据库查询
- 使用
flowOn切换线程 - 使用
catch处理错误
-
兼容旧代码 → LiveData
- 使用
asLiveData()转换 - 逐步迁移,不强制替换
- 新功能优先使用Flow
- 使用
第6章:StateFlow (热流) - 新一代状态容器
6.1 StateFlow 是什么?
6.1.1 官方定义与核心特性
StateFlow 的定义 :
StateFlow 是 Kotlin 协程库中专门为 UI 状态管理设计的热流实现。它是 SharedFlow 的一个特殊配置变体,针对状态管理场景进行了优化。
为什么需要 StateFlow?
- 状态管理痛点:传统的 LiveData 虽然简单,但缺少协程集成和丰富的操作符
- 跨平台需求:需要一套能在 Android、iOS、后端统一使用的状态管理方案
- 性能优化:需要自动去重、线程安全等特性来优化 UI 性能
核心特性:
kotlin
kotlin
// 1. 必须有初始值
val state = MutableStateFlow("初始值") // 必须提供初始值
// 2. 自动去重
state.value = "相同值" // 设置两次相同值,只触发一次更新
state.value = "相同值" // 不会触发更新
// 3. 固定重播1个值
// 新订阅者立即获得当前状态
val newSubscriber = state.collect { println("收到: $it") }
// 立即打印:收到: 相同值
// 4. 线程安全
// 可以在任何线程安全地更新
GlobalScope.launch(Dispatchers.IO) {
state.value = "IO线程更新" // 线程安全
}
6.1.2 StateFlow 与 SharedFlow 的关系
StateFlow 本质上是配置固定的 SharedFlow:
kotlin
arduino
// StateFlow 的等价 SharedFlow 配置
val sharedFlow = MutableSharedFlow<String>(
replay = 1, // 固定重播1个值
extraBufferCapacity = 0, // 无额外缓冲区
onBufferOverflow = BufferOverflow.DROP_OLDEST // 丢弃旧值
)
// StateFlow 添加了额外功能:
// 1. 便捷的 .value 属性访问器
// 2. compareAndSet 原子操作
// 3. 内置的 distinctUntilChanged 语义
两者对比:
kotlin
java
// SharedFlow 灵活配置,StateFlow 专一用途
// SharedFlow:事件总线
val eventBus = MutableSharedFlow<Event>(
replay = 0, // 新订阅者不接收历史事件
extraBufferCapacity = 100 // 较大的缓冲区
)
// StateFlow:状态容器
val uiState = MutableStateFlow(UiState()) // 专为UI状态设计
// 使用场景区分:
// - UI状态管理 → StateFlow
// - 事件总线 → SharedFlow
// - 实时数据 → SharedFlow (replay=1)
6.2 对比 LiveData:完整迁移指南
6.2.1 为什么从 LiveData 迁移到 StateFlow?
LiveData 的局限性:
- 协程集成有限:LiveData 需要额外扩展才能与协程深度集成
- 仅限 Android:LiveData 是 Android 专属,无法用于跨平台开发
- 操作符有限:缺少 Flow 丰富的操作符支持
- 背压处理不足:没有内置的背压处理机制
StateFlow 的优势:
kotlin
scss
// 1. 协程原生支持
val userState: StateFlow<User> = userRepository.userFlow
.map { it.toUiModel() }
.stateIn(viewModelScope, SharingStarted.Eagerly, User())
// 2. 丰富的操作符
userState
.filter { it.isActive }
.map { it.name }
.distinctUntilChanged()
.collect { updateUI(it) }
// 3. 跨平台支持
// 相同的代码可在 Android、iOS、Web、后端运行
// 4. 更好的错误处理
userState
.catch { e -> emit(User.Error(e.message)) }
.collect { handleResult(it) }
6.2.2 迁移步骤详解
步骤1:定义状态数据类
kotlin
kotlin
// Before: 多个 LiveData
class LiveDataViewModel : ViewModel() {
private val _user = MutableLiveData<User>()
private val _loading = MutableLiveData<Boolean>()
private val _error = MutableLiveData<String?>()
val user: LiveData<User> = _user
val loading: LiveData<Boolean> = _loading
val error: LiveData<String?> = _error
}
// After: 单一状态数据类
data class UserState(
val user: User? = null,
val loading: Boolean = false,
val error: String? = null
)
class StateFlowViewModel : ViewModel() {
private val _state = MutableStateFlow(UserState())
val state: StateFlow<UserState> = _state.asStateFlow()
}
步骤2:更新状态的方式
kotlin
kotlin
// Before: LiveData 多个更新
fun loadUser() {
_loading.value = true
_error.value = null
viewModelScope.launch {
try {
_user.value = repository.getUser()
} catch (e: Exception) {
_error.value = e.message
} finally {
_loading.value = false
}
}
}
// After: StateFlow 单次更新
fun loadUser() {
// 一次更新包含所有状态变化
_state.value = _state.value.copy(loading = true, error = null)
viewModelScope.launch {
try {
val user = repository.getUser()
_state.value = UserState(user = user, loading = false)
} catch (e: Exception) {
_state.value = _state.value.copy(
error = e.message,
loading = false
)
}
}
}
步骤3:UI层消费方式
kotlin
kotlin
// Before: LiveData(Activity/Fragment)
class UserActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.user.observe(this) { user ->
updateUserUI(user)
}
viewModel.loading.observe(this) { loading ->
showLoading(loading)
}
viewModel.error.observe(this) { error ->
showError(error)
}
}
}
// After: StateFlow(Activity/Fragment)
class UserActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// 统一收集所有状态
viewModel.state.collect { state ->
updateUserUI(state.user)
showLoading(state.loading)
showError(state.error)
}
}
}
}
}
步骤4:Compose 中的使用
kotlin
kotlin
// Before: LiveData + Compose
@Composable
fun LiveDataScreen(viewModel: LiveDataViewModel) {
val user by viewModel.user.observeAsState()
val loading by viewModel.loading.observeAsState(false)
val error by viewModel.error.observeAsState()
// 分别处理每个状态
}
// After: StateFlow + Compose
@Composable
fun StateFlowScreen(viewModel: StateFlowViewModel) {
// 使用 collectAsStateWithLifecycle 安全收集
val state by viewModel.state.collectAsStateWithLifecycle()
// 统一处理状态
UserScreen(
user = state.user,
loading = state.loading,
error = state.error
)
}
6.2.3 兼容性处理
双向兼容方案:
kotlin
kotlin
class CompatibilityViewModel : ViewModel() {
// 新代码使用 StateFlow
private val _state = MutableStateFlow(UserState())
val state: StateFlow<UserState> = _state.asStateFlow()
// 旧代码兼容:提供 LiveData 版本
val userLiveData: LiveData<User?> = _state
.map { it.user }
.asLiveData() // 转换为 LiveData
val loadingLiveData: LiveData<Boolean> = _state
.map { it.loading }
.asLiveData()
val errorLiveData: LiveData<String?> = _state
.map { it.error }
.asLiveData()
}
// 使用场景:
// 1. 新UI组件 → 直接使用 state
// 2. 旧UI组件 → 继续使用 LiveData
// 3. DataBinding → 使用 LiveData 版本
渐进式迁移策略:
text
迁移路线图:
第1阶段:新功能使用 StateFlow,旧功能保持 LiveData
第2阶段:逐步将高频修改的页面迁移到 StateFlow
第3阶段:将核心业务逻辑全部迁移到 StateFlow
第4阶段:移除所有 LiveData,完全使用 StateFlow
6.3 复杂 UI 状态管理
6.3.1 状态合并:combine实战
什么是状态合并?
状态合并是指将多个独立的数据源组合成一个统一的 UI 状态对象。当 UI 需要同时显示多个相关数据时(如用户信息、帖子列表、关注者),使用 combine 操作符可以确保所有数据同步更新,避免 UI 部分刷新导致的闪烁或不一致。
为什么需要状态合并?
- 避免UI闪烁:如果每个数据源独立更新,UI 会多次重组
- 保证数据一致性:确保相关数据同时更新,避免显示过时组合
- 简化UI逻辑:UI 只需处理一个完整状态,而不是多个独立状态
- 减少资源消耗:一次更新代替多次更新
状态合并示例:
kotlin
ini
// 场景:电商商品详情页
data class ProductDetailState(
val product: Product? = null,
val reviews: List<Review> = emptyList(),
val relatedProducts: List<Product> = emptyList(),
val stockInfo: StockInfo? = null,
val isLoading: Boolean = false,
val error: String? = null
)
class ProductDetailViewModel(productId: String) : ViewModel() {
// 多个独立数据源
private val productFlow = productRepo.getProductFlow(productId)
private val reviewsFlow = reviewRepo.getReviewsFlow(productId)
private val relatedFlow = productRepo.getRelatedProductsFlow(productId)
private val stockFlow = inventoryRepo.getStockFlow(productId)
// 使用 combine 合并所有数据源
val uiState: StateFlow<ProductDetailState> = combine(
productFlow,
reviewsFlow,
relatedFlow,
stockFlow
) { product, reviews, related, stock ->
ProductDetailState(
product = product,
reviews = reviews,
relatedProducts = related,
stockInfo = stock,
isLoading = false
)
}
.onStart {
// 开始加载时显示加载状态
emit(ProductDetailState(isLoading = true))
}
.catch { e ->
// 错误处理
emit(ProductDetailState(error = "加载失败: ${e.message}"))
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = ProductDetailState(isLoading = true)
)
}
combine 的工作原理:
- 监听所有输入流:同时监控 productFlow、reviewsFlow 等
- 等待数据就绪:当任何一个流发出新值时,collect 所有流的最新值
- 重新计算状态:使用所有最新值重新计算 ProductDetailState
- 发射统一状态:将新的状态发射给 UI 层
- 保证原子性:UI 一次性获得所有更新,确保显示一致性
6.3.2 状态派生:派生状态管理
什么是派生状态?
派生状态是指基于现有状态计算得出的新状态。这些状态不需要存储在数据库中,而是根据已有状态实时计算得出。
为什么需要派生状态?
- 避免重复计算:相同的计算逻辑可能被多个地方使用
- 保持一致性:确保所有地方使用的派生状态计算结果一致
- 性能优化:通过缓存派生状态避免重复计算
- 关注点分离:将计算逻辑与业务逻辑分离
派生状态示例:
kotlin
ini
class ShoppingCartViewModel : ViewModel() {
// 基础状态:购物车商品列表
private val _cartItems = MutableStateFlow<List<CartItem>>(emptyList())
val cartItems: StateFlow<List<CartItem>> = _cartItems.asStateFlow()
// 派生状态1:总金额
val totalPrice: StateFlow<Double> = cartItems
.map { items ->
items.sumOf { it.price * it.quantity }
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = 0.0
)
// 派生状态2:商品数量
val itemCount: StateFlow<Int> = cartItems
.map { items ->
items.sumOf { it.quantity }
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = 0
)
// 派生状态3:是否有优惠
val hasDiscount: StateFlow<Boolean> = totalPrice
.map { total -> total > 100.0 } // 满100有优惠
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = false
)
// 派生状态4:推荐商品(基于购物车内容)
val recommendedProducts: StateFlow<List<Product>> = cartItems
.flatMapLatest { items ->
// 基于购物车商品计算推荐
val categories = items.map { it.category }.toSet()
productRepo.getRecommendationsFlow(categories)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = emptyList()
)
}
派生状态的最佳实践:
kotlin
kotlin
// 1. 使用 remember 缓存计算结果(Compose中)
@Composable
fun CartSummary(cartItems: List<CartItem>) {
// 使用 remember 避免每次重组都重新计算
val totalPrice = remember(cartItems) {
cartItems.sumOf { it.price * it.quantity }
}
// 复杂的派生状态使用 derivedStateOf
val hasFreeShipping = remember(cartItems) {
derivedStateOf {
totalPrice > 50.0 || cartItems.any { it.isEligibleForFreeShipping }
}
}.value
Text("总价: $totalPrice, 免邮: $hasFreeShipping")
}
// 2. ViewModel 中的派生状态缓存
class CachedViewModel : ViewModel() {
private val calculationCache = mutableMapOf<String, StateFlow<Any>>()
fun <T> getOrCreateDerivedState(
key: String,
calculation: () -> Flow<T>
): StateFlow<T> {
return calculationCache.getOrPut(key) {
calculation().stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = null
)
} as StateFlow<T>
}
}
6.3.3 状态分片:局部更新优化
什么是状态分片?
状态分片是将大的状态对象拆分为多个小的、独立的状态片段。每个 UI 组件只订阅自己关心的状态片段,避免不必要的更新。
为什么需要状态分片?
- 减少不必要的更新:当状态某个字段变化时,只更新关心该字段的组件
- 提高性能:减少 UI 重组次数,特别是对于复杂 UI
- 更好的模块化:每个组件只关心自己的状态,降低耦合度
- 便于测试:可以独立测试每个状态片段
状态分片示例:
kotlin
kotlin
// 反模式:一个大状态对象
data class AppState(
val user: User? = null, // 用户信息
val theme: Theme = Theme.LIGHT, // 主题
val language: Language = Language.EN, // 语言
val notifications: List<Notification> = emptyList(), // 通知
val settings: Settings = Settings(), // 设置
val isOnline: Boolean = true // 网络状态
// ... 很多其他字段
)
// 问题:更新任何字段都需要创建新对象,所有订阅者都会收到更新
// 正模式:状态分片
class AppViewModel : ViewModel() {
// 按业务域拆分状态
val userState = MutableStateFlow<UserState>(UserState())
val themeState = MutableStateFlow(Theme.LIGHT)
val languageState = MutableStateFlow(Language.EN)
val notificationState = MutableStateFlow<List<Notification>>(emptyList())
val settingsState = MutableStateFlow(Settings())
val networkState = MutableStateFlow(true)
// 需要完整状态时再组合(不常用)
val fullAppState: StateFlow<AppState> = combine(
userState,
themeState,
languageState,
notificationState,
settingsState,
networkState
) { user, theme, language, notifications, settings, isOnline ->
AppState(user, theme, language, notifications, settings, isOnline)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = AppState()
)
}
// UI组件只订阅需要的部分
@Composable
fun ThemeSwitcher(viewModel: AppViewModel) {
// 只订阅主题状态
val theme by viewModel.themeState.collectAsStateWithLifecycle()
// 只有当主题变化时才重组
ThemeToggle(
currentTheme = theme,
onThemeChange = { newTheme ->
viewModel.themeState.value = newTheme
}
)
}
@Composable
fun NotificationBell(viewModel: AppViewModel) {
// 只订阅通知状态
val notifications by viewModel.notificationState.collectAsStateWithLifecycle()
// 只有当通知变化时才重组
NotificationIcon(
count = notifications.size,
hasUnread = notifications.any { !it.isRead }
)
}
状态分片的设计原则:
kotlin
kotlin
// 原则1:按业务领域划分
class UserProfileViewModel : ViewModel() {
// 用户基本信息
val basicInfo = MutableStateFlow<UserBasicInfo?>(null)
// 用户统计信息
val stats = MutableStateFlow<UserStats?>(null)
// 用户社交信息
val socialInfo = MutableStateFlow<SocialInfo?>(null)
// 用户设置
val userSettings = MutableStateFlow<UserSettings>(UserSettings())
}
// 原则2:按更新频率划分
class DashboardViewModel : ViewModel() {
// 高频更新:实时数据
val realtimeMetrics = MutableStateFlow<RealtimeMetrics>(RealtimeMetrics())
// 中频更新:用户交互
val userActions = MutableStateFlow<List<UserAction>>(emptyList())
// 低频更新:配置信息
val config = MutableStateFlow<Config>(Config())
}
// 原则3:按UI组件划分
class ProductPageViewModel : ViewModel() {
// 商品展示组件使用的状态
val productDisplayState = MutableStateFlow<ProductDisplayState>(ProductDisplayState())
// 购买组件使用的状态
val purchaseState = MutableStateFlow<PurchaseState>(PurchaseState())
// 评价组件使用的状态
val reviewState = MutableStateFlow<ReviewState>(ReviewState())
}
6.4 UI层安全消费四重保障
6.4.1 repeatOnLifecycle 原理详解
问题背景 :
在 Android 中,传统的流收集方式存在生命周期管理问题:
- 配置变更问题:屏幕旋转时协程重启,可能导致重复订阅
- 内存泄漏风险:Activity/Fragment 销毁后协程可能仍在运行
- 资源浪费:不可见时仍在处理数据,浪费 CPU 和网络资源
repeatOnLifecycle 解决方案:
kotlin
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ❌ 传统方式:有问题
lifecycleScope.launch {
// 屏幕旋转时,这个协程会重启
viewModel.uiState.collect { state ->
updateUI(state) // 可能被调用多次
}
}
// ✅ 正确方式:repeatOnLifecycle
lifecycleScope.launch {
// 只在 STARTED 到 STOPPED 状态之间收集
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
updateUI(state)
}
}
// 当生命周期离开 STARTED 时,收集协程自动取消
}
}
}
生命周期状态与收集行为:
text
ini
Activity 生命周期: onCreate → onStart → onResume → onPause → onStop → onDestroy
| | | | | |
repeatOnLifecycle(STARTED): [------- 收集运行 -------] [停止收集]
| | | | |
repeatOnLifecycle(RESUMED): [收集运行] [停止收集]
repeatOnLifecycle 的工作原理:
-
状态监听:监听生命周期的状态变化
-
智能启停:
- 当生命周期进入目标状态(如 STARTED)时,启动收集协程
- 当生命周期离开目标状态时,取消收集协程
-
自动恢复:
- 如果生命周期重新进入目标状态,重新启动收集协程
-
避免重复:确保同一时间只有一个收集协程运行
使用示例:
kotlin
scss
class UserFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 方法1:直接使用 repeatOnLifecycle
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// 可以启动多个收集协程
launch { collectUserData() }
launch { collectNotifications() }
launch { collectMessages() }
}
}
// 方法2:使用 repeatOnLifecycle 配合流
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// 组合多个流
combine(
viewModel.userFlow,
viewModel.notificationsFlow,
viewModel.messagesFlow
) { user, notifications, messages ->
Triple(user, notifications, messages)
}.collect { (user, notifications, messages) ->
updateUI(user, notifications, messages)
}
}
}
}
private suspend fun collectUserData() {
viewModel.userFlow.collect { user ->
updateUserInfo(user)
}
}
}
6.4.2 flowWithLifecycle 简化版
flowWithLifecycle 介绍 :
flowWithLifecycle 是 repeatOnLifecycle 的简化版本,它返回一个新的 Flow,这个 Flow 只在指定的生命周期状态下发射数据。
与 repeatOnLifecycle 的对比:
kotlin
scss
class MyFragment : Fragment() {
// 方式1:repeatOnLifecycle(更灵活)
private fun setupWithRepeat() {
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// 可以启动多个协程
launch { collectFlow1() }
launch { collectFlow2() }
launch { collectFlow3() }
}
}
}
// 方式2:flowWithLifecycle(更简洁)
private fun setupWithFlowWithLifecycle() {
viewLifecycleOwner.lifecycleScope.launch {
// 每个流单独处理
flow1
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect { handleFlow1(it) }
}
viewLifecycleOwner.lifecycleScope.launch {
flow2
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect { handleFlow2(it) }
}
}
}
flowWithLifecycle 的最佳实践:
kotlin
kotlin
// 场景1:Activity 中使用
class MainActivity : AppCompatActivity() {
fun observeData() {
lifecycleScope.launch {
viewModel.dataFlow
.flowWithLifecycle(lifecycle, Lifecycle.State.CREATED)
.collect { data ->
// 在 Activity 创建后开始收集
handleData(data)
}
}
}
}
// 场景2:Fragment 中使用(推荐使用 viewLifecycleOwner)
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.dataFlow
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect { data ->
// 在 Fragment 视图启动后开始收集
handleData(data)
}
}
}
}
// 场景3:多流组合
class MultiFlowFragment : Fragment() {
fun observeMultipleFlows() {
viewLifecycleOwner.lifecycleScope.launch {
// 组合多个应用了 flowWithLifecycle 的流
combine(
flow1.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED),
flow2.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED),
flow3.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
) { data1, data2, data3 ->
Triple(data1, data2, data3)
}.collect { (data1, data2, data3) ->
updateUI(data1, data2, data3)
}
}
}
}
6.4.3 Compose 中的安全收集
Compose 中的状态收集问题 :
在 Jetpack Compose 中,不正确的状态收集会导致:
- 过度重组:每次重组都重新收集,性能低下
- 内存泄漏:未正确管理生命周期的收集
- 状态不一致:收集时机不当导致 UI 显示错误状态
正确的收集方式:
kotlin
kotlin
@Composable
fun UserProfileScreen(viewModel: UserViewModel) {
// ✅ 方式1:collectAsStateWithLifecycle(推荐)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// 自动处理生命周期,只在活跃时收集
// ✅ 方式2:使用 LaunchedEffect + repeatOnLifecycle
val lifecycle = LocalLifecycleOwner.current.lifecycle
val uiState2 by produceState<UserUiState>(
initialValue = UserUiState.Loading,
key1 = lifecycle,
key2 = viewModel // 添加 key 确保唯一性
) {
// 手动管理生命周期
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { value = it }
}
}
// ❌ 错误方式:每次重组都创建新收集
var badState by remember { mutableStateOf<UserUiState>(UserUiState.Loading) }
LaunchedEffect(Unit) { // key 为 Unit,每次重组都会执行
viewModel.uiState.collect { badState = it } // 每次重组都会执行!
}
// 根据状态渲染 UI
when (val state = uiState) {
is UserUiState.Loading -> LoadingIndicator()
is UserUiState.Success -> UserProfileContent(state.user)
is UserUiState.Error -> ErrorMessage(state.message)
}
}
collectAsStateWithLifecycle 详解:
kotlin
kotlin
// collectAsStateWithLifecycle 的内部实现原理
@Composable
fun <T> Flow<T>.collectAsStateWithLifecycle(
initialValue: T,
lifecycle: Lifecycle = LocalLifecycleOwner.current.lifecycle,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED
): State<T> {
return produceState(initialValue, this, lifecycle, minActiveState) {
lifecycle.repeatOnLifecycle(minActiveState) {
collect { value = it }
}
}
}
// 使用示例
@Composable
fun ProductScreen(productId: String) {
val viewModel: ProductViewModel = viewModel(
factory = ProductViewModelFactory(productId)
)
// 安全收集状态
val productState by viewModel.productState.collectAsStateWithLifecycle()
// 派生状态:使用 remember 缓存计算结果
val discountPrice = remember(productState.product) {
productState.product?.let { product ->
if (product.onSale) product.price * 0.8 else product.price
}
}
// 条件收集:只有登录用户才收集用户特定数据
val isLoggedIn by authManager.isLoggedIn.collectAsStateWithLifecycle()
if (isLoggedIn) {
val userPreferences by viewModel.userPreferences.collectAsStateWithLifecycle()
// 显示个性化内容
}
// 渲染 UI
ProductContent(
product = productState.product,
loading = productState.loading,
error = productState.error,
discountPrice = discountPrice
)
}
Compose 中的配置变更处理:
kotlin
kotlin
@Composable
fun RememberStateExample() {
// 使用 rememberSaveable 保存 UI 状态(配置变更时保持)
var expanded by rememberSaveable { mutableStateOf(false) }
var selectedTab by rememberSaveable { mutableStateOf(0) }
// 使用 remember 缓存计算结果
val expensiveCalculation = remember(key1 = expanded, key2 = selectedTab) {
// 只有 expanded 或 selectedTab 变化时才重新计算
computeExpensiveValue(expanded, selectedTab)
}
// 对于 ViewModel 状态,ViewModel 会自动处理配置变更
val viewModel: MyViewModel = viewModel()
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// 但需要注意:ViewModel 在配置变更后可能会重新初始化
// 如果需要保持某些状态,使用 SavedStateHandle
LaunchedEffect(Unit) {
// 在配置变更后恢复状态
viewModel.restoreState()
}
}
6.4.4 配置变更处理策略
Android 配置变更问题 :
当设备配置发生变化时(如屏幕旋转、语言切换),Activity/Fragment 会被销毁并重新创建。这会导致:
- 状态丢失:UI 状态和临时数据丢失
- 资源浪费:重新执行初始化操作
- 用户体验差:数据需要重新加载
StateFlow 的配置变更优势:
kotlin
kotlin
// StateFlow 在 ViewModel 中可以自动保持状态
class UserViewModel : ViewModel() {
// StateFlow 在配置变更时保持数据
private val _userState = MutableStateFlow(UserState())
val userState: StateFlow<UserState> = _userState.asStateFlow()
// 数据会自动保持,无需额外处理
fun loadUser() {
viewModelScope.launch {
_userState.value = _userState.value.copy(loading = true)
val user = repository.getUser()
_userState.value = UserState(user = user, loading = false)
}
}
// 屏幕旋转后,userState 仍然保持之前的值
}
使用 SavedStateHandle 持久化状态:
kotlin
kotlin
// 对于需要跨进程死亡保持的状态,使用 SavedStateHandle
class SettingsViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 从 SavedStateHandle 恢复状态
private val _themeState = MutableStateFlow(
savedStateHandle.get<String>("theme")?.let { themeString ->
Theme.valueOf(themeString)
} ?: Theme.LIGHT
)
val themeState: StateFlow<Theme> = _themeState.asStateFlow()
init {
// 保存状态到 SavedStateHandle
viewModelScope.launch {
_themeState.collect { theme ->
savedStateHandle["theme"] = theme.name
}
}
}
fun setTheme(theme: Theme) {
_themeState.value = theme
}
}
处理复杂状态的配置变更:
kotlin
kotlin
class ComplexViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 复杂状态序列化/反序列化
companion object {
private const KEY_COMPLEX_STATE = "complex_state"
}
private val _complexState = MutableStateFlow(
savedStateHandle.get<String>(KEY_COMPLEX_STATE)?.let { json ->
// 自定义反序列化
Gson().fromJson(json, ComplexState::class.java)
} ?: ComplexState()
)
val complexState: StateFlow<ComplexState> = _complexState.asStateFlow()
init {
// 保存状态
viewModelScope.launch {
_complexState.collect { state ->
val json = Gson().toJson(state)
savedStateHandle[KEY_COMPLEX_STATE] = json
}
}
}
// 处理临时状态(不需要持久化)
private val _temporaryState = MutableStateFlow(TemporaryState())
val temporaryState: StateFlow<TemporaryState> = _temporaryState.asStateFlow()
// 注意:temporaryState 不会在配置变更后保持
}
最佳实践总结:
kotlin
kotlin
// 配置变更处理指南
class ConfigurationChangeViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 1. 重要状态使用 SavedStateHandle
val importantState = savedStateHandle.getStateFlow("important", ImportantState())
// 2. 临时状态使用普通 StateFlow
private val _temporaryState = MutableStateFlow(TemporaryState())
val temporaryState: StateFlow<TemporaryState> = _temporaryState.asStateFlow()
// 3. 大对象考虑分片保存
val largeObjectPart1 = savedStateHandle.getStateFlow("part1", Part1())
val largeObjectPart2 = savedStateHandle.getStateFlow("part2", Part2())
// 4. 使用自定义序列化器处理复杂对象
val complexState = savedStateHandle.getStateFlow(
"complex",
ComplexState(),
serializer = ComplexStateSerializer()
)
// 5. 在 onCleared 中清理资源
override fun onCleared() {
super.onCleared()
// 清理数据库连接、文件句柄等
cleanupResources()
}
}
6.5 核心特性详解
6.5.1 必须有初始值
设计哲学 :
StateFlow 要求必须有初始值,这个设计决策基于以下考虑:
- UI 一致性:确保 UI 在初始时就有状态可以显示,避免空白或闪烁
- 类型安全:减少空值检查的负担,提供更好的类型推断
- 可预测性:明确状态的初始情况,便于调试和测试
- 简化逻辑:减少条件分支,代码更简洁
实际意义:
kotlin
kotlin
// StateFlow 必须有初始值
class CounterViewModel : ViewModel() {
// 明确初始状态为0
private val _count = MutableStateFlow(0) // 类型推断为 Int
val count: StateFlow<Int> = _count
// 在 Compose 中直接使用,无需空检查
@Composable
fun CounterScreen(viewModel: CounterViewModel) {
val count by viewModel.count.collectAsState()
// 可以直接使用 count,它不会是 null
Text("Count: $count")
}
}
// 对比:LiveData 可能为 null
class LiveDataViewModel : ViewModel() {
private val _count = MutableLiveData<Int>() // 初始为 null!
val count: LiveData<Int> = _count
// 使用时需要空检查
viewModel.count.observe(this) { count ->
// count 可能是 null!
val safeCount = count ?: 0
updateCount(safeCount)
}
}
初始值的选择策略:
kotlin
kotlin
// 1. 简单类型的初始值
val stringState = MutableStateFlow("") // 空字符串
val intState = MutableStateFlow(0) // 0
val booleanState = MutableStateFlow(false) // false
// 2. 可空类型的初始值
val nullableUser = MutableStateFlow<User?>(null) // 明确表示没有用户
val nullableList = MutableStateFlow<List<String>?>(null) // 明确表示未加载
// 3. 复杂对象的初始值
data class UserState(
val user: User? = null,
val loading: Boolean = false,
val error: String? = null
)
val userState = MutableStateFlow(UserState()) // 使用默认值
// 或者
val userState = MutableStateFlow(UserState(loading = true)) // 初始加载状态
// 4. 密封类的初始值
sealed interface UiState {
object Loading : UiState
data class Success(val data: Data) : UiState
data class Error(val message: String) : UiState
}
val uiState = MutableStateFlow<UiState>(UiState.Loading) // 初始为加载状态
6.5.2 自动去重
去重机制的重要性 :
在 UI 开发中,频繁的状态更新会导致:
- 性能问题:不必要的 UI 重组和重绘
- 用户体验差:UI 闪烁或卡顿
- 资源浪费:CPU 和内存的无效消耗
StateFlow 的去重原理:
kotlin
kotlin
// StateFlow 内部使用 equals() 方法比较值
val state = MutableStateFlow(0)
// 连续设置相同的值
state.value = 1
state.value = 1 // 不会触发更新(equals() 返回 true)
state.value = 1 // 不会触发更新
// 只有值变化时才会通知收集者
state.collect { value ->
println("值变化: $value") // 只打印一次
}
// 对于数据类,使用 equals() 比较
data class User(val name: String, val age: Int)
val userFlow = MutableStateFlow(User("Alice", 25))
userFlow.value = User("Alice", 25) // 相同对象,不会触发更新
userFlow.value = User("Alice", 26) // 不同对象,触发更新
性能优化场景:
kotlin
kotlin
// 高频更新场景:动画或实时数据
class AnimationViewModel : ViewModel() {
val progress = MutableStateFlow(0f)
fun startAnimation() {
viewModelScope.launch {
var currentProgress = 0f
while (currentProgress < 1f) {
currentProgress += 0.01f
progress.value = currentProgress // 可能很多重复值(浮点数精度问题)
delay(16) // 约60fps
}
}
}
}
// UI 收集:由于自动去重,不会频繁重组
@Composable
fun AnimationScreen(viewModel: AnimationViewModel) {
val progress by viewModel.progress.collectAsState()
// 只有 progress 真正变化时才会重组
AnimatedProgressBar(progress = progress)
}
// 解决浮点数精度问题
class PreciseAnimationViewModel : ViewModel() {
private val _progress = MutableStateFlow(0f)
val progress: StateFlow<Float> = _progress.asStateFlow()
fun updateProgress(newValue: Float) {
// 使用阈值避免微小变化触发更新
if (abs(_progress.value - newValue) > 0.001f) {
_progress.value = newValue
}
}
}
自定义去重逻辑:
kotlin
scss
// 使用 distinctUntilChanged 操作符自定义去重
val customFlow = flow {
emit(1)
emit(2)
emit(2) // 重复值
emit(3)
}
// 默认去重(基于 equals)
customFlow.distinctUntilChanged()
.collect { println(it) } // 输出: 1, 2, 3
// 自定义去重条件
customFlow.distinctUntilChanged { old, new ->
// 只过滤连续相同的偶数
old % 2 == 0 && new % 2 == 0 && old == new
}.collect { println(it) } // 输出: 1, 2, 2, 3(第二个2没有被过滤)
// 基于特定属性去重
data class Person(val id: Int, val name: String)
val peopleFlow = flowOf(
Person(1, "Alice"),
Person(1, "Alice"), // 相同id和name
Person(1, "Bob"), // 相同id,不同name
Person(2, "Charlie")
)
peopleFlow.distinctUntilChanged { old, new ->
old.id == new.id && old.name == new.name
}.collect { println(it) } // 输出: Person(1, Alice), Person(1, Bob), Person(2, Charlie)
6.5.3 高并发安全
并发问题的挑战 :
在多线程环境中,状态更新可能遇到:
- 竞态条件:多个线程同时修改状态,结果不可预测
- 数据不一致:部分更新被覆盖,导致状态不一致
- 内存可见性:一个线程的修改对其他线程不可见
StateFlow 的线程安全实现:
kotlin
kotlin
// StateFlow 内部使用原子操作保证线程安全
class ConcurrentViewModel : ViewModel() {
val counter = MutableStateFlow(0)
fun incrementFromMultipleThreads() {
// 多个协程同时更新
repeat(100) { i ->
viewModelScope.launch(Dispatchers.Default) {
// StateFlow 内部使用原子操作,保证线程安全
counter.value = counter.value + 1
}
}
}
}
// 内部实现简化(实际更复杂):
class ThreadSafeStateFlow<T>(
private var value: T
) {
private val lock = ReentrantLock()
fun compareAndSet(expect: T, update: T): Boolean {
lock.withLock {
return if (value == expect) {
value = update
// 通知所有订阅者
true
} else {
false
}
}
}
var value: T
get() = lock.withLock { value }
set(newValue) {
lock.withLock {
if (value != newValue) {
value = newValue
// 通知订阅者
}
}
}
}
实际并发示例:
kotlin
kotlin
class StockTickerViewModel : ViewModel() {
private val _stockPrices = MutableStateFlow<Map<String, Double>>(emptyMap())
val stockPrices: StateFlow<Map<String, Double>> = _stockPrices
// ❌ 错误:非原子更新,可能导致数据丢失
fun updateStockPriceUnsafe(symbol: String, price: Double) {
val current = _stockPrices.value.toMutableMap()
current[symbol] = price
_stockPrices.value = current // 可能覆盖其他线程的更新
}
// ✅ 正确:使用原子更新
fun updateStockPrice(symbol: String, price: Double) {
var success = false
while (!success) {
val current = _stockPrices.value
val updated = current.toMutableMap().apply {
put(symbol, price)
}
// 使用原子操作确保一致性
success = (_stockPrices as? AtomicReference)?.compareAndSet(current, updated) ?: true
}
}
// ✅ 更好的方式:使用 update 函数
fun safeUpdateStockPrice(symbol: String, price: Double) {
_stockPrices.update { current ->
current.toMutableMap().apply {
put(symbol, price)
}
}
}
// ✅ 批量更新
fun updateMultiplePrices(updates: Map<String, Double>) {
_stockPrices.update { current ->
val newMap = current.toMutableMap()
newMap.putAll(updates)
newMap
}
}
}
StateFlow 的 update 函数:
kotlin
kotlin
// MutableStateFlow 提供了 update 函数用于原子更新
class UserViewModel : ViewModel() {
private val _userState = MutableStateFlow(UserState())
val userState: StateFlow<UserState> = _userState.asStateFlow()
// 使用 update 进行原子更新
fun updateUserInfo(newName: String, newAge: Int) {
_userState.update { current ->
// 这个 lambda 在原子操作中执行
current.copy(
name = newName,
age = newAge,
lastUpdated = System.currentTimeMillis()
)
}
}
// 复杂的原子更新
fun addUserTag(newTag: String) {
_userState.update { current ->
// 确保线程安全的复杂更新
val existingTags = current.tags.toMutableSet()
if (existingTags.add(newTag)) {
current.copy(
tags = existingTags.toList(),
tagCount = existingTags.size
)
} else {
current // 没有变化,返回原对象(不会触发更新)
}
}
}
// 条件更新
fun incrementCounterIf(condition: Boolean) {
_userState.update { current ->
if (condition) {
current.copy(counter = current.counter + 1)
} else {
current
}
}
}
}
并发性能优化:
kotlin
kotlin
// 避免在 update 中执行耗时操作
class OptimizedViewModel : ViewModel() {
private val _data = MutableStateFlow(Data())
// ❌ 错误:在 update 中执行耗时操作
fun updateWithHeavyOperationBad(newValue: Data) {
_data.update { current ->
// 耗时操作会阻塞其他更新
val processed = heavyComputation(newValue)
current.copy(value = processed)
}
}
// ✅ 正确:先计算,再更新
fun updateWithHeavyOperationGood(newValue: Data) {
viewModelScope.launch(Dispatchers.Default) {
// 在后台线程执行耗时操作
val processed = heavyComputation(newValue)
// 回到主线程更新 StateFlow
withContext(Dispatchers.Main) {
_data.value = _data.value.copy(value = processed)
}
}
}
// ✅ 使用缓冲减少更新频率
private val updateBuffer = Channel<Data>(capacity = Channel.UNLIMITED)
init {
// 启动一个协程处理缓冲的更新
viewModelScope.launch {
for (newData in updateBuffer) {
_data.value = newData
}
}
}
fun bufferedUpdate(newValue: Data) {
viewModelScope.launch {
updateBuffer.send(newValue)
}
}
}
6.6 常见陷阱与对策
陷阱1:更新数据类不使用 copy()
问题表现:
kotlin
kotlin
// ❌ 错误示例
data class UserState(
val name: String,
val age: Int,
val tags: MutableList<String> = mutableListOf() // 可变集合!
)
val state = MutableStateFlow(UserState("Alice", 25))
// 直接修改可变属性
state.value.tags.add("new tag") // UI 不会更新!
// 原因:StateFlow 通过 === 比较对象引用
// 直接修改对象属性不会改变对象引用,StateFlow 认为没有变化
解决方案:
kotlin
kotlin
// ✅ 正确:使用 copy() 创建新对象
data class ImmutableUserState(
val name: String,
val age: Int,
val tags: List<String> = emptyList() // 不可变集合
)
val state = MutableStateFlow(ImmutableUserState("Alice", 25))
// 更新时创建新对象
state.value = state.value.copy(
tags = state.value.tags + "new tag" // 创建新列表
)
// ✅ 对于复杂更新,使用 update 扩展函数
fun MutableStateFlow<ImmutableUserState>.addTag(newTag: String) {
update { current ->
current.copy(
tags = current.tags + newTag,
lastUpdated = System.currentTimeMillis()
)
}
}
// 使用
state.addTag("developer")
最佳实践:
kotlin
kotlin
// 1. 始终使用不可变数据结构
data class AppState(
val items: List<Item> = emptyList(), // 不可变列表
val config: Map<String, String> = emptyMap(), // 不可变Map
val user: User = User.EMPTY // 不可变数据类
)
// 2. 提供更新辅助函数
class UserViewModel : ViewModel() {
private val _state = MutableStateFlow(UserState())
val state: StateFlow<UserState> = _state.asStateFlow()
// 提供类型安全的更新方法
fun updateName(name: String) {
_state.update { it.copy(name = name) }
}
fun addItem(item: Item) {
_state.update { it.copy(items = it.items + item) }
}
fun clearItems() {
_state.update { it.copy(items = emptyList()) }
}
}
// 3. 使用 Sealed Class 表示状态
sealed interface DataState {
data class Success(val data: Data) : DataState
data class Error(val message: String) : DataState
object Loading : DataState
}
// 更新时创建全新实例
val state = MutableStateFlow<DataState>(DataState.Loading)
state.value = DataState.Success(Data()) // 总是创建新对象
陷阱2:在非主线程更新 UI
问题表现:
kotlin
scss
// ❌ 错误:在后台线程更新 StateFlow
viewModelScope.launch(Dispatchers.IO) {
val data = fetchData()
// 危险!在 IO 线程更新 StateFlow,UI 收集可能在错误线程
_uiState.value = UiState.Success(data)
}
// 可能导致:
// 1. UI 更新在后台线程执行,可能崩溃
// 2. 并发修改导致状态不一致
// 3. 违反 Android 的 UI 线程规则
解决方案:
kotlin
scss
// ✅ 正确:确保在主线程更新
viewModelScope.launch(Dispatchers.IO) {
val data = fetchData()
// 切换到主线程更新
withContext(Dispatchers.Main) {
_uiState.value = UiState.Success(data)
}
}
// ✅ 更好的方式:利用 ViewModelScope 默认在主线程
viewModelScope.launch {
// 开始在主线程
_uiState.value = UiState.Loading
// 切换到 IO 线程执行耗时操作
val data = withContext(Dispatchers.IO) {
fetchData()
}
// 自动回到主线程
_uiState.value = UiState.Success(data)
}
// ✅ 使用 StateFlow 的线程安全性
class ThreadSafeViewModel : ViewModel() {
private val _data = MutableStateFlow(Data())
// StateFlow 本身是线程安全的,但 UI 收集需要在主线程
fun updateData() {
viewModelScope.launch {
// 在主线程启动
_data.value = Data(loading = true)
// 在 IO 线程获取数据
val newData = withContext(Dispatchers.IO) {
fetchData()
}
// 回到主线程更新(viewModelScope 使用 Main 调度器)
_data.value = newData
}
}
}
最佳实践:
kotlin
kotlin
// 1. 明确线程约束
@MainThread
fun updateUIState(state: UiState) {
_uiState.value = state
}
// 2. 使用扩展函数封装
fun <T> MutableStateFlow<T>.updateOnMain(transform: (T) -> T) {
// 确保在主线程执行
if (Looper.myLooper() == Looper.getMainLooper()) {
value = transform(value)
} else {
Handler(Looper.getMainLooper()).post {
value = transform(value)
}
}
}
// 3. 在 Compose 中确保主线程
@Composable
fun UserScreen(viewModel: UserViewModel) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// Compose 的 collectAsState 确保在主线程收集
LaunchedEffect(uiState) {
// 这里已经在主线程
if (uiState is UiState.Success) {
// 执行 UI 相关操作
}
}
}
陷阱3:在 Compose 中错误收集
问题表现:
kotlin
kotlin
// ❌ 错误:每次重组都创建新的收集
@Composable
fun MyScreen(viewModel: MyViewModel) {
var state by remember { mutableStateOf<UiState>(UiState.Loading) }
LaunchedEffect(Unit) { // key 为 Unit,每次重组都会执行
viewModel.state.collect {
state = it // 每次重组都会创建新的收集协程!
}
}
// 导致:
// 1. 内存泄漏:旧的收集协程可能没有取消
// 2. 重复处理:同一个事件可能被处理多次
// 3. 性能问题:频繁创建和取消协程
}
解决方案:
kotlin
kotlin
// ✅ 正确:使用 collectAsStateWithLifecycle
@Composable
fun MyScreen(viewModel: MyViewModel) {
val state by viewModel.state.collectAsStateWithLifecycle()
// 自动处理生命周期,只在活跃时收集
// 渲染 UI
when (state) {
is UiState.Loading -> LoadingIndicator()
is UiState.Success -> Content(state.data)
is UiState.Error -> ErrorMessage(state.message)
}
}
// ✅ 正确:使用 produceState 手动管理
@Composable
fun MyScreenManual(viewModel: MyViewModel) {
val state by produceState<UiState>(
initialValue = UiState.Loading,
key1 = viewModel
) {
// 只在 Composition 进入时收集一次
viewModel.state.collect { value = it }
}
}
// ✅ 正确:对于需要参数的情况
@Composable
fun UserScreen(userId: String) {
val viewModel: UserViewModel = viewModel(
factory = UserViewModelFactory(userId)
)
val state by viewModel.state.collectAsStateWithLifecycle()
// 当 userId 变化时,ViewModel 会重新创建
// collectAsStateWithLifecycle 会自动处理
}
最佳实践:
kotlin
kotlin
// 1. 始终使用 collectAsStateWithLifecycle
@Composable
fun SafeCollector(viewModel: MyViewModel) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// 推荐:自动处理生命周期
// 对于多个状态,分别收集
val userState by viewModel.userState.collectAsStateWithLifecycle()
val settingsState by viewModel.settingsState.collectAsStateWithLifecycle()
// 避免在 LaunchedEffect 中直接收集
}
// 2. 合理使用 remember 缓存
@Composable
fun EfficientScreen(viewModel: MyViewModel) {
val items by viewModel.items.collectAsStateWithLifecycle()
// 使用 remember 避免不必要的计算
val filteredItems = remember(items) {
items.filter { it.isActive }
}
// 使用 derivedStateOf 处理派生状态
val hasActiveItems = remember(items) {
derivedStateOf {
items.any { it.isActive }
}
}.value
// 渲染
ItemList(items = filteredItems, hasActiveItems = hasActiveItems)
}
// 3. 处理配置变更
@Composable
fun ConfigurationAwareScreen(viewModel: MyViewModel) {
// ViewModel 会自动处理配置变更
val state by viewModel.state.collectAsStateWithLifecycle()
// 本地 UI 状态使用 rememberSaveable
var expanded by rememberSaveable { mutableStateOf(false) }
var selectedItem by rememberSaveable { mutableStateOf(0) }
// 屏幕旋转后,ViewModel 状态保持,本地状态通过 rememberSaveable 恢复
}
陷阱4:StateFlow 与 SharedFlow 混淆使用
问题表现:
kotlin
java
// ❌ 错误:用 SharedFlow 管理 UI 状态
val uiState = MutableSharedFlow<UiState>()
// 问题:缺少初始值,新订阅者看不到当前状态
// ❌ 错误:用 StateFlow 做事件总线
val events = MutableStateFlow<Event?>(null)
// 问题:只能重播一个事件,可能丢失事件
// 导致:
// 1. UI 状态不一致:新进入的页面看不到当前状态
// 2. 事件丢失:快速连续的事件可能被覆盖
// 3. 配置错误:缓冲区大小、溢出策略等不匹配
解决方案:
kotlin
ini
// ✅ 正确:根据场景选择
// UI 状态管理 → StateFlow
val uiState = MutableStateFlow(UiState.Initial)
// 事件总线 → SharedFlow (replay=0)
val events = MutableSharedFlow<Event>()
// 实时数据 → SharedFlow (replay=1)
val realtimeData = MutableSharedFlow<Data>(replay = 1)
// ✅ 选择指南:
// 1. 需要初始值和自动去重 → StateFlow
// 2. 需要重播多个值 → SharedFlow (replay=N)
// 3. 一次性事件 → SharedFlow (replay=0)
// 4. 高频数据 → SharedFlow (适当缓冲区)
实际场景区分:
kotlin
kotlin
// 场景1:用户登录状态(StateFlow)
class AuthViewModel : ViewModel() {
private val _authState = MutableStateFlow<AuthState>(AuthState.NotLoggedIn)
val authState: StateFlow<AuthState> = _authState.asStateFlow()
fun login(user: User) {
_authState.value = AuthState.LoggedIn(user)
}
}
// 场景2:Toast消息(SharedFlow - 事件总线)
object ToastManager {
private val _toastEvents = MutableSharedFlow<String>()
val toastEvents: SharedFlow<String> = _toastEvents.asSharedFlow()
suspend fun showToast(message: String) {
_toastEvents.emit(message)
}
}
// 场景3:实时位置更新(SharedFlow - 实时数据)
class LocationManager {
private val _locationUpdates = MutableSharedFlow<Location>(
replay = 1, // 新订阅者获得最新位置
extraBufferCapacity = 10
)
val locationUpdates: SharedFlow<Location> = _locationUpdates.asSharedFlow()
}
// 场景4:搜索建议(冷流转热流)
class SearchViewModel : ViewModel() {
val searchResults = searchQueryFlow
.debounce(300)
.flatMapLatest { query ->
performSearch(query)
}
.shareIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
replay = 1 // 新订阅者看到最新结果
)
}
混合使用模式:
kotlin
kotlin
// 同时使用 StateFlow 和 SharedFlow
class HybridViewModel : ViewModel() {
// StateFlow:UI 状态
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// SharedFlow:事件
private val _events = MutableSharedFlow<UiEvent>()
val events: SharedFlow<UiEvent> = _events.asSharedFlow()
fun performAction() {
viewModelScope.launch {
// 更新状态
_uiState.value = _uiState.value.copy(loading = true)
try {
val result = repository.getData()
// 更新状态
_uiState.value = UiState(data = result)
// 发送事件
_events.emit(UiEvent.Success("操作成功"))
} catch (e: Exception) {
// 更新状态
_uiState.value = _uiState.value.copy(error = e.message)
// 发送事件
_events.emit(UiEvent.Error("操作失败"))
}
}
}
}
// UI层同时处理状态和事件
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// 收集状态
launch {
viewModel.uiState.collect { state ->
updateUI(state)
}
}
// 收集事件
launch {
viewModel.events.collect { event ->
when (event) {
is UiEvent.Success -> showToast(event.message)
is UiEvent.Error -> showErrorDialog(event.message)
}
}
}
}
}
陷阱5:忽略 StateFlow 的取消传播
问题表现:
kotlin
scss
// ❌ 错误:StateFlow 收集被取消时,资源可能泄漏
viewModelScope.launch {
stateFlow.collect { state ->
val resource = openExpensiveResource() // 可能泄漏
useResource(resource, state)
// 如果收集被取消,可能不会执行后续清理
}
}
// 可能导致:
// 1. 资源泄漏:文件句柄、数据库连接等未关闭
// 2. 内存泄漏:对象未被释放
// 3. 状态不一致:操作被中途取消
解决方案:
kotlin
scss
// ✅ 正确:使用 try-finally 确保资源清理
viewModelScope.launch {
stateFlow.collect { state ->
val resource = openExpensiveResource()
try {
useResource(resource, state)
} finally {
// 确保清理
resource.close()
}
}
}
// ✅ 正确:使用协程的取消检查
viewModelScope.launch {
stateFlow.collect { state ->
// 方式1:使用 ensureActive
ensureActive() // 如果协程已取消,抛出 CancellationException
val resource = openExpensiveResource()
try {
useResource(resource, state)
} finally {
resource.close()
}
// 方式2:检查 isActive
if (!isActive) {
return@collect
}
}
}
// ✅ 使用协程作用域管理资源
viewModelScope.launch {
stateFlow.collect { state ->
coroutineScope {
val resource = openExpensiveResource()
// 在子协程中使用资源
val job = launch {
useResource(resource, state)
}
// 确保资源清理
job.invokeOnCompletion { cause ->
resource.close()
if (cause != null) {
// 处理异常
}
}
job.join()
}
}
}
最佳实践:
kotlin
kotlin
// 1. 创建资源安全的收集扩展函数
suspend inline fun <T> Flow<T>.collectSafely(
crossinline action: suspend (value: T) -> Unit
) {
collect { value ->
ensureActive()
action(value)
}
}
// 使用
stateFlow.collectSafely { state ->
val resource = openResource()
try {
process(state, resource)
} finally {
resource.close()
}
}
// 2. 使用 use 函数管理资源
suspend fun processWithResource(state: State) {
openResource().use { resource ->
ensureActive()
process(state, resource)
}
}
// 3. 处理取消的恢复逻辑
class ResilientViewModel : ViewModel() {
private val _state = MutableStateFlow(Data())
fun startProcessing() {
viewModelScope.launch {
_state.collect { data ->
try {
processData(data)
} catch (e: CancellationException) {
// 正常取消,记录日志但不处理
log("Processing cancelled")
throw e // 重新抛出
} catch (e: Exception) {
// 其他异常,恢复处理
recoverFromError(e)
}
}
}
}
private suspend fun recoverFromError(error: Exception) {
// 实现恢复逻辑
delay(1000) // 等待后重试
_state.value = getFallbackData()
}
}
// 4. 使用 SupervisorJob 隔离错误
viewModelScope.launch {
val supervisor = SupervisorJob()
launch(supervisor) {
stateFlow1.collect { /* 处理1 */ }
}
launch(supervisor) {
stateFlow2.collect { /* 处理2 */ }
}
// 一个收集失败不会影响另一个
}
6.7 四大数据流终极对比表
| 维度 | Flow (冷流) | StateFlow (热流) | SharedFlow (热流) | LiveData (Android) |
|---|---|---|---|---|
| 温度类型 | 冷流 | 热流 | 热流 | 热流 |
| 数据模型 | 连续数据流 | 状态容器 | 事件流/广播 | 生命周期感知数据持有者 |
| 初始值 | ❌ 无 | ✅ 必须有 | ⚠️ 可选(通过replay) | ⚠️ 可选 |
| 数据共享 | 每个收集者独立副本 | 多观察者共享状态 | 多订阅者共享数据 | 多观察者共享数据 |
| 重播机制 | ❌ 无 | ✅ 固定重播1个最新值 | ✅ 可配置重播N个值 | ❌ 无标准重播 |
| 自动去重 | ❌ 无 | ✅ 内置去重 | ❌ 无自动去重 | ❌ 无自动去重 |
| 线程安全 | 依赖 flowOn 配置 | ✅ 高并发安全 | ✅ 高并发安全 | ✅ 主线程安全 |
| 生命周期 | 依赖收集者 | 独立于收集者(需配合Lifecycle) | 独立于收集者(需配合Lifecycle) | ✅ 内置生命周期感知 |
| 背压处理 | ✅ 内置(挂起) | ✅ DROP_OLDEST策略 | ✅ 可配置溢出策略 | ❌ 无背压处理 |
| 平台支持 | ✅ Kotlin 多平台 | ✅ Kotlin 多平台 | ✅ Kotlin 多平台 | ❌ 仅 Android |
| 协程集成 | ✅ 深度集成 | ✅ 深度集成 | ✅ 深度集成 | ⚠️ 有限集成(通过扩展) |
| 操作符支持 | ✅ 丰富 Flow 操作符 | ✅ 丰富 Flow 操作符 | ✅ 丰富 Flow 操作符 | ❌ 有限操作符 |
| 发射方式 | emit() | value = 或 update() | emit() / tryEmit() | value = |
| 适用场景 | 网络请求、数据库查询、文件读取 | UI 状态管理、应用状态 | 事件总线、实时数据广播、通知 | Android UI 状态(传统) |
| 性能特点 | 按需执行,资源高效 | 状态容器,自动去重,性能优化 | 灵活配置,适用性广 | 简单易用,Android 优化 |
| 内存占用 | 每个收集者独立占用 | 共享状态,内存效率高 | 共享数据,可配置缓冲区 | 共享数据,中等效率 |
| 错误处理 | catch、retry 操作符 | 需在转换层处理 | 需在转换层处理 | 需在转换层处理 |
| 测试便利性 | ✅ 易于测试 | ✅ 易于测试 | ✅ 易于测试 | ✅ 易于测试 |
| 迁移难度 | 新概念,需要学习 | 中等,需要理解热流概念 | 中等,需要理解配置参数 | 简单,Android 开发者熟悉 |