Kotlin Flow 深度探索与实践指南——上部:基础与核心篇

第1章:初识 Kotlin Flow

1.1 Flow 是什么:官方定义与设计哲学

官方定义

Kotlin Flow 是 Kotlin 协程库中用于处理异步数据流的 API。它是一个基于协程构建的响应式流处理框架,专为异步、按顺序、连续的数据流设计。Flow 是 Kotlin 标准库的一部分,与协程深度集成,提供了一种声明式、可组合的方式来处理随时间变化的值序列。

设计哲学

  1. 声明式编程:使用函数式操作符表达数据转换,而不是命令式控制流
  2. 结构化并发:与协程作用域深度绑定,自动管理资源生命周期
  3. 冷流优先:默认采用按需生产、零共享的冷流模型,确保资源安全
  4. 可组合性:操作符可链式组合,构建复杂的数据处理管道
  5. 协程原生:无缝集成协程上下文,支持挂起函数和结构化取消

1.2 Flow 的生态位:对比 LiveData、ShareFlow、StateFlow、Channel

特性 Flow (冷流) SharedFlow (热流) StateFlow (热流) LiveData Channel
数据模型 连续数据流 事件流/广播 状态容器 生命周期感知数据持有者 点对点通信管道
温度 冷流 热流 热流 热流 热流
生命周期 依赖收集者 独立于收集者 独立于收集者 绑定观察者生命周期 独立于收发者
数据共享 每个收集者独立数据 多收集者共享数据 多观察者共享状态 多观察者共享数据 单次消费
初始值 可选(通过replay) 必须有 可有可无
背压处理 内置(协程挂起) 可配置缓冲区策略 最新值替换 无(主线程安全) 可配置缓冲区
适用场景 网络请求、数据库查询 事件总线、实时数据广播 UI状态管理 Android UI状态 协程间通信

1.3 Flow 的核心价值:为什么 Google 将其作为异步首选?

四大核心价值

  1. 协程原生

    • 无缝集成协程作用域,自动处理取消和资源清理
    • 支持挂起函数,可直接在流操作中使用异步操作
  2. 结构化并发安全

    kotlin

    scss 复制代码
    viewModelScope.launch {
        repository.dataFlow()
            .onEach { updateUi(it) }
            .launchIn(this) // 自动绑定到viewModelScope
    }
  3. 声明式与可组合性

    kotlin

    scss 复制代码
    searchFlow
        .debounce(300)
        .filter { it.length > 2 }
        .distinctUntilChanged()
        .flatMapLatest { query ->
            api.search(query).asFlow()
        }
        .catch { emit(SearchResult.Error(it)) }
  4. 平台无关性

    • 纯 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个典型场景及选择建议:

  1. UI 状态管理 → StateFlow
  2. 事件总线 → SharedFlow
  3. 网络请求 → 普通 Flow(冷流)
  4. 实时数据 → SharedFlow
  5. 一次性操作 → 普通 Flow(冷流)
  6. 多订阅者 → 热流
  7. 资源敏感 → 冷流

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 使用建议

  1. UI状态 :使用 stateIn,replay=1,SharingStarted.Eagerly
  2. 事件总线 :使用 shareIn,replay=0,SharingStarted.Eagerly
  3. 实时数据 :使用 shareIn,replay=1,SharingStarted.WhileSubscribed
  4. 一次性数据:保持冷流特性

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 操作符组合黄金法则

  1. 顺序很重要:操作符按声明顺序执行
  2. 尽早过滤:先用filter减少数据量
  3. 合理防抖:用户输入用debounce,实时数据用sample
  4. 线程优化 :IO操作用flowOn(Dispatchers.IO)
  5. 错误恢复:用catch在适当位置恢复,不要吞掉所有异常
  6. 资源清理:无限流要确保能被取消

快速参考卡片

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 的关键区别在于:

  1. 数据生产与消费解耦:生产者独立运行,不依赖收集者
  2. 多订阅者广播:多个收集者接收相同的数据序列
  3. 可配置重播:新订阅者可以获取历史数据

