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

如何将Retrofit错误响应转为正常处理,避免抛出异常?

哥们儿,我太懂你这个困扰了——Retrofit默认把非2xx的响应直接扔出HttpException,结果你想拿后端返回的JSON错误详情都拿不到,Interceptor确实搞不定这种场景,得从Retrofit的响应处理逻辑入手,给你几个实用的方案:

核心思路:让Retrofit把所有带JSON的响应(不管200还是500)都当成“成功响应”解析

本质上就是绕开Retrofit默认的响应码校验,自己接管响应的解析和状态判断,下面是具体实现步骤:

1. 先定义通用的响应封装类

不管后端返回成功还是错误,肯定有统一的字段(比如code、message),先写个数据类把这些字段统一起来:

// Kotlin示例,Java写法类似
data class ApiResponse<T>(
    val code: Int,          // 后端返回的业务码(比如200成功,500服务器错误)
    val message: String?,   // 错误提示信息
    val data: T?,           // 成功时返回的数据
    val errorDetails: Map<String, String>? // 可选:后端返回的详细错误信息
)

如果后端错误响应的结构和成功响应差异较大,你可以调整字段,比如把data和errorDetails做成互斥的。

2. 改造Retrofit接口,统一返回封装类

原来的接口可能是直接返回业务数据:

@GET("/user/profile")
suspend fun getUserProfile(): UserProfile

现在改成返回我们定义的ApiResponse,用Response包裹来拿到原始HTTP响应码,同时避免Retrofit抛异常:

@GET("/user/profile")
suspend fun getUserProfile(): Response<ApiResponse<UserProfile>>

3. 调用接口时统一处理成功/错误

调用接口后,先判断HTTP响应是否有合法响应体,再通过ApiResponse里的业务码判断状态:

val response = apiService.getUserProfile()
if (response.isSuccessful) {
    response.body()?.let { apiResp ->
        when (apiResp.code) {
            200 -> {
                // 处理成功数据
                apiResp.data?.let { showUserProfile(it) }
            }
            500 -> {
                // 处理服务器错误,拿到JSON里的错误详情
                showError(apiResp.message, apiResp.errorDetails)
            }
            // 其他业务错误码同理处理
            else -> showError(apiResp.message ?: "未知错误")
        }
    } ?: showError("响应体为空")
} else {
    // 这里是真的没有响应体的情况(比如网络断了、后端没返回JSON)
    showError("网络请求失败")
}

4. 进阶:自定义CallAdapter,简化接口写法

如果每个接口都写Response<ApiResponse<T>>太繁琐,可以自定义一个CallAdapter,自动把响应封装成ApiResponse:

class ApiResponseCallAdapterFactory private constructor() : CallAdapter.Factory() {
    override fun get(
        returnType: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): CallAdapter<*, *>? {
        // 只处理返回类型为Call<ApiResponse<T>>的接口
        if (getRawType(returnType) != Call::class.java) return null
        val innerType = getParameterUpperBound(0, returnType as ParameterizedType)
        if (getRawType(innerType) != ApiResponse::class.java) return null
        val dataType = getParameterUpperBound(0, innerType as ParameterizedType)
        return ApiResponseCallAdapter<Any>(dataType, retrofit)
    }

    private class ApiResponseCallAdapter<T>(
        private val dataType: Type,
        private val retrofit: Retrofit
    ) : CallAdapter<T, Call<ApiResponse<T>>> {
        override fun responseType(): Type = dataType

        override fun adapt(call: Call<T>): Call<ApiResponse<T>> {
            return object : Call<ApiResponse<T>> {
                override fun enqueue(callback: Callback<ApiResponse<T>>) {
                    call.enqueue(object : Callback<T> {
                        override fun onResponse(call: Call<T>, response: Response<T>) {
                            val apiResponse = if (response.isSuccessful) {
                                // 成功响应:封装成业务成功的ApiResponse
                                ApiResponse(
                                    code = response.code(),
                                    message = "请求成功",
                                    data = response.body(),
                                    errorDetails = null
                                )
                            } else {
                                // 错误响应:解析错误体的JSON
                                val errorConverter = retrofit.responseBodyConverter<ApiResponse<T>>(
                                    ApiResponse::class.java, emptyArray()
                                )
                                val errorResp = try {
                                    response.errorBody()?.let { errorConverter.convert(it) }
                                } catch (e: Exception) {
                                    // 解析失败时的兜底
                                    ApiResponse(-1, "解析错误失败", null, null)
                                }
                                errorResp ?: ApiResponse(response.code(), "未知错误", null, null)
                            }
                            callback.onResponse(this@object, Response.success(apiResponse))
                        }

                        override fun onFailure(call: Call<T>, t: Throwable) {
                            // 网络失败:封装成网络错误的ApiResponse
                            val errorResp = ApiResponse(
                                code = -1,
                                message = t.message ?: "网络请求失败",
                                data = null,
                                errorDetails = null
                            )
                            callback.onResponse(this@object, Response.success(errorResp))
                        }
                    })
                }

                // 其他方法直接委托给原Call
                override fun execute(): Response<ApiResponse<T>> {
                    TODO("如果需要同步调用,这里实现对应的逻辑")
                }

                override fun isExecuted(): Boolean = call.isExecuted
                override fun cancel() = call.cancel()
                override fun isCanceled(): Boolean = call.isCanceled
                override fun clone(): Call<ApiResponse<T>> = adapt(call.clone())
                override fun request(): Request = call.request()
            }
        }
    }

    companion object {
        fun create(): ApiResponseCallAdapterFactory = ApiResponseCallAdapterFactory()
    }
}

构建Retrofit时添加这个CallAdapter:

val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
    .client(okHttpClient)
    .build()

这样接口就可以简化成:

@GET("/user/profile")
suspend fun getUserProfile(): ApiResponse<UserProfile>

调用时直接判断apiResp.code即可,不用再处理Response。

几个注意点

  • 一定要确保后端的错误响应(比如500)确实返回了合法的JSON,不然解析会失败,记得在代码里加异常捕获做兜底。
  • 如果用的是Moshi、Jackson等其他JSON解析库,把代码里的Gson部分换成对应的解析逻辑就行。
  • 不要直接修改OkHttp的响应码(比如把500改成200),那样会丢失真实的HTTP状态,后续排查问题会很麻烦。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 07:54:14