使用Kotlin和Volley在Android中处理401响应时实现JWT刷新的最简洁方案
解决方案
1 先处理401状态码识别异常的问题
Volley出现401时networkResponse为空是已知问题,根源是默认请求栈在收到401时会优先抛出AuthFailureError,未正确填充返回字段。你可以直接通过判断错误类型来兼容识别401场景,不需要依赖networkResponse:
只要请求错误属于
AuthFailureError类型,就可以判定为token过期/无效,不需要同时判断networkResponse为空或者状态码401
2 封装通用带自动刷新重试的请求基类
把刷新、重试逻辑全部抽到基类中,业务请求直接继承基类即可,完全不需要每个请求重复写刷新逻辑,也不需要递归。
2.1 先实现全局刷新锁与刷新逻辑
首先在你的RequestSingleton单例类中添加相关字段和方法,避免多个请求同时过期触发多次刷新:
// 刷新token锁,避免并发刷新 private var isRefreshing = false // 缓存刷新期间需要重试的请求 private val retryRequests = mutableListOf<Request<*>>() // 统一刷新token方法 private fun refreshToken(context: Context, onSuccess: ()->Unit, onFail: ()->Unit) { if (isRefreshing) return isRefreshing = true val refreshRequest = object : JsonObjectRequest( Method.POST, "${Constants.BASE_URL}/refresh-token", null, { resp -> // 保存新token到全局存储 val newToken = resp.getString("token") Constants.token = newToken isRefreshing = false // 重试所有缓存的待执行请求 retryRequests.forEach { addToRequestQueue(it) } retryRequests.clear() onSuccess() }, { isRefreshing = false retryRequests.clear() // 刷新失败直接跳转到登录页清除登录状态 context.startActivity(Intent(context, LoginActivity::class.java) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)) onFail() } ) { override fun getHeaders(): MutableMap<String, String> { val headers = HashMap<String, String>() headers["Authorization"] = "Bearer ${Constants.refreshToken}" return headers } } addToRequestQueue(refreshRequest) }
2.2 封装授权请求基类
open class AuthedJsonObjectRequest( method: Int, url: String, jsonRequest: JSONObject?, listener: Response.Listener<JSONObject>, errorListener: Response.ErrorListener, private val context: Context ) : JsonObjectRequest(method, url, jsonRequest, listener, Response.ErrorListener { error -> // 非授权类错误直接走原有业务错误回调 if (error !is AuthFailureError) { errorListener.onErrorResponse(error) return@ErrorListener } val requestSingleton = RequestSingleton.getInstance(context) // 正在刷新token则把当前请求加入待重试队列 if (requestSingleton.isRefreshing) { requestSingleton.retryRequests.add( // 重新构造一模一样的请求,后续用新token执行 AuthedJsonObjectRequest(method, url, jsonRequest, listener, errorListener, context) ) return@ErrorListener } // 触发刷新逻辑 requestSingleton.refreshToken(context, onSuccess = { // 刷新成功后重试当前请求 requestSingleton.addToRequestQueue( AuthedJsonObjectRequest(method, url, jsonRequest, listener, errorListener, context) ) }, onFail = { errorListener.onErrorResponse(error) } ) }) { @Throws(AuthFailureError::class) override fun getHeaders(): MutableMap<String, String> { val headers = HashMap<String, String>() headers["Content-Type"] = "application/json" headers["Authorization"] = "Bearer ${Constants.token}" return headers } }
2.3 业务层使用方式
业务代码不需要写任何刷新相关逻辑,直接调用封装好的基类即可:
val url = "${Constants.BASE_URL}/autologin" val request = AuthedJsonObjectRequest( Method.POST, url, null, { // 原有成功逻辑不变 }, { // 原有错误逻辑不变,只会收到非授权类错误 }, this ) RequestSingleton.getInstance(this).addToRequestQueue(request)
方案优势
- 完全规避Volley 401返回空networkResponse的bug,通过错误类型判断更准确
- 所有刷新、重试逻辑全部封装在基类,业务层无冗余代码
- 没有使用递归,重试逻辑是重新构造请求入队,不会出现栈溢出问题
- 自带刷新锁,不会出现多个请求同时过期触发多次刷新的异常情况
- 刷新过程中收到的其他授权失败请求会自动缓存,刷新成功后统一重试
内容的提问来源于stack exchange,提问作者KapitaiN
相关产品推荐
相关产品推荐

