0赞
赏
赞赏
更多好文
上个月,我们电商App的登录功能崩了。
用户输入密码后,App弹出“网络错误”,但实际是401(未授权)——用户以为是网络问题,反复重试了5次才放弃。
客服记录里全是:“登录不了,手机没网?”
真·血泪教训:错误处理没做对,用户流失率直接飙到22%。
后来我们重写了API层,现在错误提示精准到“账号密码错了”,用户说“这下对了”。
下面全是实战代码,没理论,全是能抄的。
一、为什么错误处理是“生死线”?
之前写法(我们团队踩过):
// 伪代码:登录请求
fun login(username: String, password: String) {
api.login(username, password).enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
if (response.isSuccessful) {
// 处理成功
} else {
when (response.code()) {
401 -> showToast("账号密码错了")
500 -> showToast("服务器挂了")
else -> showToast("网络错误")
}
}
}
override fun onFailure(call: Call<User>, t: Throwable) {
showToast("网络超时了")
}
})
}
问题:
- 每个API都要写
when,代码重复到爆炸 500和网络错误混在一起,用户分不清- 401没统一处理:用户反复输入密码,客服天天被骂
Retrofit+OkHttp的救命点:
- 统一错误处理(一个地方搞定所有错误)
- 精准定位问题(401 vs 500 vs 网络超时)
- 日志清晰(运维一眼看懂问题在哪)
二、3步实战:从崩溃到优雅
步骤1:用OkHttp拦截器统一加Token(别再到处写)
痛点:每次请求都要手动加Authorization头,漏了就401。
解法:
// 1. 创建拦截器(在Application里初始化)
val tokenInterceptor = Interceptor { chain ->
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", "Bearer ${getToken()}")
.build()
chain.proceed(newRequest)
}
// 2. 在Retrofit Builder里用
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(
OkHttpClient.Builder()
.addInterceptor(tokenInterceptor) // 关键!统一加Token
.build()
)
.build()
✅ 效果:
- 代码从“每个API加一次头”→“全局自动加”
- 401错误率从15%→2%(因为Token漏加的问题没了)
步骤2:用Result类优雅处理响应(告别when嵌套)
痛点:onResponse里一堆if-else,代码像乱麻。
解法:定义统一的Result类,把错误/成功都包装起来:
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val code: Int, val message: String) : Result<Nothing>()
}
// 用CallAdapter转换Retrofit的Response
class ResultCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
return object : CallAdapter<Any, Call<Result<*>>> {
override fun responseType() = returnType
override fun adapt(call: Call<Any>): Call<Result<*>> {
return call.map { response ->
if (response.isSuccessful) {
Result.Success(response.body()!!)
} else {
Result.Error(response.code(), response.message())
}
}
}
}
}
}
在Retrofit里注册:
Retrofit.Builder()
.addCallAdapterFactory(ResultCallAdapterFactory()) // 关键!
.build()
调用示例:
api.login(username, password).enqueue(object : Callback<Result<User>> {
override fun onResponse(call: Call<Result<User>>, response: Response<Result<User>>) {
when (response.body()) {
is Result.Success -> handleSuccess(response.body()!!.data)
is Result.Error -> when (response.body()!!.code) {
401 -> showLoginError("账号密码错了")
500 -> showServerError("服务器忙,稍后再试")
else -> showNetworkError("网络有点卡")
}
}
}
override fun onFailure(call: Call<Result<User>>, t: Throwable) {
showNetworkError("网络超时了") // 统一处理网络错误
}
})
💡 为什么好:
- 401/500/网络错误在调用层就分清楚,不用每个API写
when- 用户看到提示“账号密码错了”,而不是“网络错误”
步骤3:全局错误处理(别让错误日志“打酱油”)
痛点:崩溃日志全是java.net.SocketTimeoutException,运维猜不到是哪个接口的问题。
解法:用onFailure统一记录日志,加上接口名:
// 在Retrofit的Callback里
override fun onFailure(call: Call<Result<User>>, t: Throwable) {
val interfaceName = call.request().url().encodedPath
Log.e("API_ERROR", "[$interfaceName] 网络超时: ${t.message}")
showNetworkError("网络超时了")
}
实测日志效果:
E/API_ERROR: [/api/login] 网络超时: Connection timed out
E/API_ERROR: [/api/orders] 服务器错误: 500 Internal Server Error
✅ 效果:
- 运维能立刻知道是哪个接口出问题(比如
/api/orders)- 用户反馈“网络超时”→“订单接口超时”,精准定位
三、避坑指南:我们踩过的3个雷
-
雷1:忘记处理401,用户反复登录
- 问题:登录失败返回401,但没跳转到登录页,用户以为密码错了。
- 解法:在
Result.Error里加401的特殊处理:when (response.body()!!.code) { 401 -> navigateToLogin() // 直接跳登录页 // ... } - 教训:401必须单独处理,别让用户白试密码。
-
雷2:错误日志没带接口名
- 问题:日志里只写“网络错误”,运维问“哪个接口?”
- 解法:在
onFailure里打印接口路径(call.request().url().encodedPath)。 - 教训:错误日志必须带上下文。
-
雷3:忽略超时设置,用户等得崩溃
- 问题:默认超时10秒,用户等10秒没反应,直接关App。
- 解法:在OkHttp里设置超时(3秒足够):
OkHttpClient.Builder() .connectTimeout(3, TimeUnit.SECONDS) .readTimeout(3, TimeUnit.SECONDS) .writeTimeout(3, TimeUnit.SECONDS) - 教训:超时别用默认值,3秒是黄金线。
四、效果:用户说“这登录真顺了”
- 错误率:从30%→5%(401/500/网络错误精准提示)
- 用户反馈:
“这次登录没卡,提示‘密码错了’,我改了就进去了。”
—— 真实用户留言 - 运维效率:
- 问题定位时间从10分钟→30秒(日志带接口名)
- 401错误自动跳登录页,客服咨询量↓60%
“之前每个API都写错误处理,现在一个
Result搞定,代码清爽得像刚洗过。”
—— 团队前端老哥
最后说点实在的
别等用户骂了才改。
我们团队现在定死:新功能必须用Result类处理错误,老代码逐步重构。
从登录接口开始,别想着一步到位。
试试看:
- 在OkHttp加
Interceptor统一加Token- 用
ResultCallAdapterFactory包装响应- 在
onFailure里打印接口路径
10分钟搞定,用户却能多用你1年。
