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

Android Architecture Components最佳实践:错误处理方案选型咨询

AAC错误处理最佳实践:单LiveData+包装对象才是最优解

嘿,这个问题问到点子上了!在使用Android Architecture Components(AAC)做错误处理时,很多开发者都会在「多LiveData」和「单LiveData+包装对象」之间纠结,我结合自己的项目经验和行业通用做法来给你梳理下:

先说说「多LiveData方案」的问题

如果为每种错误类型单独创建LiveData(比如noNetworkErrorLiveData、serverErrorLiveData、exceptionLiveData),看似直观,但实际用起来会踩不少坑:

  • 状态分散,易不一致:UI层要同时观察多个LiveData,很容易出现「数据加载成功了,但之前的错误LiveData还没清零,UI误显示错误提示」的情况。
  • UI逻辑零散:每个LiveData的回调都要单独处理,代码会变得碎片化,后期维护起来很头疼。
  • 扩展性差:以后新增错误类型时,又要加新的LiveData,会让ViewModel的状态管理越来越臃肿。

更推荐的「单LiveData+包装对象」方案

这也是AAC官方和行业普遍认可的最佳实践,核心思路是用一个**密封类(Kotlin)或带状态的包装类(Java)**把「加载中、成功、各类错误」所有状态都封装起来,只暴露一个LiveData给UI层。

举个Kotlin的实现例子

用密封类来定义所有请求状态非常合适,因为它能限制状态的可能性,避免遗漏处理:

// 封装所有请求状态的密封类
sealed class Resource<out T> {
    // 加载中状态
    object Loading : Resource<Nothing>()
    // 请求成功,携带返回数据
    data class Success<out T>(val data: T) : Resource<T>()
    // 请求失败,携带错误类型、提示信息、异常对象(可选)
    data class Error(
        val errorType: ErrorType,
        val message: String? = null,
        val exception: Exception? = null
    ) : Resource<Nothing>()
}

// 定义具体的错误类型枚举
enum class ErrorType {
    NO_NETWORK,       // 无网络错误
    SERVER_RESPONSE,  // 服务器返回的错误(字符串/JSON)
    EXCEPTION         // 其他异常类错误
}

在ViewModel中怎么用?

发起请求时统一更新这个LiveData的状态:

class MyViewModel : ViewModel() {
    private val _dataResource = MutableLiveData<Resource<UserData>>()
    val dataResource: LiveData<Resource<UserData>> = _dataResource

    fun fetchUserData() {
        _dataResource.postValue(Resource.Loading)
        viewModelScope.launch {
            try {
                if (!NetworkUtils.isNetworkAvailable()) {
                    _dataResource.postValue(Resource.Error(ErrorType.NO_NETWORK, "当前无网络连接"))
                    return@launch
                }
                val response = apiService.getUserData()
                if (response.isSuccessful) {
                    response.body()?.let {
                        _dataResource.postValue(Resource.Success(it))
                    } ?: run {
                        _dataResource.postValue(Resource.Error(ErrorType.SERVER_RESPONSE, "服务器返回空数据"))
                    }
                } else {
                    // 解析服务器返回的错误信息(比如JSON里的error字段)
                    val errorMsg = parseServerError(response.errorBody())
                    _dataResource.postValue(Resource.Error(ErrorType.SERVER_RESPONSE, errorMsg))
                }
            } catch (e: Exception) {
                _dataResource.postValue(Resource.Error(ErrorType.EXCEPTION, exception = e))
            }
        }
    }
}

UI层的处理逻辑

UI层只需要观察这一个LiveData,用when表达式(Kotlin)或switch(Java)统一处理所有状态,逻辑非常集中:

viewModel.dataResource.observe(viewLifecycleOwner) { resource ->
    when (resource) {
        is Resource.Loading -> {
            // 显示加载进度条
            progressBar.visibility = View.VISIBLE
        }
        is Resource.Success -> {
            // 隐藏加载,显示数据
            progressBar.visibility = View.GONE
            showUserData(resource.data)
        }
        is Resource.Error -> {
            progressBar.visibility = View.GONE
            when (resource.errorType) {
                ErrorType.NO_NETWORK -> showNoNetworkDialog() // 显示无网络提示+重试按钮
                ErrorType.SERVER_RESPONSE -> showToast(resource.message ?: "服务器错误")
                ErrorType.EXCEPTION -> {
                    showToast("出现未知错误")
                    // 记录异常日志,方便排查
                    Log.e("MyViewModel", "请求失败", resource.exception)
                }
            }
        }
    }
}

为什么这个方案更优?

  • 状态统一:所有和请求相关的状态都封装在一个对象里,从根本上避免了状态不一致的问题。
  • UI逻辑简洁:只需要处理一个LiveData的回调,代码集中易维护。
  • 扩展性强:新增错误类型时,只需要在ErrorType枚举里加值,然后在UI层的when里补充处理逻辑即可,无需修改ViewModel的LiveData结构。

补充:什么时候用多LiveData?

只有处理全局通用状态时(比如全局网络状态监听、全局登录失效提示),单独的LiveData才更合适。但针对单个业务请求的错误处理,「单LiveData+密封类包装」绝对是最优选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:43:49