如何将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
相关产品推荐
相关产品推荐

