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

Android Clean Architecture下如何用Flow实现两个异步请求并行执行

实现方案

核心逻辑基于Kotlin协程的async+awaitAll或Flow的combine操作符实现并行,完全贴合Clean Architecture的分层职责,不破坏各层边界。

1. 仓库层(Repo)实现

保持原有单请求逻辑不变,两个接口分别对应Repo的两个独立方法,每个方法返回独立Flow,仅负责数据存取逻辑,不做合并操作。
示例代码:

class UserRepo @Inject constructor(
    private val remoteDataSource: UserRemoteDataSource,
    private val localDataSource: UserLocalDataSource
) {
    // 第一个请求:获取用户基础信息
    fun getUserInfo(userId: String): Flow<UserInfo> = flow {
        val result = remoteDataSource.fetchUserInfo(userId)
        // 原有逻辑:缓存到本地、字段转换等
        emit(result)
    }.flowOn(Dispatchers.IO)

    // 第二个请求:获取用户订单列表
    fun getUserOrders(userId: String): Flow<List<Order>> = flow {
        val result = remoteDataSource.fetchUserOrders(userId)
        emit(result)
    }.flowOn(Dispatchers.IO)
}

2. UseCase层实现

并行请求的合并逻辑放在UseCase层,这一层本身就是用来封装业务逻辑的,不会污染Repo和ViewModel层。

场景1:单发射请求(每个接口只返回一次结果)

用async+awaitAll实现严格并行:

class GetUserCombinedDataUseCase @Inject constructor(
    private val userRepo: UserRepo
) {
    operator fun invoke(userId: String): Flow<CombinedUserData> = flow {
        coroutineScope {
            // 同时启动两个异步任务,无先后顺序
            val userInfoDeferred = async { userRepo.getUserInfo(userId).first() }
            val userOrdersDeferred = async { userRepo.getUserOrders(userId).first() }
            // 等待两个请求都完成后合并结果
            val combinedData = CombinedUserData(
                userInfo = userInfoDeferred.await(),
                orders = userOrdersDeferred.await()
            )
            emit(combinedData)
        }
    }.flowOn(Dispatchers.IO)

    // 合并后的数据类,仅在UseCase层做数据适配
    data class CombinedUserData(
        val userInfo: UserInfo,
        val orders: List<Order>
    )
}

场景2:多发射请求(比如Repo先返回本地缓存、再返回远程最新数据)

用combine操作符实现动态合并,任意一个Flow有新数据都会重新合并下发:

operator fun invoke(userId: String): Flow<CombinedUserData> = combine(
    userRepo.getUserInfo(userId),
    userRepo.getUserOrders(userId)
) { userInfo, orders ->
    CombinedUserData(userInfo, orders)
}.flowOn(Dispatchers.IO)

扩展提示:如果需要实现部分请求失败的降级逻辑,可以给每个async单独加try-catch,返回默认兜底数据即可,不需要中断整个并行流程。

3. ViewModel层调用

和原有单个UseCase的调用逻辑完全一致,不需要做额外适配:

class UserViewModel @Inject constructor(
    private val getUserCombinedDataUseCase: GetUserCombinedDataUseCase
) : ViewModel() {
    private val _combinedUiState = MutableStateFlow<UiState<CombinedUserData>>(UiState.Loading)
    val combinedUiState: StateFlow<UiState<CombinedUserData>> = _combinedUiState.asStateFlow()

    fun loadUserData(userId: String) {
        viewModelScope.launch {
            getUserCombinedDataUseCase(userId)
                .catch { e ->
                    _combinedUiState.value = UiState.Error(e.message ?: "请求失败")
                }
                .collect { data ->
                    _combinedUiState.value = UiState.Success(data)
                }
        }
    }
}

方案合规性说明

  • 各层职责单一:Repo仅负责数据存取,UseCase仅负责业务逻辑(并行调度+数据合并),ViewModel仅负责UI状态绑定
  • 无分层依赖破坏:上层仅依赖下层抽象,没有跨层调用
  • 可扩展性强:后续新增并行请求仅需在UseCase中新增对应Repo调用即可

内容的提问来源于stack exchange,提问作者Mahmoud Ayman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 07:24:03