适用场景

  • 事件总线(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():挂起式发射

特点

  1. 保证送达:如果缓冲区满,会挂起等待直到有空间
  2. 协程内使用:必须在协程作用域内调用
  3. 数据完整性:适合重要数据的发射

代码示例

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():非阻塞发射

特点

  1. 立即返回:不挂起,立即返回发射结果
  2. 任意线程:可以在任何线程调用,包括非协程环境
  3. 可能丢失:缓冲区满时发射失败,数据丢失

代码示例

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()

实际选择指南

  1. 用户交互事件(如按钮点击):

    kotlin

    kotlin 复制代码
    // 重要,不能丢失
    suspend fun onButtonClick() {
        eventFlow.emit(ClickEvent())
    }
  2. 传感器数据

    kotlin

    rust 复制代码
    // 高频,可以丢失
    sensorCallback = { value ->
        sensorFlow.tryEmit(value) // 不关心是否成功
    }
  3. 网络请求结果

    kotlin

    scss 复制代码
    // 重要,需要保证
    viewModelScope.launch {
        val result = api.getData()
        dataFlow.emit(result)
    }
  4. 性能计数器

    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>

转换过程

  1. 创建共享流:内部创建一个 MutableSharedFlow
  2. 启动收集协程:在指定作用域中启动协程收集源流
  3. 数据转发:将源流的数据转发到 SharedFlow
  4. 订阅管理:根据 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)
            }
        }
    }
}

最佳实践总结

  1. UI状态管理StateFlow

    • 必须有初始值
    • 使用 copy() 更新数据类
    • 配合 repeatOnLifecycle 收集
  2. 事件通信SharedFlow (replay=0)

    • 设置合适的缓冲区大小
    • 重要事件用 emit(),高频事件用 tryEmit()
    • 注意生命周期管理
  3. 实时数据SharedFlow (replay=1)

    • 新订阅者立即获得最新值
    • 使用 WhileSubscribed 策略节省资源
    • 设置合理的过期时间
  4. 一次性数据普通Flow

    • 网络请求、数据库查询
    • 使用 flowOn 切换线程
    • 使用 catch 处理错误
  5. 兼容旧代码LiveData

    • 使用 asLiveData() 转换
    • 逐步迁移,不强制替换
    • 新功能优先使用Flow

第6章:StateFlow (热流) - 新一代状态容器

6.1 StateFlow 是什么?

6.1.1 官方定义与核心特性

StateFlow 的定义

StateFlow 是 Kotlin 协程库中专门为 UI 状态管理设计的热流实现。它是 SharedFlow 的一个特殊配置变体,针对状态管理场景进行了优化。

为什么需要 StateFlow?

  1. 状态管理痛点:传统的 LiveData 虽然简单,但缺少协程集成和丰富的操作符
  2. 跨平台需求:需要一套能在 Android、iOS、后端统一使用的状态管理方案
  3. 性能优化:需要自动去重、线程安全等特性来优化 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 的局限性

  1. 协程集成有限:LiveData 需要额外扩展才能与协程深度集成
  2. 仅限 Android:LiveData 是 Android 专属,无法用于跨平台开发
  3. 操作符有限:缺少 Flow 丰富的操作符支持
  4. 背压处理不足:没有内置的背压处理机制

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 部分刷新导致的闪烁或不一致。

为什么需要状态合并?

  1. 避免UI闪烁:如果每个数据源独立更新,UI 会多次重组
  2. 保证数据一致性:确保相关数据同时更新,避免显示过时组合
  3. 简化UI逻辑:UI 只需处理一个完整状态,而不是多个独立状态
  4. 减少资源消耗:一次更新代替多次更新

状态合并示例

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 的工作原理

  1. 监听所有输入流:同时监控 productFlow、reviewsFlow 等
  2. 等待数据就绪:当任何一个流发出新值时,collect 所有流的最新值
  3. 重新计算状态:使用所有最新值重新计算 ProductDetailState
  4. 发射统一状态:将新的状态发射给 UI 层
  5. 保证原子性:UI 一次性获得所有更新,确保显示一致性

6.3.2 状态派生:派生状态管理

