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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 07:54:03