OkHttp Authenticator重写方法中,协程异步调用等待结果的合理实现
OkHttp Authenticator异步刷新令牌的最优实现方案
我有一个继承自OkHttp Authenticator接口的类,需要在重写的authenticate()方法中发起异步网络调用获取刷新令牌,且必须立即将结果保存至SharedPreferences。目前我使用runBlocking阻塞线程实现需求,但了解到生产环境不建议使用该方法,这是我唯一能实现功能的方式。请问是否有更优方案,或是该场景下runBlocking的使用是可接受的?
现有实现代码
class RefreshTokenAuthenticator @Inject constructor( private val serviceApi: ServiceApi, private val sharedPreferences: SharedPreferences ) : Authenticator { override fun authenticate(route: Route?, response: Response): Request? { if (response.request.url.toString().contains("/refresh")) { return null } runBlocking(Dispatchers.IO) { val apiResponse = serviceApi.get().refreshToken() sharedPreferences.saveCredentials(apiResponse) }.let { isRefreshed -> if (isRefreshed) { val newToken = sharedPreferences.getToken().orEmpty() return response.request.newBuilder() .header("Authorization", "Bearer $newToken") .build() } else { return null } } } }
方案分析与优化
1. runBlocking的问题
OkHttp的authenticate()方法本身运行在OkHttp的工作线程池中,使用runBlocking(Dispatchers.IO)会额外占用线程资源,若刷新请求耗时较长,会阻塞整个线程池,影响其他正常请求的处理,生产环境中确实不推荐这种写法。
2. 最优方案:升级OkHttp至5+,使用AsyncAuthenticator
OkHttp 5.x版本引入了AsyncAuthenticator接口,其authenticate方法是挂起函数,可直接在内部执行异步操作,完全避免线程阻塞:
class RefreshTokenAuthenticator @Inject constructor( private val serviceApi: ServiceApi, private val sharedPreferences: SharedPreferences ) : AsyncAuthenticator { override suspend fun authenticate(route: Route?, response: Response): Request? { // 避免循环刷新 if (response.request.url.toString().contains("/refresh")) { return null } return try { // 异步调用刷新接口 val apiResponse = serviceApi.get().refreshToken() // 立即保存凭证 sharedPreferences.saveCredentials(apiResponse) // 构建带新令牌的请求 val newToken = sharedPreferences.getToken().orEmpty() response.request.newBuilder() .header("Authorization", "Bearer $newToken") .build() } catch (e: Exception) { // 刷新失败返回null,终止当前请求链 null } } }
配置OkHttpClient时替换为异步认证器:
val okHttpClient = OkHttpClient.Builder() .authenticator(AsyncAuthenticatorAdapter(RefreshTokenAuthenticator(...))) .build()
3. 无法升级OkHttp时的替代方案
若项目无法升级OkHttp版本,可使用CompletableFuture配合协程实现异步逻辑,同时设置超时避免无限阻塞线程:
class RefreshTokenAuthenticator @Inject constructor( private val serviceApi: ServiceApi, private val sharedPreferences: SharedPreferences ) : Authenticator { override fun authenticate(route: Route?, response: Response): Request? { if (response.request.url.toString().contains("/refresh")) { return null } val future = CompletableFuture<Request?>() // 启动协程执行异步刷新 CoroutineScope(Dispatchers.IO).launch { try { val apiResponse = serviceApi.get().refreshToken() sharedPreferences.saveCredentials(apiResponse) val newToken = sharedPreferences.getToken().orEmpty() future.complete( response.request.newBuilder() .header("Authorization", "Bearer $newToken") .build() ) } catch (e: Exception) { future.complete(null) } } // 等待异步结果,设置超时时间防止线程长期阻塞 return try { future.get(30, TimeUnit.SECONDS) } catch (e: Exception) { null } } }
4. 关于runBlocking的可接受性
如果项目受限于环境无法使用上述异步方案,runBlocking在小流量场景或刷新请求耗时极短的情况下可以临时使用,但必须注意:
- 尽量指定
Dispatchers.IO而非默认Dispatcher,减少对主线程的影响 - 务必添加异常捕获,避免协程崩溃导致线程永久阻塞
- 生产环境中仍建议优先迁移到异步方案
内容的提问来源于stack exchange,提问作者FP34024
相关产品推荐
相关产品推荐

