安卓Room数据库

✅ Room 的三个核心组件

组件 注解 作用
Entity @Entity 定义表结构,每个类表示数据库中的一张表
DAO @Dao 定义数据操作方法,如增删查改等
Database @Database 声明数据库类,连接 Entity 和 Dao

✅添加 Room 依赖项

  1. 向 Gradle 文件添加所需的 Room 组件库。

  2. 打开模块级 Gradle 文件 build.gradle.kts (Module: app)
    在 dependencies 代码块中,为 Room 库添加依赖项,如以下代码所示。

1
2
3
4
//Room
implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}")
ksp("androidx.room:room-compiler:${rootProject.extra["room_version"]}")
implementation("androidx.room:room-ktx:${rootProject.extra["room_version"]}")
  1. 打开项目级 Gradle 文件 build.gradle.kts (Project)
1
2
3
4
5
buildscript {
extra.apply {
set("room_version", "2.7,2")
}
}

✅Room Dao中的@Query

Dao负责定义映射到 SQL 语句的函数

建议在持久性层中使用 Flow。将返回值类型设为 Flow 后,只要数据库中的数据发生更改,就会收到通知。Room 会保持更新此 Flow,也就是说,只需要显式获取一次数据。
由于返回值类型为 Flow,Room 还会在后台线程上运行该查询。无需将其明确设为 suspend 函数并在协程作用域内调用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Dao
interface ItemDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: Item)

@Update
suspend fun update(item: Item)

@Delete
suspend fun delete(item: Item)

@Query("SELECT * from items WHERE id = :id") //:id 在查询中使用英文冒号来引用函数中的参数。
fun getItem(id: Int): Flow<Item>

@Query("SELECT * from items ORDER BY name ASC")
fun getAllItems(): Flow<List<Item>>
}

✅Room Database 标准单例写法

RoomDatabase负责实例化数据库并提供对 DAO 的访问权限

这段代码定义了一个名为 InventoryDatabase 的Room 数据库类,并通过 getDatabase() 方法确保全应用只创建一次数据库实例(单例模式)

可以将此代码用作项目的模板。创建 RoomDatabase 实例的方式与该步骤中的过程类似。只需要替换特定应用的EntityDAO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//告诉 Room:数据库包含哪些表(entities)、版本号是多少、是否导出架构
@Database(entities = [Item::class], version = 1, exportSchema = false)
abstract class InventoryDatabase : RoomDatabase(){
// 声明一个返回 ItemDao 的抽象函数
abstract fun itemDao(): ItemDao
//所有定义在 companion object 中的函数/变量都可以用类名直接访问
companion object {
@Volatile //这是一个线程安全相关的注解,确保多线程访问这个变量时能看到最新值,避免创建多个实例
private var Instance: InventoryDatabase ?= null
// 提供一个公开的函数来返回数据库实例,若已存在直接返回;否则创建一个新的
fun getDatabase(context: Context): InventoryDatabase{
return Instance ?: synchronized(this){
Room.databaseBuilder(context, InventoryDatabase::class.java, "item_database")
.build()
.also { Instance = it }
}
}
}

}

补充:Elvis 运算符的语法
val result = a ?: b
如果 a != null,则 result = a
如果 a == null,则 result = b

✅Flow 和 StateFlow

ItemDao 添加用于获取商品的 getItem()getAllItems() 方法时,将 Flow 指定为返回值类型。回想一下,Flow 代表通用数据流。通过返回 Flow,只需在指定生命周期内明确调用 DAO 中的方法一次即可。Room 以异步方式处理底层数据的更新。
但直接使用Flow会存在一些问题:

  • 配置更改等生命周期事件(例如旋转设备)会导致重新创建 activity,进而导致重组,并从 Flow 重新收集数据。
  • 将值缓存为状态,这样现有数据就不会在生命周期事件之间丢失。
  • 如果没有任何观察器(例如在可组合项的生命周期结束后),则应取消数据流。

为了解决这些问题,如需从 ViewModel 公开 Flow,推荐使用 StateFlow。无论界面生命周期如何,使用 StateFlow 均可保存和观察数据。如需将 Flow 转换为 StateFlow,您可以使用 stateIn 操作符。

Flow 用在一次性事件或后台数据流(比如从 Room 订阅变化)
StateFlow 常用来存储 ViewModel 中的 UI 状态,然后在 Compose 里用 collectAsState() 转成 ComposeState

下面是在HomeViewModel中使用StateFlow的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ItemDetailsViewModel(
savedStateHandle: SavedStateHandle,
private val itemsRepository: ItemsRepository
) : ViewModel() {

private val itemId: Int = checkNotNull(savedStateHandle[ItemDetailsDestination.itemIdArg])

companion object {
private const val TIMEOUT_MILLIS = 5_000L
}

val uiState: StateFlow<ItemDetailsUiState> =
itemsRepository.getItemStream(itemId) //这里 getItemStream(itemId) 返回的是 Flow<Item>
.filterNotNull()
.map { //.map { ... } 把每个 Item 转换成 ItemDetailsUiState,结果为得到 Flow<ItemDetailsUiState>
ItemDetailsUiState(itemDetails = it.toItemDetails())
}.stateIn( //stateIn将结果最终转换成 StateFlow<ItemDetailsUiState>
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS),
initialValue = ItemDetailsUiState()
)
}
  • Copyrights © 2023-2025 Hexo

请我喝杯咖啡吧~

支付宝
微信