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

Retrofit自定义错误处理器实现及errorBody反序列化问题咨询

Retrofit 错误体自动反序列化实战

问题背景

我是Retrofit新手,想要实现自动反序列化result.errorBody()中的错误信息,已经尝试两种方式但均失败,求正确实现方法。

现有代码

自定义错误转换器(CustomErrorHandler)

class CustomErrorHandler(private val gson: Gson) : Converter<ResponseBody, Error> {

    @Throws(IOException::class)
    override fun convert(responseBody: ResponseBody): Error {
        val error = gson.fromJson(responseBody.charStream(), Error::class.java)
        responseBody.close()
        throw Exception(error.message)
    }

    class Factory(private val gson: Gson) : Converter.Factory() {

        override fun responseBodyConverter(type: Type, annotations: Array<Annotation>, retrofit: Retrofit): Converter<ResponseBody, *>? {
            val typeToken = TypeToken.get(type)
            if (typeToken.rawType != Error::class.java) {
                return null
            }
            return CustomErrorHandler(gson)
        }

    }
}

Retrofit 初始化代码

val retrofit = Retrofit.Builder().baseUrl("http://192.168.0.1:8080")
    .addConverterFactory(CustomErrorHandler.Factory(GsonBuilder().create()))
    .addConverterFactory(GsonConverterFactory.create()).build()

Error 数据类

data class Error(val code: String, val message: String) {
    override fun toString(): String {
        return "code: $code, message: $message"
    }
}

RoleService 接口

interface RoleService {
    @GET("/roles")
    @Headers("Accept: application/json")
    suspend fun findAll(): Response<List<Role>>
}

尝试1:期望自动转换errorBody

suspend fun findAll(): List<Role>? {
    val result = roleService.findAll()
    if (result.isSuccessful)
        return result.body()
    throw Exception(result.errorBody().toString()) // 无法将errorBody转为Error类
}

尝试2:手动解析errorBody(失败)

suspend fun findAll(): List<Role>? {
    val result = roleService.findAll()
    if (result.isSuccessful)
        return result.body()
    val e = Gson().fromJson(result.errorBody().toString(), Error::class.java) // 解析失败
    throw Exception(e.message)
}

错误原因分析

  1. 尝试1问题:result.errorBody().toString()返回的是ResponseBody对象的字符串标识(如okhttp3.ResponseBody$1@xxxx),并非接口返回的JSON错误内容;且自定义转换器仅处理成功响应的Body,不会自动解析错误体。
  2. 尝试2问题:同样误用errorBody().toString()获取内容,应读取ResponseBody的实际JSON流;且重复创建Gson实例既不高效,也可能与Retrofit配置的Gson实例不一致。

正确实现方案

  • 方案1:复用自定义转换器解析错误体
    已定义的CustomErrorHandler可直接从Retrofit中获取,用于解析错误体:
// 从Retrofit中获取自定义错误转换器
val errorConverter = retrofit.responseBodyConverter<Error>(Error::class.java, emptyArray())

suspend fun findAll(): List<Role>? {
    val result = roleService.findAll()
    if (result.isSuccessful) {
        return result.body()
    }
    result.errorBody()?.let { body ->
        val error = try {
            errorConverter.convert(body)
        } catch (e: IOException) {
            Error("UNKNOWN", "解析错误失败")
        } finally {
            body.close()
        }
        throw Exception(error.message)
    } ?: throw Exception("未知错误")
}
  • 方案2:手动解析错误体(优化版)
    无需转换器,直接读取错误体JSON内容解析,注意复用Retrofit配置的Gson实例:
// 复用全局Gson实例,避免重复创建
val gson = GsonBuilder().create()

suspend fun findAll(): List<Role>? {
    val result = roleService.findAll()
    if (result.isSuccessful) {
        return result.body()
    }
    result.errorBody()?.use { body ->
        // 读取错误体的JSON字符串(string()仅能调用一次)
        val errorJson = body.string()
        val error = gson.fromJson(errorJson, Error::class.java)
        throw Exception(error.message)
    } ?: throw Exception("未知错误")
}

注意:ResponseBody.string()会关闭流,因此只能调用一次,使用use块可自动关闭资源。

  • 方案3:全局错误处理(更优雅)
    自定义CallAdapter实现全局错误拦截,无需每个接口重复编写解析逻辑:
class ErrorHandlingCallAdapterFactory(private val gson: Gson) : CallAdapter.Factory() {

    override fun get(
        returnType: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): CallAdapter<*, *>? {
        val delegate = retrofit.nextCallAdapter(this, returnType, annotations)
        return object : CallAdapter<Any, Any> {
            override fun responseType(): Type = delegate.responseType()

            override fun adapt(call: Call<Any>): Any {
                return delegate.adapt(call).also { response ->
                    if (response is Response<*> && !response.isSuccessful) {
                        response.errorBody()?.use { body ->
                            val error = gson.fromJson(body.charStream(), Error::class.java)
                            throw Exception(error.message)
                        } ?: throw Exception("未知错误")
                    }
                }
            }
        }
    }
}

在Retrofit初始化时添加该CallAdapter:

val gson = GsonBuilder().create()
val retrofit = Retrofit.Builder().baseUrl("http://192.168.0.1:8080")
    .addCallAdapterFactory(ErrorHandlingCallAdapterFactory(gson))
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build()

此时接口可直接返回业务数据类型,错误会自动抛出:

interface RoleService {
    @GET("/roles")
    @Headers("Accept: application/json")
    suspend fun findAll(): List<Role>
}

// 调用时无需处理Response,直接捕获异常即可
suspend fun findAll(): List<Role> {
    return roleService.findAll()
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 22:35:19