如何正确使用Kotlin协程与Android Room?

15
最近学习Kotlin协程时,阅读了几篇相关文章。但其中一篇让我感到困惑:Coroutines On Android (part III): Real work
正如它所指出的:
注意:Room使用自己的调度程序在后台线程上运行查询。您的代码不应该使用withContext(Dispatchers.IO)来调用挂起的room queries。这将使代码变得复杂并使查询运行速度变慢。
当我看到这段话时,似乎很有道理。但是当我打开一个Android项目并尝试深入了解时,问题出现了,Android Studio警告我:
挂起函数 'yourMethod' 只应从协程或另一个挂起函数中调用。
我现在陷入了困境,因为文章告诉我不要使用withContext(Dispatchers.IO)。我现在想知道是否应该使用withContext(Dispatchers.Main)还是使用GlobalScope.launch来运行我的查询?

5
“我现在在思考是应该使用withContext(Dispatchers.Main)还是使用GlobalScope.launch来运行我的查询?”-- 使用适当的 CoroutineScope 运行 launch()(或者可能是async()await())。这可以是 GlobalScope,但通常有更好的选择,比如在 ViewModel 上使用 viewModelScope,在 Activity 上使用 lifecycleScope,在 Fragment 上使用 viewLifecycleScope,或者像 此处 所示使用自定义的 CoroutineScope - CommonsWare
@CommonsWare 谢谢,我正在寻找一个不应该被取消的工作解决方案,你提供的链接正是我要找的,我会去查看一下。感谢你的帮助! - Carter Chen
请参考以下链接:https://github.com/sasisachin12/SQLRoomSample - sasikumar
如果您不想取消挂起函数操作,请尝试使用 withContext(NonCancellable) {}。 - diousk
@sasikumar,你的解决方案似乎对这个问题没有帮助。在你的项目中,“suspend”关键字被Android Studio标记为“冗余”。 - Carter Chen
你可以观看这个由6个视频组成的系列,链接为https://www.youtube.com/watch?v=cfcyYO7osnk&list=PLQkwcJG4YTCT0RouHZ6sQlE4JE6VyD2zO&ab_channel=PhilippLackner。 - Ömer Seyfettin Yavuzyiğit
3个回答

0

在你的repository中应该使用Dispatchers.IO,在你的viewModel中可以使用viewModelScope。


如果您有新的问题,请单击提问按钮进行提问。如果链接有助于提供上下文,请包括此问题的链接。- 来自审核 - Black4Guy

0

你无法在不从另一个挂起函数调用或从协程内部调用的情况下调用挂起函数。

我认为作者想要提到,我们在启动协程时应避免使用IO分发程序,而是依赖于默认分发程序。


0

假设你有一个如下的 Room DB DAO:

@Dao
interface RoomDao
{
    @Query("SELECT * FROM $TABLE_NAME WHERE $ITEM_ID = :itemId")
    suspend fun getEntry(itemId : String) : Entry?
}

请记住,getEntry是一个suspend fun,只能在suspend fun内部或在coroutineScope内部或使用runBlocking方法中调用(这些是协程框架处理/管理所有工作的方式)

现在,有几种调用上述方法的方法

1. 使用片段、活动或viewModel提供的coroutineScope

使用 coroutineScope 意味着您已经启动了一个 coroutine(可以将其视为轻量级的 Java Thread),并且不想等待它完成。有两种方式可以实现相同的效果,即 launchasync。从执行方式来看,它们都可以实现相同的功能。它们只是返回不同类型的处理程序,供您修改已启动的 coroutine。其中一种返回 Job,您可以通过它查询 coroutine 的状态并取消它,另一种返回 Deffered 对象,您可以通过它访问已启动的 coroutine 返回的值。

当您想要直接访问 Room DB 返回的值时,可以使用此方法。

  1. 在 Fragment 中:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    // your onViewCreated related code
    viewLifecycleOwner.lifecycleScope.launch { 
        val entry = DbInstance.getDao().getEntry("<yourEntryId>")
    }
}
  • 在一个活动中
  • override fun onCreate(savedInstanceState: Bundle?) {
        // your onCreate related code
        lifecycleScope.launch {
            val entry = DbInstance.getDao().getEntry("<yourEntryId>")
        }
    }
    
  • 在viewModel中
  • fun launchEntryCall(itemId: String) {
        viewModelScope.launch {
            val entry = DbInstance.getDao().getEntry("<yourEntryId>")
        }
    }
    

    使用 GlobalScope(不建议)
    fun launchEntryCall(itemId: String) {
        GlobalScope.launch {
            val entry = DbInstance.getDao().getEntry("<yourEntryId>")
        }
    }
    

    GlobalScope 不建议使用,因为你必须自己管理作为 GlobalScope 启动的协程的生命周期,而 GlobalScope 与提供的 android 类中的 coroutineScopes 集成不良,它们往往会使代码变得复杂。

    在 fragment 中使用 viewLifecycleOwner.lifecycleScope,在 activity 中使用 lifecycleScope,这些都是由 android 库提供的(您可能需要 add 这些)

    2. 在其他 suspend fun 中调用

    在某些情况下,当您想要调用多个类似的函数并对其进行操作以返回有意义的内容时,这将对您有所帮助。假设您创建了一个函数,该函数调用前一个 getEntry 并检查以下项目是否已从服务器删除,并返回该项目和 null(如果该项目已被删除)。您可以使用以下代码模拟相同的操作

    suspend fun getItemIfNotDeleted(itemId: String): Entry? {
        val entry = DbInstance.getDao().getEntry(itemId)
        entry ?: return null
        return if (isDeleted(entry.url)) {
            null
        } else {
            entry
        }
       
    }
    

    3. 使用 runBlocking不建议在生产代码中使用

    runBlocking 阻塞当前线程的执行,直到协程返回或抛出异常。因此,假设您想测试上述函数,您可以调用 runBlocking 并获取结果,然后像下面的代码片段一样进行断言测试。

    @Test
    fun verifyItemReturnedIsNotNull() {
        val entry = runBlocking {
            DbInstance.getDao().getEntry("<yourEntryId>")
        }
        assertNotNull(entry)
    }
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接