Android—Jetpack教程(六)

前言

在上一篇中,对Room进行了ViewModel+LiveData封装。在本篇中,将会讲解Room对应的升级与预填充。直接开始吧!

1、预填充数据库

Android—Jetpack教程(六)
如图所示

有时候我们希望应用自带一些数据供我们使用,我们可以讲数据库文件放入assets目录一起打包发布,在用户首次打开App时,使用createFromAsset()和createFrimFile()创建Room数据库。

Android—Jetpack教程(六)
如图所示

既然要自带一些数据,那么我们预先准备一些数据存入自带数据库里面。

那看看如何使用?

@Database(entities = [Student::class], version =1, exportSchema = false)
abstract class MyDatabase : RoomDatabase() {
    companion object {
        private const val DATABASE_NAME = "my_db.db"
        private var mInstance: MyDatabase? = null

        @Synchronized
        @JvmStatic
        open fun getInstance(context: Context): MyDatabase? {
            if (mInstance == null) {
                mInstance = Room.databaseBuilder(
                    context.applicationContext,
                    MyDatabase::class.java,
                    DATABASE_NAME
                ) //.allowMainThreadQueries()
                    .createFromAsset("prestudent.db")
                    .build()
            }
            return mInstance
        }


    abstract fun getStudentDao(): StudentDao?
}

我们可以看到,额外加了一句.createFromAsset("prestudent.db")注意这里FromAsset,也就是说,对应的预存储数据库是存放在Asset目录下的,这里就不贴图了。

来看看运行效果(卸载原有的):

Android—Jetpack教程(六)

可以看出,在软件安装成功时,就预先保留了对应数据!

好了接着下一个!

2、使用Migration升级数据库

2.1 只增列名情况

这里都已经提到了使用Migration,那么试试看?

@Database(entities = [Student::class], version =1, exportSchema = false)
abstract class MyDatabase : RoomDatabase() {

    companion object {
        private const val DATABASE_NAME = "my_db.db"
        private var mInstance: MyDatabase? = null

        @Synchronized
        @JvmStatic
        open fun getInstance(context: Context): MyDatabase? {
            if (mInstance == null) {
                mInstance = Room.databaseBuilder(
                    context.applicationContext,
                    MyDatabase::class.java,
                    DATABASE_NAME
                ) //.allowMainThreadQueries()
                    .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                    .createFromAsset("prestudent.db")
                    .build()
            }
            return mInstance
        }

        @JvmStatic
        val MIGRATION_1_2: Migration = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE student ADD COLUMN sex INTEGER NOT NULL DEFAULT 1")
            }
        }

        @JvmStatic
        val MIGRATION_2_3: Migration = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE student ADD COLUMN bar_data INTEGER NOT NULL DEFAULT 1")
            }
        }

    }
    abstract fun getStudentDao(): StudentDao?
}

代码解析

  • MIGRATION_1_2MIGRATION_2_3分别代表对应数据库的升级策略
  • 1-2版本添加了COLUMN sex INTEGER列名
  • 2-3版本添加了COLUMN bar_data INTEGER列名
  • @Database(xxx, version =1, xxxz)这里面的version表示当前版本,如果要升级2,那么就将最新App对应属性改为2
  • 注意,对应Student实体类也需要根据对应SQL做对应的修改!比如1-2版本添加了sex,那么实体类也需要哟

实体类就不贴了哈,要根据SQL做对应的调整!

来看看1-2升级的运行效果

升级前对应版本1的数据库信息:
Android—Jetpack教程(六)

升级后对应版本2的数据库信息:(version=2 ,对应数据表实体类调整)

Android—Jetpack教程(六)

升级后对应版本3的数据库信息:(version=3 ,对应数据表实体类调整)

Android—Jetpack教程(六)
OK,测试都没问题!我们现在都是1升2,2升3。那么如果说保留1升2,2升3,没有直接1升3,用户版本就为1,而我们数据库已经升级到3了呢?

我们将版本改为1,对应数据表实体类也回退1状态,卸载原App重新试试!

重新运行后,当前App数据库版本为1,这时,

:(直接将版本改为3,使用版本3的升级策略,version=3 ,对应数据表实体类调整)

