Kotlin Android Coroutines - 暂停功能似乎不在后台运行

时间:2018-03-19 16:28:42

标签: android kotlin kotlin-coroutines

我觉得我缺少一些关键部分来理解下面这段代码的工作原理:

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 {}包装db调用。它可以工作,这是否意味着协同程序只能调用其他协同程序?

1 个答案:

答案 0 :(得分:5)

协同程序不是在前台或后台运行。它们是关于被挂起的能力,就像本机线程被操作系统暂停一样,但是在你控制这种行为的层面上。

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

当你的“某些代码”遇到suspendCoroutine调用时,神奇的部分会出现:这是执行停止的地方,你在传递给suspendCoroutine的块中得到一个延续对象。你可以随心所欲地使用该对象,通常将其存储在某个地方然后再继续使用。

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

一个这样的库函数是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)
}

我还要补充一点,您最好创建自己的线程池而不是依赖于系统范围的CommonPool。有关详细信息,请参阅this thread