Android实战 -> 使用Interceptor+Lock实现无缝刷新Token
前言
哈喽各位我又来了,相信大家在做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")
}
}
输出日志
如图,线程2抢到了Lock锁,线程1、3则进入了等待状态
如图,线程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
作者:新啊新之助
来源:juejin.cn/post/7306018966920970274
来源:juejin.cn/post/7306018966920970274