不止是“点外卖”:构建工业级的 Android 网络请求架构

233 阅读3分钟

一句话总结:

工业级的网络请求,不是在 ViewModel 中零散地 try-catch,而是通过构建一个标准的 Result 封装、一个可复用的 safeApiCall 和一个完备的 UiState 模型,将网络请求的加载、成功、失败状态,以及精细化的错误类型,优雅地传递给 UI 层。


第一章:坚实的基础——现代化的 Retrofit + 协程请求

你的文章已经出色地展示了这一基础:使用 suspend 函数定义 API,并在 ViewModel 中通过 viewModelScope 发起请求。这是我们构建一切的起点。

// ApiService.kt
interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") userId: Int): User
}

// UserViewModel.kt (基础版)
class UserViewModel : ViewModel() {
    fun fetchUser() {
        viewModelScope.launch {
            try {
                val user = apiService.getUser(1)
                // ... 成功 ...
            } catch (e: Exception) {
                // ... 失败 ...
            }
        }
    }
}

这个模型能工作,但它脆弱、粗糙且充满了重复代码。现在,我们开始为它“添砖加瓦”,将其打造成坚固的“城堡”。


第二章:架构的三大支柱

支柱一:用 Result 封装“结果”,告别分散的 try-catch

我们首先定义一个标准的、密封的 Result 类,用来封装所有数据请求的两种可能结果:成功或失败。

sealed class Result<out T> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}

现在,我们要求所有 Repository 的方法,返回的不再是 User,而是 Result<User>。这使得“失败”成为了一种可被类型系统识别的一等公民。

支柱二:用 BaseRepository 抽象“过程”,消除重复代码

创建一个 BaseRepository,在其中定义一个可被所有子类复用的 safeApiCall 函数。这个函数将成为我们所有网络请求的“安全外壳”。

// BaseRepository.kt
abstract class BaseRepository {
    suspend fun <T> safeApiCall(apiCall: suspend () -> T): Result<T> {
        return withContext(Dispatchers.IO) {
            try {
                Result.Success(apiCall.invoke())
            } catch (e: Exception) {
                Result.Error(e)
            }
        }
    }
}

现在,我们的 UserRepository 变得极其干净:

// UserRepository.kt
class UserRepository(private val apiService: ApiService) : BaseRepository() {
    suspend fun getUser(id: Int): Result<User> {
        // 只关心业务调用,所有 try-catch 和线程切换都已封装
        return safeApiCall { apiService.getUser(id) }
    }
}

支柱三:用 UiState 表达“状态”,让 UI 响应更完整

UI 需要知道的不仅是成功和失败,还有“加载中”。因此,我们在 ViewModel 中定义一个 UiState

// UserViewModel.kt
sealed class UserUiState {
    object Loading : UserUiState()
    data class Success(val user: User) : UserUiState()
    data class Error(val message: String) : UserUiState()
}

第三章:完整的工业级数据流

现在,我们将三大支柱串联起来,形成一个清晰、健壮的数据流动管道。

// UserViewModel.kt
class UserViewModel(private val repository: UserRepository) : ViewModel() {
    
    private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
    val uiState: StateFlow<UserUiState> = _uiState

    fun fetchUser(id: Int) {
        // 1. UI 进入 Loading 状态
        _uiState.value = UserUiState.Loading
        
        viewModelScope.launch {
            // 2. Repository 返回标准化的 Result
            val result = repository.getUser(id)
            
            // 3. ViewModel 根据 Result 更新 UiState
            when (result) {
                is Result.Success -> {
                    _uiState.value = UserUiState.Success(result.data)
                }
                is Result.Error -> {
                    // 4. 在这里进行精细化的错误处理
                    val errorMessage = parseError(result.exception)
                    _uiState.value = UserUiState.Error(errorMessage)
                }
            }
        }
    }
    
    // 统一的错误解析逻辑
    private fun parseError(exception: Exception): String {
        return when (exception) {
            is HttpException -> "服务器错误: ${exception.code()}"
            is IOException -> "网络连接失败"
            else -> "未知错误"
        }
    }
}

最后,Activity 的职责变得前所未有的纯粹:

// MainActivity.kt
lifecycleScope.launch {
    viewModel.uiState.collect { state ->
        when (state) {
            is UserUiState.Loading -> showProgressBar()
            is UserUiState.Success -> showUserData(state.user)
            is UserUiState.Error -> showError(state.message)
        }
    }
}

四、总结:从“能用”到“可靠”的架构演进

维度基础实践 (能用)工业级架构 (可靠)
返回类型suspend fun getData(): Datasuspend fun getData(): Result<Data>
错误处理ViewModel 中分散 try-catchBaseRepository 中统一处理,返回 Result.Error
UI 状态只有成功/失败两种状态包含 Loading, Success, ErrorUiState
代码复用每个请求都重复 try-catch, withContext通过 safeApiCall 消除所有样板代码

通过这套架构,我们构建的不再是一次性的“外卖订单”,而是一个标准化的、可复用的、高度健壮的“中央厨房” ,能够为应用的任何部分,稳定地提供数据成品。