Room 3.0 正式发布!包名彻底重构,KMP 成为核心主线

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

如果项目里有公共封装,例如 AppDatabaseProviderRoomModuleDatabaseFactory,先从这些位置改 import 和 builder。业务层 DAO 调用如果本来只暴露 suspend / Flow,改动会少很多;如果业务层拿到了 SupportSQLiteDatabaseCursor,后面就要拆得更细。

SQLiteDriver

Room 3.0 的数据库底层改成 SQLiteDriver。主线 API 不再引用 SupportSQLiteDatabaseSupportSQLiteOpenHelper,也不再把 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 函数如果不是返回 FlowPagingSource 或自定义 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-rxjava3room3-guavaroom3-livedata

DAO return type converter 也可以自定义。Room 生成代码负责执行 SQL 和转换实体,converter 决定最终包成什么返回类型。Web 侧如果要把 DAO 返回值包装成 Promise,就属于这类扩展。

KMP 和 Web

Room 3.0 面向 KMP 的变化不只停在 Android 和 iOS。它新增了 JavaScript 和 WasmJS 目标,配合 androidx.sqlite:sqliteSQLiteDriver 接口,以及 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;如果数据库层历史包袱多,SupportSQLiteDatabaseCursor、KAPT、Executor、Observer 这些点都要逐个处理。

最后

如果项目只把 Room 当 Android 本地数据库用,不需要急着改到 3.0;如果已经在做 KMP 数据层,Room 3.0 才是后面应该对齐的主线。

#Android #Jetpack #Room #KotlinMultiplatform

相关推荐
三少爷的鞋2 小时前
Kotlin 协程环境下的 DCL 懒加载:别把线程时代的经验直接搬过来
android
plainGeekDev2 小时前
Gson → kotlinx.serialization
android·java·kotlin
CYY9516 小时前
Compose 入门篇
android·kotlin
杉氧19 小时前
Compose 时代的 MVI 架构:如何用单向数据流驱动复杂 UI?
android·架构·android jetpack
杉氧19 小时前
Modifier 的艺术:为什么链式调用的顺序决定了UI 的生命周期?
android·架构·android jetpack
李斯维20 小时前
腾讯 XLog 日志框架 Android 端接入
android·android studio·android jetpack
黄林晴20 小时前
Kotlin Toolchain 0.11 发布:Amper 正式更名,统一 kotlin 命令
android·kotlin
雨白1 天前
C语言基础快速入门与指针初探
android