乐趣区

关于android:深入探讨-Room-240-的最新进展

在 Google I/O 2019,咱们分享了 Room 2.2 的最新进展。只管过后曾经反对了很多性能,如 反对 Flow API,反对预填充数据库,反对一对一及多对多数据库关系,然而开发者们对 Room 有着更高的冀望,咱们也致力于此,在 2.2.0 – 2.4.0 版本中公布了很多开发者们期待的新性能!包含自动化迁徙,关系查询方法以及反对 Kotlin Symbol Processing (KSP) 等等。上面咱们就来逐个介绍这些新性能!

如果您更喜爱通过视频理解此内容,请在此处查看:

https://www.bilibili.com/vide…

△ 深入探讨 Room 2.4.0 的最新进展

自动化迁徙

在谈自动化迁徙之前,先看看什么是数据库迁徙。如果您更改了数据库 schema,就须要依据数据库版本进行迁徙,以防用户设施内置数据库中现有数据失落。

如果您应用 Room,那么在 数据库迁徙 过程中会进行查看并验证更新后的 schema,另外您也能够在 @Database 中设置 exportSchema,来导出 schema 信息。

对于 Room 2.4.0 版本之前的数据库迁徙,您须要实现 Migration 类,并在其中编写大量简单简短的 SQL 语句,来解决不同版本之间的迁徙。这种手动迁徙的模式,非常容易引发各种谬误。

当初 Room 反对了主动迁徙,让咱们通过两个示例来比照手动迁徙和主动迁徙:

批改表名

假如有一个蕴含两个表的数据库,表名别离是 Artist 和 Track,当初想要将表名 Track 改为 Song。

如果应用手动迁徙,必须编写和执行 SQL 语句能力更改,须要如下操作:

val MIGRATION_1_2: Migration = Migration(1, 2) {fun migrate(database: SupportSQLiteDatabase) {database.execSQL("ALTER TABLE `Track` RENAME TO `Song`")
    }
}

如果应用主动迁徙,您只须要在定义数据库时增加 @AutoMigration 配置,同时提供两个版本数据库导出的 schema。Auto Migration API 将为您生成并实现 migrate 函数,编写并执行迁徙所需的 SQL 语句。代码如下:

@Database(
    version = MusicDatabase.LATEST_VERSION
    entities = {Song.class, Artist.class}
    autoMigrations = {@AutoMigration (from = 1,to = 2)
    }
    exprotSchema = true
)

批改字段名

当初,演示一个更简单的场景,假如咱们要将 Artist 表中的 singerName 字段批改为 artistName。

尽管这看起来很简略,然而因为 SQLite 并没有提供用于此操作的 API,因而咱们须要依据 ALERT TABLE 实现,有如下几步操作:

  1. 获取须要执行更改的表
  2. 创立一个新表,满足更改后的表构造
  3. 将旧表的数据插入到新表中
  4. 删除旧表
  5. 把新表重命名为原表名称
  6. 进行外键查看

迁徙代码如下:

