7 月 1 日,androidx.room3:room3-*:3.0.0 正式发布。
这次不是 Room 2.x 的小版本升级。Room 3.0 换到了新的 androidx.room3 包和 Maven group,核心方向是 Kotlin Multiplatform,同时把 SupportSQLite、KAPT、Java 代码生成这些旧入口从主路径里移了出去。

依赖入口
Room 3.0 的坐标已经从 androidx.room 变成 androidx.room3。如果只是把版本号从 2.x 改成 3.0.0,Gradle 依赖会先找不到原来的 artifact。
bash
plugins {
id("com.google.devtools.ksp")
id("androidx.room3") version "3.0.0"
}
dependencies {
val roomVersion = "3.0.0"
implementation("androidx.room3:room3-runtime:$roomVersion")
ksp("androidx.room3:room3-compiler:$roomVersion")
}
room3 {
schemaDirectory("$projectDir/schemas")
}
这里有两个点不要漏。第一,Room 3.0 要求 KSP,不能再用 KAPT 或 Java annotation processor 配置 room3-compiler。第二,模块里要配置 com.google.devtools.ksp 插件,KSP 版本仍然要和项目 Kotlin 版本对齐。
包名也要一起改。androidx.room.RoomDatabase 变成 androidx.room3.RoomDatabase,@Database、@Entity、@Dao、@Query 这些核心注解还在,只是 import 路径换了。Room 2.x 和 Room 3.0 可以因为包名不同而并存,这能减少 WorkManager 这类间接依赖带来的冲突。

新包名
新包名不是单纯为了"看起来像大版本"。Room 2.x 生态里有很多库会传递依赖 androidx.room,如果 Room 3.0 仍然沿用旧包名,迁移时很容易出现 ABI、注解处理器和运行时代码混在一起的问题。
换到 androidx.room3 以后,旧代码可以继续留在 androidx.room,新模块单独接 androidx.room3。这对大项目更实际:可以先把数据库集中在一个 data 模块里迁移,而不是一次性改完整个仓库。
bash
// Room 2.x
import androidx.room.Database
import androidx.room.RoomDatabase
// Room 3.0
import androidx.room3.Database
import androidx.room3.RoomDatabase
如果项目里有公共封装,例如 AppDatabaseProvider、RoomModule、DatabaseFactory,先从这些位置改 import 和 builder。业务层 DAO 调用如果本来只暴露 suspend / Flow,改动会少很多;如果业务层拿到了 SupportSQLiteDatabase 或 Cursor,后面就要拆得更细。
SQLiteDriver
Room 3.0 的数据库底层改成 SQLiteDriver。主线 API 不再引用 SupportSQLiteDatabase、SupportSQLiteOpenHelper,也不再把 Android 的 Cursor 放在核心路径里。这个变化直接服务于 KMP,因为 common 代码不能依赖 Android 专有类型。
最常见的直接事务写法要改成 suspend API:
bash
// Room 2.x
database.runInTransaction {
dao.insert(user)
dao.updateCount(user.id)
}
// Room 3.0
database.withWriteTransaction {
dao.insert(user)
dao.updateCount(user.id)
}
直接查询也不再拿 Cursor。Room 3.0 给的是连接和 statement:
bash
database.useReaderConnection { connection ->
connection.usePrepared("SELECT name FROM User WHERE id = ?") { stmt ->
stmt.bindLong(1, userId)
if (stmt.step()) {
stmt.getText(0)
} else {
null
}
}
}
迁移回调也跟着变了。以前 Migration.migrate()、RoomDatabase.Callback.onCreate() 里常见的是 SupportSQLiteDatabase,Room 3.0 对应的是 SQLiteConnection。仓库里如果有手写 SQL、建索引、补默认值、数据修复脚本,这些 callback 是迁移时最容易先报错的位置。

兼容包装
Room 3.0 没有完全堵死 SupportSQLite。如果项目里还有第三方工具或旧封装只能吃 SupportSQLiteDatabase,可以临时加 androidx.room3:room3-sqlite-wrapper。
bash
dependencies {
implementation("androidx.room3:room3-sqlite-wrapper:3.0.0")
}
原来从 openHelper 拿 writable database 的代码,可以改成 wrapper:
bash
// Room 2.x
val db = database.openHelper.writableDatabase
// Room 3.0
val db = database.getSupportWrapper()
这个 artifact 更像迁移缓冲。新代码还是应该往 SQLiteDriver 和协程 API 走,否则只是把旧依赖包了一层,后面接 KMP、Web 或 common 代码时还会碰到 Android 类型边界。
协程优先
Room 3.0 要求数据库操作走协程。DAO 函数如果不是返回 Flow、PagingSource 或自定义 DAO return type,就需要是 suspend。
bash
@Dao
interface UserDao {
@Query("SELECT * FROM User WHERE id = :id")
suspend fun findById(id: Long): User?
@Query("SELECT * FROM User ORDER BY name")
fun observeUsers(): Flow<List<User>>
}
数据库 builder 也不再配置 Executor。如果需要指定线程,改成传 CoroutineContext 和 dispatcher。这样 Room 在 Android、iOS、desktop、web 这些平台上可以使用同一套协程模型。
InvalidationTracker.Observer 这一套也移除了,addObserver()、removeObserver() 不再是 Room 3.0 的观察入口。新的写法是从 InvalidationTracker.createFlow() 拿 Flow,再在业务里组合查询结果。
bash
fun observeUserTable(): Flow<List<User>> {
return database.invalidationTracker
.createFlow("User")
.map { userDao.loadAll() }
}
这段代码的关键不是多了一层 Flow,而是失效通知和 DAO 查询都进入协程模型。之前如果项目里有手动 observer,再自己切线程、发 LiveData 或 callback,迁移时可以把这一段收回到 Flow 链路里。

