You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin StateFlow切后台再前台重复请求API的问题求助

解决Kotlin Flow后台恢复后重复API请求的问题

问题场景

使用Kotlin Flow实现API调用,ViewModel通过StateFlow向Activity暴露数据。应用启动时API请求正常,Activity能成功收集数据;但将应用置于后台5秒后再打开,API会再次发起请求,需要避免这种重复请求。

现有代码

ViewModel代码

class FlowViewModel(
    dataSource: DataSource
) : BaseViewModel<UiState>() {

    val priceFlow: StateFlow<UiState> = dataSource
        .apiCall
        .onStart {
            Timber.tag("Flow").d("Flow has started.")
        }
        .map { stockList ->
            UiState.Success(stockList) as UiState
        }
        .onCompletion {
            Timber.tag("Flow").d("Flow has completed.")
        }.stateIn(
            scope = viewModelScope,
            initialValue = UiState.Loading,
            started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000)
        )
}

Activity收集Flow代码

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.priceFlow.collect { uiState ->
            render(uiState)
        }
    }
}

模拟DataSource代码

class DataSource(mockApi: MockApi) : BaseDataSource {

    override val apiCall: Flow<List<Price>> = flow {
        val currentStockList = mockApi.getCurrentPrices()
        emit(currentStockList)
    }
}

问题原因

  1. StateFlow共享策略:当前使用SharingStarted.WhileSubscribed(5000),表示最后一个订阅者(Activity)取消订阅后,等待5秒就会停止Flow共享,上游的API请求Flow会被取消。
  2. 冷Flow特性:apiCall是一个冷Flow,每次被订阅时都会重新执行代码块,发起新的API请求。当应用回到前台,Activity重新订阅StateFlow,会触发上游冷Flow再次执行,导致重复请求。

解决方案

可以通过以下两种核心思路解决,根据业务需求选择:

方案一:修改StateFlow的共享策略,保持共享状态

将stateIn的started参数改为SharingStarted.Lazily或设置无限长的超时时间,让Flow共享在ViewModel生命周期内持续保持,避免上游Flow被取消后重新执行:

// 方案1a:使用Lazily,第一次订阅启动后持续共享直到ViewModel销毁
.stateIn(
    scope = viewModelScope,
    initialValue = UiState.Loading,
    started = SharingStarted.Lazily
)

// 方案1b:设置无限超时,避免订阅取消后停止共享
.stateIn(
    scope = viewModelScope,
    initialValue = UiState.Loading,
    started = SharingStarted.WhileSubscribed(stopTimeoutMillis = Long.MAX_VALUE)
)

方案二:缓存API请求结果,避免重复调用

在DataSource或ViewModel中缓存API结果,即使Flow重新订阅,也直接返回缓存数据而不是发起新请求:

修改DataSource缓存结果

将apiCall转为热Flow,只请求一次并缓存结果:

class DataSource(mockApi: MockApi) : BaseDataSource {
    override val apiCall: Flow<List<Price>> = flow {
        val currentStockList = mockApi.getCurrentPrices()
        emit(currentStockList)
    }.shareIn(
        scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
        started = SharingStarted.Lazily, // 第一次订阅时发起请求
        replay = 1 // 缓存最新的1条结果
    )
}
在ViewModel中缓存结果

如果需要更灵活的缓存控制,可在ViewModel中存储已请求的数据:

class FlowViewModel(
    dataSource: DataSource
) : BaseViewModel<UiState>() {

    private var cachedStockList: List<Price>? = null

    val priceFlow: StateFlow<UiState> = flow {
        // 优先使用缓存数据
        cachedStockList?.let {
            emit(UiState.Success(it))
            return@flow
        }
        // 无缓存时发起请求并缓存
        val stockList = dataSource.apiCall.first()
        cachedStockList = stockList
        emit(UiState.Success(stockList))
    }.stateIn(
        scope = viewModelScope,
        initialValue = UiState.Loading,
        started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000)
    )
}

内容的提问来源于stack exchange,提问作者Darshan Mistry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 09:51:27