Room 3.0 并不只是一个增量更新,它是对 Android 持久化库的一次底层重写。此版本的主要目标是支持 Kotlin 多平台 (KMP) ,使 Room 能够在 Android、iOS、JVM 和 Native 平台上运行。为了实现这一目标,该库进行了现代化改造,采用了全新的包结构,转向了 Kotlin 符号处理 (KSP) ,并提供了完全以协程为核心的 API。
本文重点介绍了 Room 3.0 中引入的关键变化和新 API,并将其与大家熟悉的 Room 2.x 模式进行了对比。
重大转变:Kotlin 多平台与包结构变化
为了在不破坏现有 Android 应用的前提下支持 KMP,Room 3.0 迁移到了全新的包命名空间:androidx.room3。
- Room 2.x:
androidx.room.* - Room 3.0:
androidx.room3.*
这一改动允许开发者进行增量迁移。你可以在同一个项目中同时使用 Room 2.x 和 Room 3.0(尽管它们会管理各自独立的数据库)。
KSP 现已成为强制要求
Room 3.0 完全停止了对 KAPT (Kotlin Annotation Processing Tool) 的支持。你必须使用 KSP (Kotlin Symbol Processing) 进行代码生成。
// build.gradle.kts
dependencies {
implementation("androidx.room3:room3-runtime:3.0.0-alpha01")
ksp("androidx.room3:room3-compiler:3.0.0-alpha01")
// Optional: Bundled SQLite driver for consistent behavior across platforms
implementation("androidx.sqlite:sqlite-bundled:2.5.0-alpha01")
}
KMP 适配的实例化:@ConstructedBy
“现代化”改造中最大的变化之一在于数据库的实例化方式。在 Android(Room 2.x)上,Room 使用反射(Class.newInstance)来创建生成的数据库实现类。而在 iOS (Native) 等平台上,反射受到限制或无法使用。
Room 3.0 引入了 @ConstructedBy 注解,用于在编译时将你的抽象数据库类与其生成的实现类构造函数进行关联。
Room2.x
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase()
Room3.0
import androidx.room3.ConstructedBy
import androidx.room3.Database
import androidx.room3.RoomDatabaseConstructor
@Database(entities = [User::class], version = 1)
@ConstructedBy(AppDatabaseConstructor::class) // Links to generated constructor
abstract class AppDatabase : RoomDatabase()
// This interface is generated by KSP
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase>
SQLite 驱动抽象
Room 3.0 将库本身与 Android 框架的 SQLiteOpenHelper 进行了解耦。它现在使用通用的 SQLiteDriver 接口。
这允许你接入不同的驱动程序。对于 KMP(Kotlin 多平台)项目,官方推荐使用 Bundled SQLite 驱动。它会将特定版本的 SQLite 直接编译到你的应用中,从而确保你的数据库在 iOS 17、Android 14 以及 Windows 上的行为表现完全一致。
// Common Main
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFile = getDatabasePath()
return Room.databaseBuilder<AppDatabase>(name = dbFile.absolutePath)
.setDriver(BundledSQLiteDriver()) // Use the bundled driver
.setQueryCoroutineContext(Dispatchers.IO)
}
API 现代化:协程优先
Room 3.0 在更深层级上拥抱了 Kotlin 协程。虽然 Room 2.x 在基于 Java 的底层实现之上增加了对 suspend 的支持,但 Room 3.0 的核心是完全围绕挂起函数 (Suspending Functions) 和 Flow 构建的。
事务 (Transactions)
在 Room 2.x 中,事务通常会阻塞线程。Room 3.0 引入了挂起事务作用域 (Suspending Transaction Scopes) ,这种方式更加安全且符合 Kotlin 编程习惯。
Room 2.x
// Blocks the current thread
roomDatabase.runInTransaction {
userDao.insert(user)
accountDao.update(account)
}
Room3.0
// Suspends the coroutine; non-blocking
roomDatabase.withWriteTransaction {
// Executes in an IMMEDIATE transaction scope
userDao.insert(user)
accountDao.update(account)
}
对于需要一致性的只读操作,你可以使用 withReadTransaction:
val userWithDetails = roomDatabase.withReadTransaction {
// Executes in a DEFERRED transaction scope
val user = userDao.getUser(userId)
val details = detailsDao.getDetails(userId)
UserWithDetails(user, details)
}
连接管理
Room 3.0 开放了对数据库连接的底层控制,这对于在多平台环境中管理并发至关重要。
- useWriterConnection: 获取唯一的排他性写连接。
- useReaderConnection: 获取可用的读连接之一。
// Explicitly use the writer connection
roomDatabase.useWriterConnection { transactor ->
transactor.immediateTransaction {
// Perform complex write operations
}
}
响应式失效 (Reactive Invalidation)
InvalidationTracker 已升级,现支持直接创建 Flow。这使你能够构建响应式流水线,只要特定表发生修改,就会触发相应操作。
- Room 2.x: 通常依赖 DAO 返回的
LiveData或Flow。 - Room 3.0: 你可以通过编程方式直接监听表的变化:
fun getArtistTours(from: Date, to: Date): Flow<Map<Artist, TourState>> {
// Create a Flow that emits whenever the "Artist" table is modified
return db.invalidationTracker.createFlow("Artist").map { _ ->
val artists = artistsDao.getAllArtists()
val tours = tourService.fetchStates(artists.map { it.id })
associateTours(artists, tours, from, to)
}
}
自定义 DAO 返回类型
Room 3.0 引入了一项强大的新功能:自定义 DAO 返回类型。在之前的版本中,你仅限于使用 Room 显式支持的返回类型(如 Flow、LiveData、PagingSource 等)。如果你想返回自定义类型,通常必须编写额外的包装代码。
在 Room 3.0 中,你可以定义自己的 DaoReturnTypeConverter 来处理任何所需的返回类型。
如何实现
定义转换器: 创建一个类或对象,并在其中的函数上添加 @DaoReturnTypeConverter 注解。该函数会接收数据库实例、查询详情以及用于执行查询的 continuation。
object MyResultConverter {
@DaoReturnTypeConverter(operations = [OperationType.READ])
fun <T : Any> convert(
db: RoomDatabase,
tableNames: Array<String>,
query: RoomRawQuery,
execute: suspend (RoomRawQuery) -> T
): MyResult<T> {
// You can execute the query and wrap the result
return MyResult.Success(runBlocking { execute(query) })
}
}
注册转换器: 在你的 Database 或 DAO 类上添加 @DaoReturnTypeConverters 注解。你可以将其应用于全局的 @Database 级别,也可以专门应用于某个 @Dao 接口。
@Database(entities = [Note::class], version = 1)
@DaoReturnTypeConverters(MyResultConverter::class)
abstract class AppDatabase : RoomDatabase() { ... }
// Or specifically on a DAO
@Dao
@DaoReturnTypeConverters(MyResultConverter::class)
interface NoteDao { ... }
在 DAO 中使用: 现在你可以在 DAO 方法中直接使用你的自定义返回类型了。
@Dao
interface NoteDao {
@Query("SELECT * FROM note")
fun getNotes(): MyResult<List<Note>>
}
这项功能对于将 Room 与自定义的响应式库、结果类型(Result types)或日志框架进行集成特别有用。
预定义的自定义转换器
Room 3.0 为常用库提供了多个内置的 DaoReturnTypeConverter 实现。如果你想使用它们,必须进行显式注册。
例如,若要从 DAO 返回 PagingSource,你需要注册 PagingSourceDaoReturnTypeConverter(该转换器位于 androidx.room3:room3-paging 库中)。如果没有注册,KSP 会报错,提示类似:“[ksp] 无法确定如何将查询结果转换为此函数的返回类型”。
同样,如果你想返回 RxJava2 或 RxJava3 类型(如 Flowable、Observable),必须分别注册 RxJava2DaoReturnTypeConverter 或 RxJava3DaoReturnTypeConverter。这些转换器默认不启用,你必须通过添加 @DaoReturnTypeConverters 注解来手动开启。
@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface NoteDao {
// This now works because the converter is registered!
@Query("SELECT * FROM note")
fun getNotesPagingSource(): PagingSource<Int, Note>
}
对比:@TypeConverter vs @DaoReturnTypeConverter
这两者很容易混淆,但它们解决的是完全不同的问题:
- @TypeConverter (Room 2.x 标准功能): 转换单个列的数据值。当你需要存储 SQLite 原生不支持的类型时使用它。例如:将
Date对象保存为Long时间戳,或将UUID保存为String。 - @DaoReturnTypeConverter (Room 3.0 新功能): 转换 DAO 函数的方法返回类型。当你希望 DAO 返回一个自定义容器时使用它。例如:特定的响应式类型(如 RxJava3 的
Observable)或自定义的结果包装类(如MyResult<List<Note>>)。
在 Room 3.0 中,你很可能会同时用到这两者:
- 使用
@TypeConverter来告诉 Room 如何保存你的Date字段(用法与 Room 2.x 完全一致)。 - 使用
@DaoReturnTypeConverter如果你希望你的 DAO 直接返回MyCustomResponse<List<Note>>这种自定义类型。
// Example: Using both in AppDatabase
@Database(entities = [Note::class], version = 1)
@TypeConverters(Converters::class) // Standard Room 2.x style converters for column data
@DaoReturnTypeConverters(MyResultConverter::class) // New Room 3.0 converters for return types
abstract class AppDatabase : RoomDatabase() { ... }
结论
Room 3.0 是一个巨大的飞跃。通过与 Android 框架解耦并全面拥抱 KMP(Kotlin 多平台),它确保了你的数据层逻辑可以在不同平台间共享。全新的 androidx.room3 包结构和 KSP 优先的方案为现代化开发奠定了清爽的基础,而针对事务和连接的协程原生 API,则让该库在现代 Kotlin 应用中变得更加安全、强大。
随着生态系统向多平台迈进,Room 3.0 将成为本地持久化存储的新标准。