Kotlin Android 协程 - 挂起函数似乎不在后台 运行

Kotlin Android Coroutines - suspend function doesn't seem to run in the background

我觉得我在理解下面这段代码的工作原理时遗漏了一些关键部分:


private fun retrieveAndStore() {
        launch(UI) {
            val service = retrofit.create(AWSService::class.java)
            val response = service.retrieveData().await()
            store(data = response)
        }
    }

    private suspend fun store(data: JsonData) {
        val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app-db").build()
        db.appDao().insert(storyData)
    }

这是我在 运行:

时得到的错误
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

我不明白为什么通过改造的网络代码有效,但商店功能失败。我希望有人能告诉我这是怎么回事?

有趣的是,如果我用 async {}.await 包装数据库调用它可以工作,这是否意味着协程只能调用其他协程?

协程与 运行 在前台或后台中的宁无关。它们是关于暂停的能力,就像本机线程被 OS 暂停一样,但在您控制该行为的级别上。

当你说 launch(UI) { some code } 时,你告诉 Kotlin 将 "some code" 作为任务提交给 GUI 事件循环。它将在 GUI 线程上 运行 直到明确挂起;唯一的区别是它不会立即 运行 所以 launch(UI) 块下面的下一行代码将在它之前 运行。

当你的 "some code" 遇到一个 suspendCoroutine 调用时,神奇的部分就出现了:这是它的执行停止的地方,你在传递给 suspendCoroutine 的块中得到一个延续对象。您可以随心所欲地处理该对象,通常将其存储在某个地方,然后稍后再恢复。

通常您看不到 suspendCoroutine 调用,因为它在您正在调用的某些 suspend fun 的实现中,但您可以自由实现自己的调用。

一个这样的库函数是withContext,它是您解决问题所需要的。它使用您传递给它的块创建另一个协程,将该协程提交到您指定的其他上下文(一个有用的示例是 CommonPool),然后挂起当前协程直到另一个协程完成。这正是您将阻塞调用转换为可挂起函数所需要的。

在你的情况下,它看起来像这样:

private suspend fun store(data: JsonData) = withContext(CommonPool) {
    val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app-db").build()
    db.appDao().insert(storyData)
}

我还要补充一点,您最好创建自己的线程池,而不是依赖 system-wide CommonPool。详见