Android—Jetpack教程(六)
我们发现,当Room遇到跨版本升级时:

  • Room会先判断有没有从1到3的升级方案,
  • 如果有,就直接执行从1到3的升级方案;
  • 如果没有,那么Room会按照顺序先后执行Migration(1, 2)Migration(2, 3)以完成升级!

这时我们看到,升级数据库只看到过添加列名情况!那万一说,对应列名需要该变量类型该怎么做呢?

比如说原有的sex为INTEGER,后面突然发病要将sex改为TEXT呢?

2.2 销毁和重建策略

大致分为以下步骤:

  • 创建一张符合表结构要求的临时表temp_student
  • 将数据从旧表student复制到临时表temp_student
  • 删除旧表student
  • 将临时表temp_student重命名为student

看完步骤,思路清晰了,来实现看看!

@Database(entities = [Student::class], version =3, exportSchema = false)
abstract class MyDatabase : RoomDatabase() {

    companion object {
        private const val DATABASE_NAME = "my_db.db"
        private var mInstance: MyDatabase? = null

        @Synchronized
        @JvmStatic
        open fun getInstance(context: Context): MyDatabase? {
            if (mInstance == null) {
                mInstance = Room.databaseBuilder(
                    context.applicationContext,
                    MyDatabase::class.java,
                    DATABASE_NAME
                ) //.allowMainThreadQueries()
//                    .fallbackToDestructiveMigration()
                    .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
                    .createFromAsset("prestudent.db")
                    .build()
            }
            return mInstance
        }

        @JvmStatic
        val MIGRATION_1_2: Migration = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE student ADD COLUMN sex INTEGER NOT NULL DEFAULT 1")
            }
        }

        @JvmStatic
        val MIGRATION_2_3: Migration = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE student ADD COLUMN bar_data INTEGER NOT NULL DEFAULT 1")
            }
        }

        @JvmStatic
        val MIGRATION_3_4: Migration = object : Migration(3, 4) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL(
                    "CREATE TABLE temp_student (" +
                            "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                            "name TEXT," +
                            "age INTEGER NOT NULL," +
                            "sex TEXT DEFAULT 'M'," +
                            "bar_data INTEGER NOT NULL DEFAULT 1)"
                )
                database.execSQL(
                    "INSERT INTO temp_student (name,age,sex,bar_data)" +
                            "SELECT name,age,sex,bar_data FROM student"
                )
                database.execSQL("DROP TABLE student")
                database.execSQL("ALTER TABLE temp_student RENAME TO student")
            }
        }
    }


    abstract fun getStudentDao(): StudentDao?
}

这时,我们看到,创建了MIGRATION_3_4,里面的逻辑就是上面我们理清的步骤,那么?

将版本改为4(version=4,对应实体类需要对应调整)运行效果
Android—Jetpack教程(六)
我们看到对应列名类型已经修改成功!

那么在升级时,我们要不要将升级的过程保留下呢?如果能保留,以后排查问题的时候也会方便一点!

3、Schema文件

Room在每次数据库升级过程中,都会导出一个Schema文件,这是一个json格式的文件,其中包含了数据库的基本信息,有了该文件,开发者能清楚的知道数据可的厉次变更过程,极大方便了开发者排查问题!

那么该怎么使用呢?

android {
    compileSdkVersion 30

    defaultConfig {
		...略

	//        javaCompileOptions {
//            annotationProcessorOptions {
//                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]//指定数据库schema导出的位置
//            }
//        }

        kapt {
            arguments {
                arg("room.schemaLocation", "$projectDir/schemas".toString())
            }
        }
    }
}

注释部分是业务逻辑使用Java时,对应配置导出的位置!下者为Kotlin对应配置导出位置!

此外还需要将

@Database(entities = [Student::class], version =1, exportSchema = false){

}

对应的 exportSchema 改为true(默认为true),卸载重新从版本1开始运行!

运行成功后,修改版本为2(version=2,对应实体类调整)重新运行后:

Android—Jetpack教程(六)
OK,我们看到左边已经有对应的新文件!打开看,就是对应的升级策略!

结束语

好了,本篇到这里就结束了,相信你对Room有了全面的理解!在下一篇中,将会讲解Jetpack对应的Navigation组件!

上一篇:Oracle各版本驱动下载


下一篇:小知识:RMAN基于某个具体时间点的恢复示例