比如以下这个场景:
/**
* 认证拦截器
* 负责在请求头中添加Token,并处理401未授权响应
*
* 注意:OkHttp拦截器运行在网络线程,路由跳转需要切换到主线程执行
*/
class AuthInterceptor(
private val context: Context,
private val tokenProvider: () -> String?
) : Interceptor {
companion object {
// 防止多个401响应同时触发多次跳转登录页
private val isHandlingUnauthorized = AtomicBoolean(false)
// 主线程Handler,用于切换线程执行路由跳转
private val mainHandler = Handler(Looper.getMainLooper())
}
override fun intercept(chain: Interceptor.Chain): Response {
val token = tokenProvider()
val request = chain.request().newBuilder().apply {
token?.let { header("Authorization", "Bearer $it") }
}.build()
val response = chain.proceed(request)
// 如果返回401,代表token失效
if (response.code == 401) {
handleUnauthorized()
}
return response
}
/**
* 处理401未授权响应
* 使用AtomicBoolean确保并发情况下只执行一次
* 切换到主线程执行路由跳转,避免TheRouter的ConcurrentModificationException
*/
private fun handleUnauthorized() {
// 使用CAS操作确保只有一个线程能够执行跳转逻辑
if (isHandlingUnauthorized.compareAndSet(false, true)) {
// 切换到主线程执行,避免TheRouter并发问题
mainHandler.post {
try {
// 清除本地token
CommonUtil.exitClearAccount()
// 跳转登录页
WrapperIntentUtil.toActivity(ARouterConfig.MainRouterConst.PHONE_LOGIN_HOST)
} finally {
// 延迟重置标志位,避免短时间内重复触发
mainHandler.postDelayed({
isHandlingUnauthorized.set(false)
}, 2000)
}
}
}
}
}
问题原因:
- OkHttp 的拦截器运行在网络线程池中
- 当多个网络请求同时返回 401 时,多个线程并发调用 TheRouter 的路由跳转
- TheRouter 内部的 SimpleArrayMap 不是线程安全的,导致 ConcurrentModificationException
解决方案:
- 切换到主线程执行路由跳转
mainHandler.post {
// 路由跳转代码
} - 使用 AtomicBoolean 防止重复跳转
private val isHandlingUnauthorized = AtomicBoolean(false)
// CAS操作确保只有一个线程能执行
if (isHandlingUnauthorized.compareAndSet(false, true)) {
// 执行跳转逻辑
}
- 延迟重置标志位
- 2秒后重置 isHandlingUnauthorized,避免短时间内重复触发
这样修改后:
- 路由跳转在主线程执行,避免了 TheRouter 的并发问题
- 即使有多个 401 响应同时到达,也只会触发一次跳转登录页