1. 差异
flatMap
- 行为:转换每个输入值到 Flow,并按顺序收集所有生成的 Flow
- 特点:新的输入不会取消之前正在进行的转换
- 使用场景:需要处理所有事件,且事件间有依赖关系或需保持顺序
flatMapLatest
- 行为:当有新输入时,立即取消前一个转换的 Flow
- 特点:只处理最新的输入,忽略中间结果
- 使用场景:只需最新结果,可安全取消旧操作
// 对比示例
fun demonstrateDifference() {
runBlocking {
val flow = flowOf(1, 2, 3).onEach { delay(100) }
// flatMap:处理所有值
flow.flatMap { value ->
flow {
emit("Processing $value")
delay(200) // 模拟耗时操作
emit("Completed $value")
}
}.collect { println("flatMap: $it") }
// 输出所有 1,2,3 的处理结果
// flatMapLatest:只处理最新的
flow.flatMapLatest { value ->
flow {
emit("Latest: $value")
delay(200)
emit("Done: $value") // 可能被取消
}
}.collect { println("flatMapLatest: $it") }
// 只输出 3 的最新结果
}
}
2. 实战场景分析
场景一:搜索自动补全
class SearchViewModel {
private val searchQuery = MutableStateFlow("")
// 使用 flatMapLatest:用户连续输入时取消之前的请求
val searchResults = searchQuery
.debounce(300) // 防抖
.filter { it.length >= 2 }
.flatMapLatest { query ->
flow {
emit(SearchState.Loading)
try {
val results = api.searchAutocomplete(query)
emit(SearchState.Success(results))
} catch (e: Exception) {
emit(SearchState.Error(e))
}
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), SearchState.Idle)
fun onQueryChanged(query: String) {
searchQuery.value = query
}
}
// 错误使用 flatMap 的情况:所有请求都会完成,可能导致结果显示错乱
val wrongResults = searchQuery
.flatMap { query -> // 错误!应该用 flatMapLatest
api.searchAutocompleteFlow(query)
}
场景二:位置更新
class LocationTracker {
private val locationUpdates = locationProvider.getUpdates()
// 使用 flatMap:每个位置都要上传,顺序重要
val uploadStatus = locationUpdates
.filter { it.accuracy < 50 } // 只处理高精度位置
.conflate() // 合并位置更新,避免过快
.flatMap { location ->
flow {
try {
val response = uploadToServer(location)
emit(UploadResult.Success(location.id, response))
} catch (e: Exception) {
emit(UploadResult.Failure(location.id, e))
}
}
}
.catch { e -> emit(UploadResult.NetworkError(e)) }
// 使用 flatMapLatest:只关心最新位置的周边搜索
val nearbyPlaces = locationUpdates
.flatMapLatest { location ->
placesApi.getNearbyPlaces(location.lat, location.lng)
}
}
场景三:文件上传队列
class FileUploadManager {
private val uploadQueue = MutableSharedFlow<FileData>()
// 使用 flatMap + buffer:并发上传但限制并发数
val uploadProgress = uploadQueue
.flatMapMerge(concurrency = 3) { file -> // 限制3个并发
uploadFileFlow(file)
}
.shareIn(ioScope, SharingStarted.Lazily)
private fun uploadFileFlow(file: FileData): Flow<UploadStatus> = flow {
emit(UploadStatus.Progress(file.id, 0))
val chunks = splitIntoChunks(file)
chunks.forEachIndexed { index, chunk ->
api.uploadChunk(file.id, chunk)
val progress = ((index + 1) / chunks.size.toFloat() * 100).toInt()
emit(UploadStatus.Progress(file.id, progress))
}
emit(UploadStatus.Completed(file.id))
}
// 使用 flatMapLatest:用户取消上传时立即停止
fun uploadWithCancel(): Flow<UploadStatus> {
val cancelSignal = MutableStateFlow(false)
return uploadQueue
.flatMapLatest { file ->
if (cancelSignal.value) {
emptyFlow()
} else {
uploadFileFlow(file)
}
}
}
}
3. 常见陷阱与解决方案
陷阱一:资源泄漏
// 错误:flatMapLatest 取消时资源未清理
flow.flatMapLatest { id ->
flow {
val connection = openDatabaseConnection() // 可能泄漏!
try {
val data = connection.query(id)
emit(data)
} finally {
connection.close() // 必须清理
}
}
}
// 正确:使用 cancellable 操作或清理资源
flow.flatMapLatest { id ->
callbackFlow {
val connection = openDatabaseConnection()
try {
val data = connection.query(id)
send(data)
awaitClose()
} finally {
connection.close()
}
}
}
陷阱二:状态不一致
// 错误:状态更新可能被取消,导致不一致
var currentState = State.IDLE
flow.flatMapLatest {
currentState = State.LOADING // 可能执行但后续被取消
apiCallFlow(it).onCompletion {
currentState = State.IDLE // 可能不会执行
}
}
// 正确:使用状态流
val state = MutableStateFlow(State.IDLE)
flow.flatMapLatest {
state.value = State.LOADING
apiCallFlow(it)
.catch { e ->
state.value = State.ERROR(e)
emptyFlow()
}
.onCompletion {
if (it == null) state.value = State.IDLE
}
}
陷阱三:背压处理不当
// 错误:没有处理背压,可能内存溢出
highFrequencyFlow
.flatMapLatest { // 仍然可能快速发射
heavyOperationFlow(it)
}
// 正确:添加背压策略
highFrequencyFlow
.conflate() // 合并中间值
.flatMapLatest {
heavyOperationFlow(it)
}
// 或限制并发
highFrequencyFlow
.flatMapMerge(concurrency = 1) { // 限制为顺序执行
heavyOperationFlow(it)
}
陷阱四:异常处理缺失
// 错误:异常会使整个流终止
flow.flatMapLatest {
flow {
if (it.isEmpty()) throw IllegalArgumentException()
emit(process(it))
}
}
// 正确:妥善处理异常
flow.flatMapLatest {
flow {
try {
if (it.isEmpty()) throw IllegalArgumentException()
emit(Result.Success(process(it)))
} catch (e: Exception) {
emit(Result.Failure(e))
}
}
}
4. 性能优化技巧
// 1. 合并操作减少开销
searchQuery
.debounce(300)
.distinctUntilChanged() // 避免重复查询
.flatMapLatest { query ->
api.search(query)
.retry(2) // 重试机制
.timeout(5000) // 超时
}
// 2. 使用 shareIn 避免重复订阅
val sharedFlow = sourceFlow
.flatMapLatest { expensiveOperation(it) }
.shareIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
replay = 1
)
// 3. 适当使用 buffer
flow
.buffer(Channel.UNLIMITED) // 根据场景选择
.flatMapLatest { /* ... */ }
5. 选择指南
| 场景特征 | 推荐操作符 | 理由 |
|---|---|---|
| 需处理所有输入,顺序重要 | flatMapConcat | 保持顺序,无并发 |
| 需处理所有输入,顺序不重要 | flatMapMerge | 并发处理,提高效率 |
| 只需最新结果,可取消旧操作 | flatMapLatest | 避免无效计算 |
| 有限并发控制 | flatMapMerge with concurrency | 控制资源使用 |
| 冷流转换 | transform | 更轻量级的转换 |
总结
- flatMapLatest 适合响应式 UI 交互(搜索、点击防抖),可避免陈旧数据
- flatMapMerge 适合并行任务处理(批量上传、并发请求),提高吞吐量
- flatMapConcat 适合顺序敏感操作(文件分片上传、数据库事务)
- 始终考虑资源清理,特别是在可取消的操作中
- 合理处理背压,根据场景选择 buffer、conflate 等策略
- 统一异常处理,避免流意外终止
正确选择 flatMap 变体可以显著提升应用性能和用户体验,特别是在处理异步数据流时。