Kotlin协程:类RxJava订阅运行任务、检查状态及预取数据复用
当然有靠谱的实现方案!结合Kotlin协程的特性,我们可以用两种常用思路来搞定这个「页面跳转前预取、到达后复用任务不重复请求」的需求,我给你拆解清楚:
方案一:利用
Deferred实现单次预取复用 Deferred是协程中代表异步任务的对象,它可以让我们在任务启动后,随时等待其结果,非常适合这种单次预取的场景。
核心逻辑
- 在Repository层维护一个可空的
Deferred<Data>引用,用来追踪当前是否有正在进行的预取任务 - 跳转前调用预取方法:只有当没有活跃的预取任务时,才启动协程发起网络请求并赋值给这个
Deferred - 进入目标页面后,直接调用
await()等待结果——如果任务已经完成,直接拿到数据;如果还在运行,就等待它完成,不会触发新请求 - 页面销毁时记得取消任务并清空引用,避免内存泄漏
代码示例
class DataRepository(private val networkManager: NetworkManager) { // 追踪预取任务的Deferred private var prefetchDeferred: Deferred<Data>? = null // 跳转前调用:触发预取 fun prefetchData() { // 检查是否已有活跃的预取任务,避免重复请求 if (prefetchDeferred?.isActive != true) { prefetchDeferred = CoroutineScope(Dispatchers.IO).async { networkManager.fetchDataFromBackend() } } } // 进入页面后调用:获取数据(复用预取任务) suspend fun getData(): Data { return prefetchDeferred?.await() ?: run { // 极端情况:如果预取没触发,这里兜底发起请求 val data = networkManager.fetchDataFromBackend() prefetchDeferred = null // 兜底请求完成后清空,避免下次复用旧任务 data } } // 页面销毁时调用:清理任务 fun clearPrefetchTask() { prefetchDeferred?.cancel() prefetchDeferred = null } }
如果你的场景需要支持多个页面/组件共享预取结果,或者需要后续刷新数据,SharedFlow会是更灵活的选择——它可以让多个收集者共享同一份数据流,且不会重复触发上游请求。
核心逻辑
- 在Repository层创建一个
MutableSharedFlow,设置replay=1来保存最新的请求结果,让新的收集者能直接拿到历史数据 - 跳转前调用预取方法:检查是否已有订阅者或缓存结果,没有的话才发起网络请求并发射结果到Flow中
- 进入页面后,收集这个Flow的结果——如果预取已经完成,直接拿到缓存数据;如果还在运行,就等待结果
代码示例
class DataRepository(private val networkManager: NetworkManager) { // replay=1:保存最新的1条数据,让新收集者能获取历史结果 private val _dataFlow = MutableSharedFlow<Data>(replay = 1) // 对外暴露不可变的SharedFlow val dataFlow: SharedFlow<Data> = _dataFlow // 跳转前调用:触发预取 fun prefetchData() { // 只有当没有订阅者且没有缓存数据时,才发起新请求 if (_dataFlow.subscriptionCount.value == 0 && !_dataFlow.hasValue()) { CoroutineScope(Dispatchers.IO).launch { try { val data = networkManager.fetchDataFromBackend() _dataFlow.emit(data) } catch (e: Exception) { // 这里可以处理异常,比如发射错误状态(建议封装成密封类) // _dataFlow.emit(DataResult.Error(e)) } } } } // 进入页面后调用:获取数据 suspend fun getData(): Data { return dataFlow.first() // 等待第一个结果(有缓存直接返回,无缓存则等待请求完成) } }
关键注意事项
- 协程作用域选择:推荐使用和页面/ViewModel生命周期绑定的作用域(比如
viewModelScope),不要直接用GlobalScope,避免内存泄漏 - 异常处理:一定要在网络请求的协程块里加上
try-catch,处理请求失败的情况——可以给Deferred抛出异常,或者给SharedFlow发射封装好的错误状态(比如用密封类sealed class DataResult) - 任务清理:在页面销毁时(比如ViewModel的
onCleared方法),记得取消未完成的Deferred任务,或者重置SharedFlow的缓存,避免无用的后台任务占用资源 - 数据刷新:如果需要支持数据刷新,要在刷新时清空之前的
Deferred引用,或者调用_dataFlow.resetReplayCache(),确保新的请求能触发
内容的提问来源于stack exchange,提问作者luckyhandler
相关产品推荐
相关产品推荐

