Android 关于Retrofit OkHttp Token过期刷新 请求并发的问题

本文探讨了在Android应用中使用Retrofit和OkHttp处理Token过期的策略。通过OkHttp拦截器检查Token状态,一旦发现Token过期,则自动发起刷新请求。经初步测试已实现单次有效刷新,但可能存在未知问题,欢迎反馈。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

class TokenInterceptor(
    @Volatile
    var token: String,
    @Volatile
    var refreshToken: String
) : Interceptor {

    companion object {
        const val ACCESS_TOKEN_NAME = "accessToken"
        const val REFRESH_TOKEN_NAME = "refreshToken"
        const val CODE_NAME = "code"
        const val TAG = "TokenInterceptor"
    }

    fun updateTokenAndRefreshToken(token: String, refreshToken: String) {
        this.token = token
        this.refreshToken = refreshToken
    }

    private val refreshTokenUrl = "login/updateToken"

    override fun intercept(chain: Interceptor.Chain): Response {
        Log.d(TAG, "intercept")
        val builder = chain.request().newBuilder()
        builder.header(ACCESS_TOKEN_NAME, token)
        val outDateToken = token
        val response = chain.proceed(builder.build())

        // 先判断请求回来的数据是否过期
        if (isTokenExpired(response)) {
            // 请求并发的情况下会有多个线程进入这个分支
            // refreshToken 同步请求,确保后续请求被阻塞
            refreshToken(outDateToken)
            val newBuilder = chain.request().newBuilder()
            newBuilder.header(ACCESS_TOKEN_NAME, token)
            return chain.proceed(newBuilder.build())
        }
        return response
    }

    private fun isTokenExpired(response: Response): Boolean {
        // Response.body.string 只能调用一次
        val string = copyBuffer(response.body())
        return getCode(string) == 401
    }

    private fun getCode(string: String?): Int {
        return string?.let {
            JSONObject(string).getInt(CODE_NAME)
        } ?: error("body null")
    }

    private fun copyBuffer(body: ResponseBody?): String? {
        val source = body?.source()
        source?.request(Long.MAX_VALUE)
        val buffer = source?.buffer
        return buffer?.clone()?.readString(Charset.defaultCharset())
    }

    /**
     * [outDateToken] 网络请求保存的token
     * 用于判断token是否被刷新
     * token过期进入该函数时会竞争,后续会被阻塞,当 token刷新完毕时
     * 再次判断,如果之前保存的[outDateToken]和全局[token]相等,则是过期token,刷新就行
     * 不相等则说明保存的[outDateToken]是没有过期的,携带进行请求
     */
    @Synchronized
    private fun refreshToken(outDateToken: String): String {
        if (token.isEmpty() || outDateToken == token) {
            Log.d(TAG, "refreshToken: start token = $token")
            val client = OkHttpClient()
            val builder = Request.Builder()
            val call = client.newCall(
                builder.get()
                    .url("$refreshTokenUrl?refreshToken=$refreshToken")
                    .build()
            )
            Log.d(TAG, "refreshToken: call start")
            //OKHttp同步请求
            updateTokenByResponse(call.execute())
            Log.d(TAG, "refreshToken: call end")
            Log.d(TAG, "refreshToken: end token = $token")
        }
        return token
    }

    private fun updateTokenByResponse(response: Response) {
        response.code().also {
            if (it == 200) {
                val string = response.body()?.string()
                if (string != null) {
                    JSONObject(string).let { json ->
                        val code = json.getInt(CODE_NAME)
                        Log.d(TAG, "updateTokenByResponse: code = $code")
                        if (code == 200) {
                            val data = json.getJSONObject("data")
                            Log.d(TAG, "updateTokenByResponse: sleep")
                            updateTokenAndRefreshToken(
                                data.getString(ACCESS_TOKEN_NAME),
                                data.getString(REFRESH_TOKEN_NAME)
                            )
                            return
                        }
                    }
                } else {
                    Log.d(TAG, "updateTokenByResponse: string = $string")
                }
            }
            //todo new login
        }
    }
}

经过测试,只刷新了一次token,但是不知道还会有什么bug,如果发现了请一定要告知我,十分感谢!

基本思路是利用OkHttp的拦截器,添加token,判断token是否过期,过期就再次请求。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值