如何在TokenAuthenticator刷新token失败时显示错误并跳转登录页?
问题根因
你遇到的两个核心问题根因非常明确:
- 无限循环触发的原因有两点:
- 当前
authenticate方法中使用GlobalScope.launch异步执行刷新逻辑,方法直接返回null,OkHttp收到null返回值后会立即重试原请求,原请求携带旧Token依然会返回401,再次触发authenticate逻辑,形成死循环 - 普通
MutableLiveData是粘性事件,你没有对isRefreshTokenFail做消费后重置的处理,每次Activity生命周期回调时都会收到之前发送的true值,反复触发跳转逻辑
- 当前
- 刷新成功后新请求不生效的原因:你在协程里构建了新的带新Token的Request,但是没有作为方法返回值返回,实际不会生效
解决方案
步骤1:修复TokenAuthenticator核心逻辑
authenticate方法本身运行在OkHttp的后台线程,不会阻塞主线程,可以直接用runBlocking同步等待刷新结果,同时添加刷新中标记避免并发刷新:
class TokenAuthenticator( val preferenceHelperImp: PreferenceHelperImp, private val tokenApi: RefreshTokenApi, ) : Authenticator{ // 加锁防止多个请求同时触发重复刷新 private val refreshLock = Any() private var isRefreshing = false override fun authenticate(route: Route?, response: Response): Request? { // 已经在刷新中则直接返回null避免重复执行 if (isRefreshing) return null synchronized(refreshLock) { if (isRefreshing) return null isRefreshing = true } return runBlocking { var newRequest: Request? = null getUpdatedRefreshToken(RefreshTokenRequest(preferenceHelperImp.getRefreshToken()) ).collect { when (it) { is State.Success -> { preferenceHelperImp.setCurrentUserLoggedInMode(Constants.App.LoggedInMode.LOGGED_IN_MODE_SERVER) val accessToken = it.data.body()?.payload?.accessToken ?: "" val refreshToken = it.data.body()?.payload?.refreshToken ?: "" preferenceHelperImp.setAccessToken(accessToken) preferenceHelperImp.setRefreshToken(refreshToken) preferenceHelperImp.setUserInfo(Gson().toJson(TokenInfo.initFromToken(accessToken))) Session.current.userInfo = Gson().fromJson(preferenceHelperImp.getUserInfo(), TokenInfo::class.java) // 构建新请求作为方法返回值,OkHttp会自动重试这个新请求 newRequest = response.request.newBuilder() .header("Authorization", "Bearer $accessToken") .build() } is State.Fail, is State.Error -> { // 先清空本地存储的登录信息,避免后续请求重复触发刷新逻辑 preferenceHelperImp.clearLoginInfo() Session.current.userInfo = null // 发送刷新失败事件 Session.current.isRefreshTokenFail.postValue(true) newRequest = null } } } isRefreshing = false newRequest } } private fun getUpdatedRefreshToken(refreshTokenRequest: RefreshTokenRequest): Flow<State<LoginResponse>> { return object : NetworkBoundRepository<LoginResponse>() { override suspend fun fetchFromRemote(): retrofit2.Response<LoginResponse> = tokenApi.getRefreshToken(refreshTokenRequest) }.asFlow() } }
步骤2:替换普通MutableLiveData为单次事件类
避免粘性事件重复触发,自定义只会消费一次的SingleLiveEvent:
class SingleLiveEvent<T> : MutableLiveData<T>() { private val pending = AtomicBoolean(false) override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { super.observe(owner) { t -> if (pending.compareAndSet(true, false)) { observer.onChanged(t) } } } override fun setValue(t: T?) { pending.set(true) super.setValue(t) } override fun postValue(value: T) { pending.set(true) super.postValue(value) } }
修改Session类中的属性定义:
val isRefreshTokenFail = SingleLiveEvent<Boolean>()
步骤3:BaseActivity中添加观察逻辑
不需要给TokenAuthenticator传Context,直接在BaseActivity统一处理跳转即可:
// 在onCreate生命周期中添加观察 Session.current.isRefreshTokenFail.observe(this) { isFail -> if (isFail) { // 展示错误提示 Toast.makeText(this, "登录状态已过期,请重新登录", Toast.LENGTH_SHORT).show() // 跳转到登录页,清空任务栈避免回退 val intent = Intent(this, LoginActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } startActivity(intent) } }
可选方案:直接注入Application跳转
如果你不想用事件通知的方案,可以直接在Hilt模块中注入ApplicationContext给TokenAuthenticator,刷新失败时直接跳转:
// NetworkModule中修改提供逻辑 @Provides fun provideTokenAuthenticator( preferenceHelperImp: PreferenceHelperImp, tokenApi: RefreshTokenApi, @ApplicationContext context: Context ): TokenAuthenticator { return TokenAuthenticator(preferenceHelperImp, tokenApi, context) }
刷新失败时直接用Context跳转即可,注意要加FLAG_ACTIVITY_NEW_TASK标记。
内容的提问来源于stack exchange,提问作者Onur
相关产品推荐
相关产品推荐