Paging 写法
Room 2.x 里,PagingSource、RxJava、Guava、LiveData 这些返回类型有内置集成。Room 3.0 把它们改成 DAO return type converter,需要在 @Dao 或 @Database 上注册 converter。
以 Paging 为例,依赖还是单独加:
bash
dependencies {
implementation("androidx.room3:room3-paging:3.0.0")
}
DAO 上要显式注册 PagingSourceDaoReturnTypeConverter:
bash
@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface SongDao {
@Query("SELECT * FROM Song ORDER BY name")
fun pagingSource(): PagingSource<Int, Song>
}
这个变化会影响已有分页列表。以前只要返回 PagingSource<Int, Entity> 就能过编译,现在缺少 converter 时,Room 编译期会报返回类型不支持。RxJava3、Guava、LiveData 也是同一类问题,对应 artifact 分别是 room3-rxjava3、room3-guava、room3-livedata。
DAO return type converter 也可以自定义。Room 生成代码负责执行 SQL 和转换实体,converter 决定最终包成什么返回类型。Web 侧如果要把 DAO 返回值包装成 Promise,就属于这类扩展。
KMP 和 Web
Room 3.0 面向 KMP 的变化不只停在 Android 和 iOS。它新增了 JavaScript 和 WasmJS 目标,配合 androidx.sqlite:sqlite 的 SQLiteDriver 接口,以及 androidx.sqlite:sqlite-web 里的 WebWorkerSQLiteDriver,Room 可以放进覆盖 Web 的 common 代码里。
Web 平台有一个明显限制:数据库操作天然是异步的。Room 里接收 SQLiteStatement 的 API 会变成 suspend 函数,例如 Migration.onMigrate()、RoomDatabase.Callback.onCreate()、PooledConnection.usePrepared()。如果 common 代码同时面向 Web 和非 Web,优先用 androidx.sqlite 提供的 suspend 扩展函数,例如 step()、executeSQL()。
bash
import androidx.sqlite.executeSQL
import androidx.sqlite.step
database.useWriterConnection { connection ->
val count = connection.usePrepared("SELECT count(*) FROM Song") { stmt ->
stmt.step()
stmt.getLong(0)
}
connection.executeSQL("DELETE FROM Song")
count
}
WebWorkerSQLiteDriver 还需要一个实现通信协议的 Web Worker。当前它没有自带默认 worker,AndroidX 仓库里有示例 worker,可以把 SQLite WASM 和 OPFS 存储组合起来用。也就是说,Web 方向已经能跑,但不是 Android 项目改个依赖就能顺手打开的功能。

表结构能力
Room 3.0 还补了一些 SQLite 层面的能力。@Fts5 可以声明 FTS5 表,主键增加了生成算法配置,也可以创建 WITHOUT ROWID 表。
bash
@Fts5
@Entity(tableName = "ArticleFts")
data class ArticleFts(
@PrimaryKey
val rowid: Long,
val title: String,
val body: String,
)
Kotlin 默认参数值也能被 Room 识别。以前实体字段有默认值时,很多场景仍然需要写完整构造参数,Room 3.0 可以更自然地对接 Kotlin data class。
bash
@Entity
data class DownloadTask(
@PrimaryKey val id: String,
val url: String,
val retryCount: Int = 0,
val state: String = "pending",
)
这些能力不一定要求立刻迁移,但它们说明 Room 3.0 的重心已经更偏 Kotlin 和多平台。Android-only 项目如果数据库封装很薄,升级成本主要在 import、KSP、协程和 SQLite API;如果数据库层历史包袱多,SupportSQLiteDatabase、Cursor、KAPT、Executor、Observer 这些点都要逐个处理。
最后
如果项目只把 Room 当 Android 本地数据库用,不需要急着改到 3.0;如果已经在做 KMP 数据层,Room 3.0 才是后面应该对齐的主线。