You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 13:55:28