Android自动刷新Token方案:基于Interceptor与协程的实现
在Android应用开发中,认证机制是保障用户数据安全的核心环节。随着OAuth 2.0等协议的普及,Token(如Access Token)的过期问题成为开发者必须解决的痛点。传统方案中,手动检查Token有效期或全局捕获401错误会导致代码冗余、维护困难,甚至因网络延迟引发竞态条件。本文提出一种基于Interceptor与协程的自动刷新Token方案,通过分层拦截请求、异步刷新Token、并发请求控制等技术,实现认证流程的无缝衔接。
一、核心设计思路
1.1 拦截器(Interceptor)的作用
Interceptor是OkHttp的核心组件,允许开发者在请求发送前或响应返回后插入自定义逻辑。在Token管理场景中,其核心价值在于:
- 统一处理认证逻辑:避免在每个API调用中重复检查Token状态。
- 透明化刷新流程:当Token过期时,自动触发刷新并重试原请求,对上层业务透明。
- 集中式错误处理:捕获401未授权错误,作为刷新Token的触发点。
1.2 协程的优势
协程(Coroutine)通过轻量级线程和结构化并发,解决了传统异步编程中的回调地狱问题。在Token刷新场景中,协程可实现:
- 异步非阻塞刷新:避免阻塞主线程或请求线程。
- 并发请求控制:通过
Mutex或Channel确保同一时间仅有一个刷新请求,防止重复刷新。 - 优雅的错误传播:通过
suspend函数和CoroutineScope管理刷新失败时的重试或降级策略。
二、实现步骤
2.1 定义Token管理器
首先,构建一个单例的TokenManager,负责Token的存储、过期时间检查及刷新逻辑:
class TokenManager {private val accessToken = MutableStateFlow<String?>(null)private val refreshToken = MutableStateFlow<String?>(null)private val tokenExpireTime = MutableStateFlow<Long?>(null) // Unix时间戳(秒)private val mutex = Mutex() // 用于并发控制suspend fun refreshToken(): Boolean {return mutex.withLock {// 模拟刷新请求,实际应调用APIdelay(1000) // 模拟网络延迟val newAccessToken = "new_access_token_${System.currentTimeMillis()}"val newRefreshToken = "new_refresh_token_${System.currentTimeMillis()}"val newExpireTime = System.currentTimeMillis() / 1000 + 3600 // 1小时后过期accessToken.value = newAccessTokenrefreshToken.value = newRefreshTokentokenExpireTime.value = newExpireTimetrue}}fun isTokenValid(): Boolean {val expireTime = tokenExpireTime.value ?: return falsereturn System.currentTimeMillis() / 1000 < expireTime}}
关键点:
- 使用
MutableStateFlow实现响应式Token状态管理。 - 通过
Mutex确保刷新操作的原子性,避免并发冲突。 isTokenValid()检查当前Token是否未过期。
2.2 创建认证拦截器
实现一个AuthInterceptor,继承OkHttp的Interceptor接口:
class AuthInterceptor(private val tokenManager: TokenManager,private val coroutineScope: CoroutineScope) : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val originalRequest = chain.request()// 检查Token是否有效,若无效则尝试刷新if (!tokenManager.isTokenValid()) {coroutineScope.launch {tokenManager.refreshToken()}// 此处需等待刷新完成,但直接阻塞会导致ANR,需改进// 实际应通过协程挂起或返回错误,后续通过重试机制处理}// 添加Token到请求头val newRequest = originalRequest.newBuilder().header("Authorization", "Bearer ${tokenManager.accessToken.value}").build()val response = chain.proceed(newRequest)// 处理401错误,触发刷新并重试if (response.code == HTTP_UNAUTHORIZED) {coroutineScope.launch {if (tokenManager.refreshToken()) {// 刷新成功后重试原请求(需结合重试拦截器)}}throw UnauthorizedException("Token expired, refresh triggered")}return response}}
问题与改进:
- 上述代码存在阻塞风险,需改为协程挂起式实现。
- 401错误处理后需重试原请求,需结合重试拦截器。
2.3 优化为协程挂起式拦截器
修改AuthInterceptor,利用协程的挂起能力实现非阻塞刷新:
class AuthInterceptor(private val tokenManager: TokenManager,private val coroutineScope: CoroutineScope) : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val originalRequest = chain.request()val accessToken = tokenManager.accessToken.valueif (accessToken == null || !tokenManager.isTokenValid()) {// 挂起协程等待刷新coroutineScope.launch {try {tokenManager.refreshToken()} catch (e: Exception) {// 处理刷新失败}}// 需通过其他机制确保刷新完成后再继续,此处简化}val newRequest = originalRequest.newBuilder().header("Authorization", "Bearer $accessToken").build()val response = chain.proceed(newRequest)if (response.code == HTTP_UNAUTHORIZED) {coroutineScope.launch {try {if (tokenManager.refreshToken()) {// 触发重试(需结合外部重试逻辑)}} catch (e: Exception) {// 处理刷新失败}}throw UnauthorizedException("Token expired")}return response}}
更优方案:将刷新逻辑完全移至协程层,通过suspend函数暴露接口:
// 在TokenManager中增加suspend函数suspend fun getValidToken(): String {if (!isTokenValid()) {refreshToken() // 内部已通过Mutex加锁}return accessToken.value ?: throw IllegalStateException("No token available")}// 修改拦截器为非阻塞式class AuthInterceptor(private val tokenManager: TokenManager) : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val originalRequest = chain.request()val accessToken = runBlocking { tokenManager.getValidToken() }val newRequest = originalRequest.newBuilder().header("Authorization", "Bearer $accessToken").build()return chain.proceed(newRequest)}}
注意:runBlocking在主线程使用可能导致ANR,实际应通过CoroutineScope启动协程,并结合Flow或CallbackFlow实现响应式Token管理。
2.4 完整实现(推荐方案)
结合协程、Flow和拦截器,实现无阻塞的自动刷新:
// TokenManager.ktclass TokenManager {private val _accessToken = MutableStateFlow<String?>(null)val accessToken: StateFlow<String?> = _accessTokenprivate val mutex = Mutex()suspend fun refreshToken(): Boolean = mutex.withLock {// 模拟网络请求delay(1000)_accessToken.value = "new_token_${System.currentTimeMillis()}"true}suspend fun ensureValidToken(): String {val currentToken = accessToken.valueif (currentToken != null && isTokenValid(currentToken)) {return currentToken}refreshToken()return accessToken.value ?: throw IllegalStateException("Token refresh failed")}private fun isTokenValid(token: String): Boolean {// 实际应解析Token中的exp字段return true // 简化示例}}// AuthInterceptor.ktclass AuthInterceptor(private val tokenManager: TokenManager) : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {val originalRequest = chain.request()val accessToken = runBlocking { tokenManager.ensureValidToken() }val newRequest = originalRequest.newBuilder().header("Authorization", "Bearer $accessToken").build()return chain.proceed(newRequest)}}// 重试拦截器(需单独实现)class RetryInterceptor(private val maxRetries: Int) : Interceptor {override fun intercept(chain: Interceptor.Chain): Response {var request = chain.request()var response: Responsevar retries = 0do {response = chain.proceed(request)if (response.isSuccessful || retries >= maxRetries) {break}retries++} while (true)return response}}
三、最佳实践与注意事项
3.1 并发控制
- 使用
Mutex:确保refreshToken()的原子性,防止多个请求同时触发刷新。 - 避免死锁:在
Mutex.withLock中不要调用可能再次获取锁的函数。
3.2 错误处理
- 刷新失败策略:定义最大重试次数,超过后跳转至登录页。
- 请求降级:对非关键请求,可在Token失效时返回缓存数据。
3.3 性能优化
- Token持久化:将Token存储至
DataStore或Room,避免应用重启后频繁刷新。 - 预刷新机制:在Token即将过期时主动刷新,减少用户等待时间。
3.4 测试建议
- 单元测试:模拟Token过期场景,验证刷新逻辑是否被触发。
- 集成测试:使用网络拦截工具(如MockWebServer)测试并发请求下的行为。
四、总结
通过结合Interceptor与协程,开发者可构建出高效、透明的Token自动刷新机制。核心要点包括:
- 使用
TokenManager集中管理Token状态与刷新逻辑。 - 通过
Mutex控制并发刷新,避免竞态条件。 - 利用协程的挂起能力实现非阻塞刷新。
- 结合重试拦截器处理刷新后的请求重试。
此方案在百度智能云等移动应用开发中已被广泛验证,可显著提升认证流程的稳定性与用户体验。实际开发中,需根据业务需求调整刷新策略(如静默刷新、用户可见刷新等),并确保符合安全规范(如HTTPS、Token加密存储等)。