使用协程访问数据库。

3

到目前为止,每当我想要访问数据库时,我都使用以下模式:

runBlocking {
    launch {
        // fetch something from the database and put it to some view
    }
}

现在我深入研究Kotlin协程,越来越确信这是一种不好的模式。基本上,我可能只要allowMainThreadQueries,因为我的模式无论如何都会阻塞主线程。

不幸的是,我还没有找到一个合适的模式。如何有效地使用Kotlin协程访问数据库?

runBlocking是进入协程的唯一入口吗?

考虑以下场景:

override fun onCreate() {
    setContentView(someLayout)

    // concurrently fetch something from the database and put it in some view
    // onCreate may return before this has finished

    someButton.setOnClickListener {
        // concurrently insert or update something in the database
    }
}

您提到了 allowMainThreadQueries,这听起来好像是在使用 Room。您为什么不使用 Room 内置的支持 Kotlin Flow / 协程进行异步查询呢?(https://developer.android.com/training/data-storage/room/async-queries) - ianhanniballake
1
我假设他们正在使用挂起函数和 Flows,并且他们说通过使用 runBlocking 调用挂起函数,实际上他们没有使用它们,可以使用 allowMainThreadQueries 来获得相同的结果,这是正确的。 - Tenfour04
2个回答

3
在Android项目中,除非您要将Kotlin协程代码与无法使用协程的一些Java代码混合,并且需要一种在其自己的后台线程上以阻塞方式调用某个协程的方法,否则永远不应该使用runBlocking。在这种情况下,您可以使用runBlocking创建一个桥接函数供Java代码调用,但您绝不会从Kotlin调用此函数,也绝不会从主线程调用它。在主线程上调用阻塞代码会冻结UI,使您的应用程序感觉不爽,并可能触发ANR(应用程序无响应)错误。
开始一个协程的正确方法是使用CoroutineScope来启动您的协程。对于Activities、Fragments和ViewModels,Android Jetpack框架已经为您提供了这些。
在Activity中使用。在Fragment中,通常应该使用。在ViewModel中使用。
使用CoroutineScope而不是runBlocking的好处是什么?它可以防止长时间运行的挂起操作(如从磁盘上读取数据库)阻塞主线程并冻结您的UI。它还可以在Activity/Fragment/ViewModel被撤消时自动取消长时间运行的工作,从而防止内存泄漏和资源浪费。

2
假设您正在使用 RoomrunBlockingallowMainThreadQueries 通常用于测试目的,您不应该在发布产品中使用它们。 allowMainThreadQueries 的作用是允许您从主线程访问数据库,这是您绝对不应该做的,因为它可能会冻结UI。
请使用 lifecycleScope.launchFragment/Activity 启动协程,或使用 viewModelScope.launchViewModel 启动。您可能需要明确添加依赖项。
def lifecycleVersion = '2.4.0'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
// Lifecycles only (without ViewModel or LiveData)
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")

生命周期发布说明 https://developer.android.com/jetpack/androidx/releases/lifecycle

为了防止配置更改时取消操作,您应该从ViewModel调用database操作。如果用户在操作正在进行时旋转屏幕,则操作将被取消,并且结果不会被缓存。

在Activity/Fragment中

// read data from db
lifecycleScope.launch {
   viewModel.someData.collect { 
      //do some stuff
   }
}

// insert
someButton.setOnClickListener {
   viewModel.insertToDatabase(someData)
}

在ViewModel中

class MainViewModel (val dao: Dao) : ViewModel() {
  
  // you should expose your data as Observable
  val someData : Flow<List<SomeData>> = dao.getAllSomeData()
   
  fun insertToDatabase(data:SomeData) = viewModelScope.launch {
    dao.insert(data)
  }
}

// query
@Query("SELECT * FROM some_data_table")
abstract fun getAllSomeData(): Flow<List<SomeData>>

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