什么是派生状态?

派生状态是指基于现有状态计算得出的新状态。这些状态不需要存储在数据库中,而是根据已有状态实时计算得出。

为什么需要派生状态?

  1. 避免重复计算:相同的计算逻辑可能被多个地方使用
  2. 保持一致性:确保所有地方使用的派生状态计算结果一致
  3. 性能优化:通过缓存派生状态避免重复计算
  4. 关注点分离:将计算逻辑与业务逻辑分离

派生状态示例

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 组件只订阅自己关心的状态片段,避免不必要的更新。

为什么需要状态分片?

  1. 减少不必要的更新:当状态某个字段变化时,只更新关心该字段的组件
  2. 提高性能:减少 UI 重组次数,特别是对于复杂 UI
  3. 更好的模块化:每个组件只关心自己的状态,降低耦合度
  4. 便于测试:可以独立测试每个状态片段

状态分片示例

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 中,传统的流收集方式存在生命周期管理问题:

  1. 配置变更问题:屏幕旋转时协程重启,可能导致重复订阅
  2. 内存泄漏风险:Activity/Fragment 销毁后协程可能仍在运行
  3. 资源浪费:不可见时仍在处理数据,浪费 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 的工作原理

  1. 状态监听:监听生命周期的状态变化

  2. 智能启停

    • 当生命周期进入目标状态(如 STARTED)时,启动收集协程
    • 当生命周期离开目标状态时,取消收集协程
  3. 自动恢复

    • 如果生命周期重新进入目标状态,重新启动收集协程
  4. 避免重复:确保同一时间只有一个收集协程运行

使用示例

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 中,不正确的状态收集会导致:

  1. 过度重组:每次重组都重新收集,性能低下
  2. 内存泄漏:未正确管理生命周期的收集
  3. 状态不一致:收集时机不当导致 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 会被销毁并重新创建。这会导致:

  1. 状态丢失:UI 状态和临时数据丢失
  2. 资源浪费:重新执行初始化操作
  3. 用户体验差:数据需要重新加载

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 要求必须有初始值,这个设计决策基于以下考虑:

  1. UI 一致性:确保 UI 在初始时就有状态可以显示,避免空白或闪烁
  2. 类型安全:减少空值检查的负担,提供更好的类型推断
  3. 可预测性:明确状态的初始情况,便于调试和测试
  4. 简化逻辑:减少条件分支,代码更简洁

实际意义

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 开发中,频繁的状态更新会导致:

  1. 性能问题:不必要的 UI 重组和重绘
  2. 用户体验差:UI 闪烁或卡顿
  3. 资源浪费: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 高并发安全

并发问题的挑战

在多线程环境中,状态更新可能遇到:

  1. 竞态条件:多个线程同时修改状态,结果不可预测
  2. 数据不一致:部分更新被覆盖,导致状态不一致
  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 开发者熟悉
相关推荐
HashTang2 小时前
【AI 编程实战】第 5 篇:Pinia 状态管理 - 从混乱代码到优雅架构
前端·vue.js·ai编程
Bug生活20482 小时前
五年断更,AI助我半天复活小程序
前端·微信小程序·ai编程
狗头大军之江苏分军2 小时前
Node.js 性能优化实践,但老板只关心是否能跑
前端·后端
恋猫de小郭2 小时前
2025 年终醒悟,AI 让我误以为自己很强,未来程序员的转型之路
android·前端·flutter
用泥种荷花2 小时前
【前端学习AI】PromptTemplate的使用
前端
狗头大军之江苏分军2 小时前
Node.js 真香,但每次部署都想砸电脑
前端·javascript·后端
Shi_haoliu2 小时前
inno setup6.6.1实例,制作安装包,创建共享文件夹,写入注册表(提供给excel加载项,此文章解释iss文件)
前端·vue.js·windows·excel
MediaTea2 小时前
Python:实例 __dict__ 详解
java·linux·前端·数据库·python
2501_915918412 小时前
iOS 开发中证书创建与管理中的常见问题
android·ios·小程序·https·uni-app·iphone·webview