Kotlin Coroutine 等待 Retrofit Response

Kotlin Coroutine wait for Retrofit Response

我正在尝试将 Android MVVM 模式与存储库 class 和 Retrofit 一起用于网络调用。我有一个常见的问题,我无法让协程等待网络响应 return。

这个方法在我的ViewModel class:

private fun loadConfigModel() {
    val model = runBlocking {
        withContext(Dispatchers.IO) {
            configModelRepository.getConfigFile()
        }
    }
    configModel.value = model
}

ConfigModelRepository 中,我有这个:

suspend fun getConfigFile(): ConfigModel {
    val configString = prefs.getString(
        ConfigViewModel.CONFIG_SHARED_PREF_KEY, "") ?: ""

    return if (configString.isEmpty() || isCacheExpired()) {
        runBlocking { fetchConfig() }
    } else {
        postFromLocalCache(configString)
    }
}

private suspend fun fetchConfig(): ConfigModel {
    return suspendCoroutine { cont ->
         dataService
            .config()  // <-- LAST LINE CALLED
            .enqueue(object : Callback<ConfigModel> {
                 override fun onResponse(call: Call<ConfigModel>, response: Response<ConfigModel>) {
                    if (response.isSuccessful) {
                        response.body()?.let {
                            saveConfigResponseInSharedPreferences(it)
                            cont.resume(it)
                        }
                    } else {
                        cont.resume(ConfigModel(listOf(), listOf()))
                    }
                }

                override fun onFailure(call: Call<ConfigModel>, t: Throwable) {
                    Timber.e(t, "config fetch failed")
                    cont.resume(ConfigModel(listOf(), listOf()))
                }
            })
    }
}

我的代码运行到 dataService.config()。它从不输入 onResponseonFailure。网络调用正常进行 returns(我可以使用 Charles 看到这一点),但协程似乎没有在监听回调。

所以,我的问题很常见。我怎样才能让协程阻塞,以便它们等待来自 Retrofit 的回调?谢谢。

问题一定是 response.body() returns null 因为这是唯一缺少对 cont.resume() 的调用的情况。确保在这种情况下也调用 cont.resume(),你的代码至少应该不会卡住。

但是就像 CommonsWare 指出的那样,更好的办法是升级到 Retrofit 2.6.0 或更高版本并使用本机 suspend 支持而不是滚动您自己的 suspendCoroutine 逻辑。

您也应该完全停止使用 runBlocking。在第一种情况下, launch(Dispatchers.Main) 改为一个协程并在其中移动 configModel.value = model 。在第二种情况下,您可以删除 runBlocking 并直接调用 fetchConfig()