使用协程访问数据库
Using coroutines to access the database
到目前为止,每当我想访问数据库时,我一直在使用这种模式:
runBlocking {
launch {
// fetch something from the database and put it to some view
}
}
现在我正在深入研究 Kotlin 协程,我越来越相信这是一个糟糕的模式。本质上,我还不如 allowMainThreadQueries
,因为我的模式无论如何都会阻塞主线程。
很遗憾,我还没有找到合适的模式。如何有效使用Kotlins协程访问数据库?
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
}
}
你应该 永远不要 在 Android 项目中使用 runBlocking
,除非你将 Kotlin 协程代码与一些 Java 代码混合'使用协同程序,它需要一种方法在它自己的后台线程中以阻塞方式调用某些协同程序。在这种情况下,您可能会使用 runBlocking
为要调用的 Java 代码创建桥接函数,但您永远不会从 Kotlin 调用此函数,当然也永远不会从主线程调用它。在主线程上调用阻塞代码会冻结 UI,这会使您的应用感觉卡顿并有可能触发 ANR(应用未响应)错误。
启动协程的正确方法是使用 CoroutineScope 启动协程。 Android Jetpack 框架已经为您提供了 Activities、Fragments 和 ViewModels。
在 Activity 中,使用 lifecycleScope.launch
。在 Fragment 中,通常应该使用 viewLifecycleOwner.lifecycleScope.launch
。在 ViewModel 中,使用 viewModelScope.launch
.
使用 CoroutineScope 而不是 runBlocking
有什么作用?它可以防止 long-running 暂停操作(例如从磁盘读取数据库)阻塞主线程并冻结您的 UI。并且当Activity/Fragment/ViewModel被拆除时,它会自动取消long-running的工作,因此它可以防止内存泄漏和资源浪费。
假设您正在使用 Room
,
runBlocking
和 allowMainThreadQueries
通常用于 测试 目的,你应该 永远不要 在发布中使用它们产品。
allowMainThreadQueries
所做的是授予您从 主线程 访问数据库的权限,您应该 永远不要这样做 ,因为它可能会冻结 UI.
使用 lifecycleScope.launch
从 Fragment/Activity
或 viewModelScope.launch
从 ViewModel
启动协程,您可能需要显式添加依赖项
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>>
到目前为止,每当我想访问数据库时,我一直在使用这种模式:
runBlocking {
launch {
// fetch something from the database and put it to some view
}
}
现在我正在深入研究 Kotlin 协程,我越来越相信这是一个糟糕的模式。本质上,我还不如 allowMainThreadQueries
,因为我的模式无论如何都会阻塞主线程。
很遗憾,我还没有找到合适的模式。如何有效使用Kotlins协程访问数据库?
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
}
}
你应该 永远不要 在 Android 项目中使用 runBlocking
,除非你将 Kotlin 协程代码与一些 Java 代码混合'使用协同程序,它需要一种方法在它自己的后台线程中以阻塞方式调用某些协同程序。在这种情况下,您可能会使用 runBlocking
为要调用的 Java 代码创建桥接函数,但您永远不会从 Kotlin 调用此函数,当然也永远不会从主线程调用它。在主线程上调用阻塞代码会冻结 UI,这会使您的应用感觉卡顿并有可能触发 ANR(应用未响应)错误。
启动协程的正确方法是使用 CoroutineScope 启动协程。 Android Jetpack 框架已经为您提供了 Activities、Fragments 和 ViewModels。
在 Activity 中,使用 lifecycleScope.launch
。在 Fragment 中,通常应该使用 viewLifecycleOwner.lifecycleScope.launch
。在 ViewModel 中,使用 viewModelScope.launch
.
使用 CoroutineScope 而不是 runBlocking
有什么作用?它可以防止 long-running 暂停操作(例如从磁盘读取数据库)阻塞主线程并冻结您的 UI。并且当Activity/Fragment/ViewModel被拆除时,它会自动取消long-running的工作,因此它可以防止内存泄漏和资源浪费。
假设您正在使用 Room
,
runBlocking
和 allowMainThreadQueries
通常用于 测试 目的,你应该 永远不要 在发布中使用它们产品。
allowMainThreadQueries
所做的是授予您从 主线程 访问数据库的权限,您应该 永远不要这样做 ,因为它可能会冻结 UI.
使用 lifecycleScope.launch
从 Fragment/Activity
或 viewModelScope.launch
从 ViewModel
启动协程,您可能需要显式添加依赖项
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>>