编写异步 DAO 查询

为防止查询阻止界面,Room 不支持在主线程上访问数据库。此限制意味着您必须将 DAO 查询 设为异步。Room 库包含与多个框架的集成,以提供异步查询执行功能。

DAO 查询分为三类:

  • 单次写入查询,用于在数据库中插入数据或者更新或删除其中的数据。
  • 单次读取查询,是指仅从数据库中读取一次数据,并在执行时返回带有数据库快照的结果。
  • 可观察读取查询,是指每当底层数据库表发生变化时,都会从数据库中读取数据,并发出新值来反映这些更改。

语言和框架选项

Room 可为涉及特定语言功能和库的互操作性提供集成支持。下表根据查询类型和框架展示了适用的返回值类型:

查询类型 Kotlin 语言功能(原生) RxJava 番石榴粉 Jetpack 生命周期*
单次写入 协程 (suspend) Single<T>Maybe<T>Completable ListenableFuture<T> 不适用
单次读取 协程 (suspend) Single<T>Maybe<T> ListenableFuture<T> 不适用
可观察读取 Flow<T> Flowable<T>Publisher<T>Observable<T> 不适用 LiveData<T>

本指南介绍了三种方法,让您可以使用这些集成在 DAO 中实现异步查询。

Kotlin 与 Flow 和协程

Kotlin 提供了内置语言功能,让您无需使用第三方框架即可编写异步查询:

协程和 Flow 支持直接内置于核心 Room 运行时,因此无需其他工件。

适用于 Kotlin 和 Java 的 RxJava

Room 3.0 支持 RxJava 3 返回值类型。如需使用 RxJava 返回值类型,您必须在数据库或 DAO 中注册 RxJava 返回值转换器:

  1. 在 build 配置中添加 androidx.room3:room3-rxjava3 工件。
  2. 使用 @DaoReturnTypeConverters(RxDaoReturnTypeConverters::class) 为您的 @Database@Dao 声明添加注解。

Room 支持以下 RxJava 3 返回值类型:

LiveData 和番石榴粉

Room 3.0 使用转换器支持 LiveData 和番石榴粉 ListenableFuture 返回值类型:

  • LiveData:添加 androidx.room3:room3-livedata 工件,并 使用 @DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class)为数据库或 DAO 添加注解。
  • 番石榴粉:添加 androidx.room3:room3-guava 工件,并使用 为数据库或 DAO 添加注解 @DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class)

编写异步单次查询

单次查询是指仅执行一次并在执行时获取数据快照的数据库操作。以下是异步单次查询的一些示例:

@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    suspend fun loadUserById(id: Int): User

    @Query("SELECT * from user WHERE region IN (:regions)")
    suspend fun loadUsersByRegion(regions: List<String>): List<User>
}

编写可观察查询

可观察查询是指在引用的表发生更改时发出新值的读取操作。例如,您可以使用此行为在数据库发生更改时及时更新显示的列表项。下面是可观察查询的一些示例:

@Dao
interface ObservableUserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    fun loadUserById(id: Int): Flow<User>

    @Query("SELECT * from user WHERE region IN (:regions)")
    fun loadUsersByRegion(regions: List<String>): Flow<List<User>>
}

手动跟踪数据库失效

当您需要手动构建可观察数据库操作时,可以使用 createFlow API 的 InvalidationTracker。借助此 API,您可以创建一个 Flow,用于跟踪对特定表的修改,并在这些表发生更改时发出通知。

fun getArtistTours(db: RoomDatabase, from: Date, to: Date): Flow<Map<Artist, TourState>> {
    return db.invalidationTracker.createFlow("Artist").map { _ ->
        val artists = artistsDao.getAllArtists()
        val tours = tourService.fetchStates(artists.map { it.id })
        associateTours(artists, tours, from, to)
    }
}

默认情况下,返回的 Flow 会发出一个包含所有已注册表的初始值,以启动流。您可以通过将 emitInitialState 参数设置为 false 来停用此行为。

自定义 DAO 返回值类型转换器

对于 Room 或其扩展库未直接支持的类型,您可以定义自定义 DAO 返回值转换器来支持其他返回值类型。如需将 DAO 函数的结果转换为自定义类型, 请使用 @DaoReturnTypeConverter 为转换器函数添加注解。

例如,您可以定义一个转换器,该转换器使用 androidx.tracing 在查询执行前后添加跟踪部分,以通过将执行封装在自定义 TracedQuery 类型中来监控对性能敏感的查询:

class TracedQuery<T>(val result: T)

object TracingDaoReturnTypeConverter {
    @DaoReturnTypeConverter([OperationType.READ])
    suspend fun <T> convert(
        rawQuery: RoomRawQuery,
        executeAndConvert: suspend () -> T
    ): TracedQuery<T> {
        val result = trace("TracedQuery: ${rawQuery.sql}") {
            executeAndConvert()
        }
        return TracedQuery(result)
    }
}

如需使用转换器,请使用 @DaoReturnTypeConverters为数据库或 DAO 添加注解:

@Dao
@DaoReturnTypeConverters(TracingDaoReturnTypeConverter::class)
interface MusicDao {
    @Query("SELECT * FROM Song")
    suspend fun getAllSongs(): TracedQuery<List<Song>>
}

控制 DAO 返回值转换器初始化

通常,Room 会处理 DAO 返回值类型转换器的实例化。不过,如果您必须将其他依赖项传递给转换器类,则应用必须直接控制其初始化。如果是这样,请使用您的 转换器类为 @ProvidedDaoReturnTypeConverter 添加注解:

@ProvidedDaoReturnTypeConverter
class TracingDaoReturnTypeConverter(val tracer: Tracer) {
    @DaoReturnTypeConverter([OperationType.READ])
    suspend fun <T> convert(
        rawQuery: RoomRawQuery,
        executeAndConvert: suspend () -> T
    ): TracedQuery<T> {
        val result = tracer.trace("TracedQuery: ${rawQuery.sql}") {
            executeAndConvert()
        }
        return TracedQuery(result)
    }
}

然后,除了在 @DaoReturnTypeConverters中声明转换器类之外,您还可以使用 RoomDatabase.Builder.addDaoReturnTypeConverter函数将 转换器类的实例传递给RoomDatabase构建器:

val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name")
    .addDaoReturnTypeConverter(TracingDaoReturnTypeConverter(myLoggerInstance))
    .build()

转换器函数要求

@DaoReturnTypeConverter 函数必须满足以下几项要求:

  • 它必须将函数参数作为最后一个实参,通常命名为 executeAndConvert。此参数是 Room 生成的 suspend lambda,用于执行查询和解析结果。
    • 如果转换器需要转换查询(例如分页),则 lambda 可以采用 RoomRawQuery 参数。
  • 它可以选择在 lambda 之前接受以下参数:
    • db: RoomDatabase:访问数据库实例,这对于获取协程范围或执行其他操作非常有用。
    • tableNames: Array<String>List<String>:提供查询访问的表的名称,这对于可观察类型非常有用。
    • rawQuery: RoomRawQuery:提供查询的运行时实例。
    • inTransaction: Boolean:指示查询是否在事务中执行。