无法使用 GoogleSignInClient.silentSignIn() 在 OkHttpClient 身份验证器中检索 ID 令牌

Cannot use GoogleSignInClient.silentSignIn() to retrieve an id token in an OkHttpClient Authenticator

在我用 Kotlin 编写的 Android 应用程序中,我使用 Google 登录进行授权,并使用 JWT 令牌对我的后端进行身份验证 API。我的 JWT 令牌和 Google Sign-In 的 JWT 令牌都将在 1 小时后过期。

当对我的后端 API 的请求因 401 错误而失败时,我希望我的应用程序通过 Google 登录重新授权并通过我的 API 重新验证,所以我向 OkHttpClient 添加了一个验证器。

但是,当我 运行 下面的代码时,silentSignIn 任务永久锁定并且不响应 val result = Tasks.await(task)

我是不是遗漏了什么明显的东西?我找不到任何关于为什么 silentSignIn() 在从身份验证器调用时会锁定的原因。

在其他地方,我不使用 Tasks.await(task),因为我使用 OnCompleteListener 来更新 UI,更新 apiTokengoogleToken 变量,但是在下面的代码中,我想在请求失败时更新 apiTokengoogleToken,而不需要我项目中的其他代码将请求重新发送到后端 API .

TLDR:我想在 OkHttpClient 身份验证器中调用 GoogleSignIn.silentSignIn(),获取令牌并立即重试请求,但我的应用程序在 Tasks.await 时冻结。

    val RC_SIGN_IN = 9001
    var apiToken: String? = null
    var googleToken: String? = null

    val client: OkHttpClient = OkHttpClient
        .Builder()
        .authenticator(object : Authenticator {
            override fun authenticate(route: Route?, response: Response): Request? {
                return if (response.code == 401) {
                    // Configure sign-in to request the user's ID, email address, and basic
                    // profile. ID and basic profile are included in DEFAULT_SIGN_IN.
                    val gso =
                        GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                            .requestEmail()
                            .requestIdToken("token_goes_here")
                            .build()

                    // Build a GoogleApiClient with access to the Google Sign-In API and the
                    // options specified by gso.
                    val googleSignInClient = GoogleSignIn.getClient(this@MainActivity, gso)
                    val task = googleSignInClient.silentSignIn().addOnCompleteListener {
                        val result = it.getResult(ApiException::class.java)
                        googleToken = result?.idToken
                    }.addOnFailureListener {
                        val signInIntent = googleSignInClient.signInIntent
                        startActivityForResult(signInIntent, RC_SIGN_IN)
                    }

                    if (task.isSuccessful) {
                        googleToken = task.result?.idToken
                    } else {
                        try {
                            /**** The following line is where the routine locks up: ****/
                            val result = Tasks.await(task)
                            googleToken = result?.idToken
                        } catch (ex: ExecutionException) {
                            // task failed
                            Log.e(TAG, "authenticate error:{ex}")
                        } catch (ex: InterruptedException) {
                            // an interrupt occurred while waiting for the task to finish
                            Log.e(TAG, "authenticate error:{ex}")
                        }
                    }
                    apiToken = getApiToken(googleToken)
                    response.request
                        .newBuilder()
                        .header("Authorization", "Bearer $apiToken")
                        .build()
                } else {
                    null
                }
            }
        }
        )
        .build()

经过一番挖掘,我找到了答案。为了其他人在类似的船上,我将在下面记录问题和解决方案:

当我开始发现 Google Play Billing Library 以类似方式锁定的类似错误时,我的怀疑就增加了。一项调查显示我是从 runBlocking { } 上下文中调用我的 API get() 代码。 Google 使用与 OkHTTP 相同的线程,因此 runBlocking 导致 OkHTTP 之外的所有网络 I/O 被阻塞,直到 OkHTTP 完成。

解决方案是尽可能移除 runBlocking,通过 launch {} 异步加载数据,并在完成后使用 notifyDataSetChanged() 更新 RecyclerView。我还没有能够完全删除 runBlocking,但它现在很少使用,我已经重新设计了 Task.await() 块,使其超时,并在超时时调用意图:

try {
    val result = Tasks.await(task, 5, TimeUnit.SECONDS)
    googleToken = result?.idToken
} catch (ex: ExecutionException) {
    // task failed
    Log.e(TAG, "authenticate error:{ex}")
    val signInIntent = googleSignInClient.signInIntent
    startActivityForResult(signInIntent, RC_SIGN_IN)
} catch (ex: InterruptedException) {
    // an interrupt occurred while waiting for the task to finish
    Log.e(TAG, "authenticate error:{ex}")
    val signInIntent = googleSignInClient.signInIntent
    startActivityForResult(signInIntent, RC_SIGN_IN)
} catch (ex: TimeoutException) {
    // an timeout occurred while waiting for the task to finish
    Log.e(TAG, "authenticate error:{ex}")
    val signInIntent = googleSignInClient.signInIntent
    startActivityForResult(signInIntent, RC_SIGN_IN)
}

更好的解决方案是实施 ViewModels (https://developer.android.com/topic/libraries/architecture/viewmodel),我建议将其用于新项目,但由于截止日期紧迫,我未能在此版本中这样做。