Android实战 -> 使用Interceptor+Lock实现无缝刷新Token

4,008 阅读2分钟

前言

哈喽各位我又来了,相信大家在做APP的时候肯定会遇到用户Token即将过期或者已经过期的情况,那么这个时候后端都返回相应的Code提示我们要求刷新Token处理。

那么今天这篇文章就给大家提供一个思路。

开工

技术点
  • Interceptor -> 拦截器
  • ReentrantLock -> 重入锁
实现思路
  • 通过TokenInterceptor获取Response解析请求结果验证是否Token过期
  • 监控到Token已过期后阻塞当前线程,调用刷新Token接口并使用Lock锁
  • 并发的请求也监控到了Token过期后,先校验Lock是否已锁,已锁等待,未锁步骤2
  • Token刷新成功后各线程携带新的Token创建Request重新请求

总结:4个并发线程接口,谁抢到了Lock锁谁去刷新Token,其他三个线程阻塞等待

实现代码
private fun handle(name: String) {
    Log.d(TAG, "handle 【Start】 called with: name = $name")
    try {
        if (!mLock.isLocked) {
            this.mLock.lock() // 加锁
            Log.d(TAG, "handle 【Start  Refresh】 called with: name = $name")
            Thread.sleep(5000) // 此处应为刷新Token请求
            Log.d(TAG, "handle 【End Refresh】 called with: name = $name")
            this.mLock.unlock() // 释放锁
        } else {
            Log.d(TAG, "handle 【Wait Refresh】 called with: name = $name")
            while (true) { // 阻塞等待
                if (!mLock.isLocked) { // 查询锁状态
                    Log.d(TAG, "handle 【OK Refresh】 called with: name = $name")
                    break
                }
            }
        }
    } finally {
        if (mLock.isLocked) {
            this.mLock.unlock()
        }
    }
    Log.d(TAG, "handle 【End】 called with: name = $name")
}

如上述代码,抢到Lock锁的线程去刷新Token,其余线程等待结果。

模拟测试
// 此处模拟并发请求
this.findViewById<View>(R.id.btnGo).setOnClickListener {
    thread {
        handle("线程1")
    }
    thread {
        handle("线程2")
    }
    thread {
        handle("线程3")
    }
}
输出日志

image.png

如图,线程2抢到了Lock锁,线程1、3则进入了等待状态

image.png

如图,线程2刷新Token成功后释放了锁,线程1、3监听到了锁被释放则进入重新请求逻辑

实践代码
class TokenInterceptor : Interceptor {

    @Volatile
    private var mRefreshInvalidTime = 0L

    @Volatile
    private var isRefreshToken = false
    
    private val mRefreshTokenLock by lazy { ReentrantLock() }

    private val mAccountRep by lazy { .... }

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        // 接口过滤Token校验
        val ignoreToken = request.headers[HeaderConstant.IGNORE_TOKEN_NAME]
        if (ignoreToken == HeaderConstant.IGNORE_TOKEN_VALUE) {
            return chain.proceed(request)
        }
        val response = chain.proceed(request)
        if (HttpFactory.bodyEncoded(response.headers)) {
            return response
        }
        // 解析反参Json
        val result = HttpFactory.bodyToString(response) ?: ""
        if (!TextUtils.isEmpty(result)) {
            val resp = result.convert<BaseResp<Any>>()
            // 校验Token是否过期
            if (ResponseConstant.isTokenExpire(resp.code)) {
                return onTokenRefresh(chain, response) ?: kotlin.run { response }
            }
            // 校验Token是否失效
            if (ResponseConstant.isTokenInvalid(resp.code)) {
                this.onTokenInvalid(response)
            }
        }
        return response
    }

    /**
     * Token 刷新
     */
    private fun onTokenRefresh(chain: Interceptor.Chain, response: Response): Response? {
        var newResponse: Response? = response
        try {
           if (!mRefreshTokenLock.isLocked) {
                this.mRefreshTokenLock.lock()
                this.isRefreshToken = true
                runBlocking {
                    launch(Dispatchers.Default) {
                        newResponse = requestAuthToken(chain, response)
                    }
                }
                
                this.mRefreshTokenLock.unlock()
                this.isRefreshToken = false
            } else {
                while (true){
                    if (!isRefreshToken){
                        newResponse = doRequest(chain)
                        break
                    }
                }
            }
        } catch (e: Exception) {
            // do something
        } finally {
            if (mRefreshTokenLock.isLocked) {
                this.mRefreshTokenLock.unlock()
                this.isRefreshToken = false
            }
        }
        return newResponse
    }


    /**
     * Token 失效
     */
    private fun onTokenInvalid(response: Response) {
        response.close()
        // 防抖
        val currentTime = System.currentTimeMillis()
        if ((currentTime - mRefreshInvalidTime) > KET_TOKEN_INVALID_ANTI_SHAKE) {
            this.mRefreshInvalidTime = currentTime
            // 跳转登录页 or 自行逻辑
            ...
        }
    }
    
    /**
     * 请求 刷新Token
     */
    private suspend fun requestAuthToken(chain: Interceptor.Chain, response: Response): Response? {
        var newResponse: Response? = response
        val resp = .... // 请求代码
        if (resp.isSuccess()) {
            response.close()
            resp.data?.let { data -> .... //更新本地Token }
            newResponse =  doRequest(chain)
        }
        return newResponse
    }

    private fun doRequest(chain: Interceptor.Chain): Response? {
        var response: Response? = null
        try {
            val newRequest = HttpFactory.newRequest(chain.request()).build()
            response = chain.proceed(newRequest)
        } catch (e: Exception) {
            // do something
        }
        return response
    }

    companion object {
        const val KET_TOKEN_INVALID_ANTI_SHAKE = 2000
    }
}
End

到这里就结束了,简单吧,希望可以帮到在座的小伙伴们。当然如果有更好的实现方式或方案也希望各位在评论区留言讨论,我秒回复哦~ Bye