val MIGRATION_1_2: Migration = Mirgation(1, 2) {fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("CREATE TABLE IF NOT EXISTS `_new_Artist`(`id` INTEGER NOT 
            NULL, artistName` TEXT, PRIMARY KEY(`id`)"
        )
        db.execSQL("INSERT INTO `_new_Artist` (id,artistName) 
            SELECT id, singerName FROM `Artist`"
        )
        db.execSQL("DROP TABLE `Artist`")
        db.execSQL("ALTER TABLE `_new_Artist` RENAME TO `Artist`")
        db.execSQL("PRAGMA foreign_key_check(`Artist`)")
    }
}

从下面的代码就能够看出,如果应用手动迁徙,即便两个版本之间仅有一处更改,也可能须要繁琐的操作,并且这些操作极易出错。

那咱们来看看主动迁徙该如何应用。在下面的示例中,主动迁徙无奈间接解决重命名表中的某一列,因为 Room 在进行主动迁徙时,会遍历两个版本的数据库 schema,通过比拟来检测两者之间的更改。在解决列或者表的重命名时,Room 无奈明确产生了什么更改,此时可能有两种状况,是删除后新增加的?还是进行了重命名?解决列或者表的删除操作时也会有同样问题。

所以咱们须要给 Room 增加一些配置来阐明这些不确定的场景——定义 AutoMigrationSpec。AutoMigrationSpec 是定义主动迁徙标准的接口,咱们须要实现该类,并在实现类上增加和批改绝对应的注解。本例中,咱们应用 @RenameColumn 注解,并在注解参数中,提供表名、列的原始名称以及更新后的名称。如果在迁徙实现之后,还须要执行其余工作,能够在 AutoMigrationSpec 的 onPostMigrate 函数中进行解决,相干代码如下:

@RenameColumn(
    tableName = "Artist",
    fromColumnName = "singerName",
    toColumnName = "artistName"
)
static class MySpec : AutoMigrationSpec {override fun onPostMigrate(db: SupportSQLiteDatabase) {// 迁徙工作实现后处理工作的回调}
}

实现 AutoMigrationSpec 的实现后,还须要将其增加到数据库定义时配置的 @AutoMigation 中,同时提供两个版本的数据库 schema,Auto Migration API 将生成和实现 migrate 函数,配置代码如下:

@Database(
    version = MusicDatabase.LATEST_VERSION
    entities = {Song.class, Artist.class}
    autoMigrations = {@AutoMigration (from = 1,to = 2,spec = MySpec.class)
    }
    exprotSchema = true
)

下面的案例提到了 @RenameColumn,相干的变更解决注解有如下几种:

  • @DeleteColumn
  • @DeleteTable
  • @RenameColumn
  • @RenameTable

假如在同一迁徙中有多个更改须要配置,咱们还能够通过这些可复用的注解简化解决。

测试主动迁徙

假如您在一开始就应用了主动迁徙,当初心愿测试其是否失常工作,能够应用现有的 MigrationTestHelper API 无需任何更改。如以下代码:

@Test
fun v1ToV2() {
    val helper = MigrationTestHelper(InstrumentationRegisty.getInstrumentation(),
            AutoMigrationDbKotlin::class.java
    )
    val db: SupportSQLiteDatabase = helper.runMigrationsAndValidate(
        name = TEST_DB,
        version = 2,
        validateDroppedTables = true
    )
}

在无需额定配置的状况下,MigrationTestHelper 将主动运行并验证所有主动迁徙。在 Room 外部,如果存在主动迁徙,它们将主动增加到须要运行和验证的迁徙列表中。

须要留神的是,开发者提供的迁徙具备更高的优先级,也就是说,如果您定义主动迁徙的两个版本之间,曾经定义了手动迁徙,那么手动迁徙将优先于主动迁徙。

关系查询方法

关系查问也是新增的一个重要性能,咱们还是用一个示例阐明。

假如咱们应用与之前雷同的数据库和表,当初表名别离为 Artist 和 Song。如果咱们心愿取得音乐人到歌曲的映射汇合,就要在 artistName 和 songName 之间建设关系。如下图中 Purple Lloyd 与其热门歌曲《Another Tile in the Ceiling》和《The Great Pig in the Sky》匹配,AB/CD 将与其热门歌曲《Back in White》和《Highway to Heaven》匹配。

应用 @Relation

如果应用 @Relation 和 @Embedded 反应该映射关系,则有如下代码:

data class ArtistAndSongs(
    @Embedded
    val artist: Artist,
    @Relation(...)
    val songs: List<Song>
)
 
@Query("SELECT * FROM Artist")
fun getArtistsAndSongs(): List<ArtistAndSongs>

在此计划中,咱们创立了全新的 数据类,将音乐人和歌曲列表相关系。然而这种额定创立 data 类的形式,容易造成代码繁冗的问题。而 @Relation 中并不反对过滤、排序、分组或组合键,其设计初衷也是用于数据库中只有一些简略的关系,尽管受限于关系后果,但这是一种疾速实现较简略工作的便捷办法。

所以为了反对简单关系的解决,咱们并没有扩大 @Relation,而是心愿您充分发挥 SQL 的潜能,因为它的性能十分弱小。

接下来让咱们来看看 Room 如何利用全新的性能来解决这一问题。

应用全新关系查问性能

为了示意后面所示的音乐人与其歌曲之间的关系,咱们当初能够编写一个简略的 DAO 办法,其返回类型为 Map,而咱们须要做的仅仅是提供 @Query 和返回标记,Room 将为您解决其余的所有!相干代码如下:

@Query("SELECT * FROM Artist JOIN Song ON Artist.artistName = Song.songArtistName")
fun getAllArtistAndTheirSongsList(): Map<Artist, List<Song>>

在 Room 外部,实际上要做的是找到音乐人、歌曲和 Cursor 并将它们放入 Map 中的 Key 和 Value 中。

在本例中,波及到一对多的映射关系,其中单个音乐人映射到一个歌曲汇合。当然咱们也能够应用一对一映射,如下文所示:

// 一对一映射关系
@Query("SELECT * FROM Song JOIN Artist ON Song.songArtistName = Artist.artistName")
fun getSongAndArtist(): Map<Song, Artist>

应用 @MapInfo

实际上,您能够通过 @MapInfo 在映射的应用中更加灵便。

MapInfo 是用于阐明开发者配置的辅助程序 API,相似于后面谈到的主动迁徙更改注解。您能够应用 MapInfo 明确阐明您心愿如何解决查问到的 Cursor 所蕴含的信息。应用 MapInfo 注解您能够指定输入的数据结构中用于查问的 Key 和 Value 所映射的列。须要留神,用于 Key 的类型必须实现 equals 和 hashCode 函数因为这对映射过程十分重要。

假如咱们心愿以 artistName 作为 Key,取得歌曲列表作为 Value,则代码实现如下:

@MapInfo(keyColumn = "artistName")
@Query("SELECT * FROM Artist JOIN Song ON Artist.artistName = Song.songArtistName")
fun getArtistNameToSongs(): Map<String, List<Song>>

在该示例中,artistName 用作 Key,音乐人被映射到其歌曲名称列表,最初 artistName 被映射到其歌曲名称列表。

MapInfo 注解使您能够灵便地应用特定列,而不是整个 data 类从而进行更加自定义的映射。

其余劣势

关系查询方法的另一个益处是反对更多的数据操作,能够通过这个新性能来反对分组、筛选等性能。示例代码如下:

@MapInfo(valueColumn = "songCount")
@Query("
    SELECT *, COUNT(songId) as songCount FROM Artist JOIN Song ON
    Artist.artistName = Song.songArtistName
    GROUP BY artistName WHERE songCount = 2
")
fun getArtistAndSongCountMap(): Map<Artist, Integer>

最初须要留神多重映射是一个外围返回类型,能够应用 Room 曾经反对的各种可察看类型封装 (包含 LiveData、Flowable、Flow)。因而,关系查询方法可让您轻松地在数据库中定义任意数量的关联关系。

更多新性能

内置 Enum 类型转换器

当初,如果零碎未提供任何类型转换器,Room 将默认应用 “ 枚举 – 字符串 ” 双向类型转换器。如果已存在实用于枚举的类型转换器,Room 将优先应用该转换器,而不应用默认转换器。

反对查问回调

当初,Room 提供了一个通用 callback API RoomDatabase.QueryCallback,此 API 会在执行查问时被调用,这将十分有助于咱们在 Debug 模式下记录日志。可通过 RoomDatabase.Builder#setQueryCallback() 设置此回调。

如果您心愿记录查问以理解数据库中产生了什么,该性能能够帮忙您进行记录,示例代码如下:

fun setUp() {
    database = databaseBuilder.setQueryCallback(
        RoomDatabase.QueryCallback{ sqlQuery, bindArgs ->
            // 记录所有触发的查问
            Log.d(TAG, "SQL Query $sqlQuery")
        },
        myBackgroundExecutor
    ).build()}

反对原生 Paging 3.0 API

Room 当初反对为返回值类型为 androidx.paging.PagingSource 且带 @Query 注解的办法生成实现。

反对 RxJava3

Room 当初反对 RxJava3 类型。通过依赖 androidx.room:room-rxjava3,您能够申明返回值类型为 Flowable、Single、Maybe 和 Completable 的 DAO 办法。

反对 Kotlin Symbol Processing (KSP)

KSP 用于代替 KAPT,它可能在 Kotlin 编译器上以原生形式运行注解处理器,从而显著缩短构建工夫。

对于 Room,应用 KSP 有如下益处:

  • 进步 2 倍的构建速度;
  • 间接解决 Kotlin 代码,更好的反对空平安。

随着 KSP 的稳固,Room 将应用其性能实现 value 类、生成 Kotlin 代码等。

从 KAPT 迁徙到 KSP 非常简单,只需应用 KSP 插件替换 KAPT 插件,并应用 KSP 配置 Room 注解处理器,示例代码如下:

plugins{
    // 应用 KSP 插件替换 KATP 插件
    // id("kotlin-kapt") 
    id("com.google.devtools.ksp")
}
 
dependencies{
    // 应用 KSP 配置代替 KAPT
    // kapt "androidx.room:room-compiler:$version"
    ksp "androidx.room:room-compiler:$version"
}

总结

自动化迁徙、关系查询方法、KSP——Room 带来了很多新性能,心愿大家和咱们一样对所有这些 Room 更新感到兴奋,记得查看并开始在您的利用中应用这些新性能!

欢迎您 点击这里 向咱们提交反馈,或分享您喜爱的内容、发现的问题。您的反馈对咱们十分重要,感谢您的反对!

退出移动版