Android 拦截器与协程融合:实现无感 Token 预刷新方案
在Android应用开发中,Token认证是保障API安全的核心机制。然而,传统方案中Token过期导致的请求失败、用户频繁重登等问题,严重影响了用户体验。本文提出一种基于OkHttp拦截器与协程的无感知Token预刷新方案,通过拦截器捕获Token过期信号,结合协程的挂起机制与并发控制,实现Token的静默更新,确保业务请求的连续性。
一、方案核心设计思路
1.1 拦截器:Token状态的“守门人”
拦截器是方案的核心组件,负责在请求发出前检查Token有效性,并在响应阶段捕获401未授权错误。通过重写OkHttp的intercept方法,可实现以下逻辑:
- 请求阶段:从本地存储(如SharedPreferences或加密数据库)读取当前Token,附加到请求头。
- 响应阶段:若服务器返回401错误,拦截器需暂停当前请求,触发Token刷新流程,待新Token就绪后重试原请求。
1.2 协程:异步刷新的“调度器”
协程的轻量级线程特性与挂起函数(suspend)机制,使其成为处理异步刷新的理想选择。方案中需定义以下协程任务:
- 刷新任务:调用Token刷新API,获取新Token并更新本地存储。
- 重试任务:在刷新完成后,重新发起被拦截的原始请求。
1.3 并发控制:避免“刷新风暴”
当多个请求同时触发401错误时,需避免重复刷新Token。通过共享的Mutex锁或AtomicBoolean标志位,确保同一时间仅有一个刷新任务执行。
二、技术实现步骤
2.1 定义Token管理类
封装Token的存储、刷新与状态管理逻辑:
class TokenManager(private val context: Context) {private val tokenStore = context.getSharedPreferences("auth", Context.MODE_PRIVATE)private val refreshLock = Mutex() // 协程互斥锁suspend fun getToken(): String? = tokenStore.getString("token", null)suspend fun refreshToken(refreshToken: String): Result<String> = coroutineScope {refreshLock.withLock {// 检查是否已有刷新任务在执行if (isRefreshing.compareAndSet(false, true)) {try {val response = apiClient.refreshToken(refreshToken)tokenStore.edit().putString("token", response.newToken).apply()Result.success(response.newToken)} catch (e: Exception) {Result.failure(e)} finally {isRefreshing.set(false)}} else {// 等待其他任务完成delay(100) // 简单轮询,实际可用Channel优化getToken()?.let { Result.success(it) }?: Result.failure(IllegalStateException("Token refresh failed"))}}}}
2.2 实现拦截器逻辑
自定义拦截器需处理三种场景:
- 正常请求:Token有效,直接放行。
- Token过期:触发刷新并重试。
- 刷新中:挂起当前请求,等待刷新完成。
class TokenInterceptor(private val tokenManager: TokenManager,private val scope: CoroutineScope) : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val originalRequest = chain.request()val token = runBlocking { tokenManager.getToken() }?: return chain.proceed(originalRequest) // 无Token直接放行val newRequest = originalRequest.newBuilder().header("Authorization", "Bearer $token").build()val response = chain.proceed(newRequest)if (response.code == HTTP_UNAUTHORIZED) {response.close() // 关闭原响应return retryWithFreshToken(chain, originalRequest)}return response}private fun retryWithFreshToken(chain: Interceptor.Chain, originalRequest: Request): Response {val refreshToken = runBlocking { tokenManager.getRefreshToken() }?: throw IllegalStateException("Refresh token missing")return scope.async {try {tokenManager.refreshToken(refreshToken)val newToken = runBlocking { tokenManager.getToken() }?: throw IllegalStateException("Token refresh failed")val freshRequest = originalRequest.newBuilder().header("Authorization", "Bearer $newToken").build()chain.proceed(freshRequest)} catch (e: Exception) {chain.proceed(originalRequest) // 刷新失败时返回原始错误}}.await()}}
2.3 优化并发控制
上述代码中简单的轮询可能导致性能问题,可通过Channel实现更高效的等待机制:
private suspend fun retryWithFreshToken(chain: Interceptor.Chain, originalRequest: Request): Response {val refreshToken = runBlocking { tokenManager.getRefreshToken() }?: throw IllegalStateException("Refresh token missing")val refreshChannel = Channel<String>(Channel.RENDEZVOUS)scope.launch {try {tokenManager.refreshToken(refreshToken)val newToken = runBlocking { tokenManager.getToken() }?: throw IllegalStateException("Token refresh failed")refreshChannel.send(newToken)} catch (e: Exception) {refreshChannel.close(e)}}return try {val newToken = refreshChannel.receive() // 挂起等待新Tokenval freshRequest = originalRequest.newBuilder().header("Authorization", "Bearer $newToken").build()chain.proceed(freshRequest)} catch (e: Exception) {chain.proceed(originalRequest) // 刷新失败时返回原始错误}}
三、最佳实践与注意事项
3.1 刷新令牌的有效期管理
- 本地存储的Refresh Token需设置合理的过期时间,避免服务器已失效但本地仍尝试刷新。
- 在TokenManager中增加对Refresh Token有效期的检查逻辑。
3.2 错误处理与降级策略
- 网络异常或服务器错误时,需返回原始401错误,避免无限重试。
- 可通过重试计数器限制最大重试次数。
3.3 测试验证要点
- 单元测试:模拟401响应,验证拦截器是否触发刷新。
- 集成测试:在弱网环境下测试并发请求的刷新行为。
- 压力测试:模拟高并发场景,检查锁机制是否生效。
3.4 性能优化方向
- 使用
Dispatchers.IO调度Token刷新任务,避免阻塞主线程。 - 对频繁请求的API,可考虑预加载Token(如应用启动时检查有效期)。
四、方案优势总结
- 无感知体验:用户无需手动重登,业务请求自动恢复。
- 低延迟:协程的轻量级特性减少线程切换开销。
- 高可靠性:通过并发控制避免重复刷新,确保数据一致性。
- 可扩展性:易于集成至现有网络层,支持多种认证协议(如OAuth2、JWT)。
五、进阶思考:与百度智能云的结合
若应用后端部署于百度智能云,可进一步优化方案:
- 利用百度智能云的API网关服务,在网关层实现Token验证与刷新,减少客户端逻辑。
- 结合百度智能云的密钥管理服务(KMS),安全存储Refresh Token,避免本地泄露风险。
通过拦截器与协程的深度融合,开发者能够构建出既稳定又高效的Token管理方案,为Android应用提供无缝的认证体验。