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

整洁架构下如何优雅实现应用启动阶段的串行请求链?

安卓启动串行请求链最佳实践

方案1:一次性启动任务(推荐大多数启动场景)

你第一种UseCase方案的嵌套问题本质是没有利用Result的链式转换能力,同时可以把整条启动链路封装成独立的组合UseCase,避免逻辑散在ViewModel中:

  1. 首先定义启动状态密封类,方便UI感知全链路进度和错误:
sealed interface AppStartupState {
    object Idle : AppStartupState
    object Locating : AppStartupState
    object FetchingServers : AppStartupState
    object FetchingConfig : AppStartupState
    object Success : AppStartupState
    data class Failed(val step: String, val throwable: Throwable) : AppStartupState
}
  1. 封装组合启动UseCase,用flatMap消除嵌套回调:
    如果Kotlin版本标准库Result没有内置flatMap,可以先加个扩展:
inline fun <T, R> Result<T>.flatMap(transform: (T) -> Result<R>): Result<R> {
    return if (isSuccess) transform(getOrThrow()) else Result.failure(exceptionOrNull()!!)
}

组合UseCase实现:

class AppStartupUseCase(
    private val locateUseCase: LocateUseCase,
    private val retrieveServersUseCase: RetrieveServersUseCase,
    private val retrieveRemoteConfigUseCase: RetrieveRemoteConfigUseCase
) {
    suspend operator fun invoke(): Result<Unit> {
        return locateUseCase()
            .flatMap { location -> retrieveServersUseCase(location) }
            .flatMap { servers -> retrieveRemoteConfigUseCase(servers) }
    }
}
  1. ViewModel中调用并下发状态:
class MainViewModel(
    private val startupUseCase: AppStartupUseCase
) : ViewModel() {
    private val _startupState = MutableStateFlow<AppStartupState>(AppStartupState.Idle)
    val startupState: StateFlow<AppStartupState> = _startupState.asStateFlow()

    fun startInit() = viewModelScope.launch {
        // 细粒度状态上报可拆分每步执行逻辑,执行完一步发一次状态
        _startupState.value = AppStartupState.Locating
        val result = startupUseCase()
        result.onSuccess {
            _startupState.value = AppStartupState.Success
        }.onFailure {
            // 可给每个步骤的异常包自定义类型,这里判断异常返回对应错误步骤
            _startupState.value = AppStartupState.Failed("初始化失败", it)
        }
    }
}

该方案逻辑简单直观,取消只需终止viewModelScope即可,重试直接重新调用startInit()即可,适合绝大多数一次性启动初始化场景。

方案2:响应式流式链路(适合后续需要自动更新的场景)

如果后续定位、服务器列表变化时需要自动更新配置,可以用Flow构建链式链路,不建议你通过Hilt注入Flow的实现,会导致依赖耦合度过高,正确实现如下:

class AppStartupFlowUseCase(
    private val locationRepository: LocationRepository,
    private val serverRepository: ServerRepository,
    private val remoteConfigRepository: RemoteConfigRepository
) {
    fun execute(): Flow<AppStartupState> = flow {
        emit(AppStartupState.Locating)
        val location = locationRepository.locate().getOrThrow()
        emit(AppStartupState.FetchingServers)
        val servers = serverRepository.retrieve(location)
        emit(AppStartupState.FetchingConfig)
        remoteConfigRepository.retrieve(servers)
        emit(AppStartupState.Success)
    }.catch { throwable ->
        // 捕获全链路异常,映射对应错误步骤
        val step = when(throwable) {
            is LocationException -> "定位失败"
            is ServerFetchException -> "服务器列表获取失败"
            is ConfigFetchException -> "配置获取失败"
            else -> "初始化失败"
        }
        emit(AppStartupState.Failed(step, throwable))
    }.retryWhen { cause, attempt ->
        // 自定义重试规则,例如定位失败重试2次,其他错误重试1次,间隔1秒
        val maxRetry = if (cause is LocationException) 2 else 1
        if (attempt < maxRetry) {
            delay(1000)
            true
        } else false
    }
}

ViewModel中直接收集Flow即可:

fun startInit() = viewModelScope.launch {
    appStartupFlowUseCase.execute().collect { state ->
        _startupState.value = state
    }
}

该方案原生支持取消、重试逻辑,状态自动流式下发,后续如果把定位改成热流,还能实现定位更新后自动刷新服务器列表和配置的能力。

原有方案问题说明

你第二种通过Hilt注入Flow的实现会把依赖关系隐式化,后续排查问题、调整链路的成本会非常高,不推荐使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 09:57:06