Room是一个数据库,基于SQLite的抽象层,也可以直接使用SQLite,但强烈建议使用Room。
官方文档:Android Room
导入依赖
1 | dependencies { |
创建表。
Room里的表,需要用@Entry注解标注,这里用个简单的学生表
1 | // table_name就是表的名字,indecies是可选项,用来生成表的索引 |
创建数据表访问类。
这里我们只需要声明接口,并加以@Dao注解标注,具体实现交给Room来做,
1 | // 这里只定义CRUD,增删改查,也可根据需要定义其他接口,如查询分数大于某个数值的所有学生 |
表有了,访问方式也有了,下面创建数据库。
1 | // 继承自RoomDatabase,其中已经实现了绝大数功能,我们只需要额外声明几个接口,用来提供Dao数据类 |
万事俱备,现在可以用了。在Activity里调用下试试看
1 | override fun onCreate(savedInstanceState: Bundle?) { |
添加完成,运行一下。
然后你会发现,崩溃了。。。日志如下:
1 | java.lang.RuntimeException: Unable to start activity ComponentInfo{com.oynix.room.sample/com.oynix.room.sample.MainActivity}: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time. |
这句话的意思是说,不能在main thread,也就是主线程访问数据库,因为这样有可能会把页面卡住一段很长的时间。这样的检测机制算是合理的,毕竟这属于IO操作,而IO操作都应该放到单独的线程去跑。
但是也可以去掉这种检测,在创建数据库的时候额外传入个配置:
1 | override fun onCreate(savedInstanceState: Bundle?) { |
这个时候再运行一下,就会发现顺利执行
数据库文件
这个时候,在Android Studio的Device File Explorer中,可以在data/data/{包名}/database目录下发现3个文件,app.db、app.db-shm和app.db-wal,其中.db是数据库文件,另外两个是临时文件。
把这三个文件导出到电脑桌面,然后用能打开db文件的软件将app.db打开,如SQLite Professional、SQLite Studio等,会看到我们创建的student表,以及表中刚刚插入的那条数据。
增加一张表
这个时候,业务发生调整,我们需要增加一张教师表,如下
1 | ]) |
同时,也要修改数据库类,增加新的教师表实体
1 |
|
增加一条教师的记录。张老师,他很厉害,会教数学
1 | override fun onCreate(savedInstanceState: Bundle?) { |
再一运行,发现又报错了。。。
1 | java.lang.RuntimeException: Unable to start activity ComponentInfo{com.oynix.room.sample/com.oynix.room.sample.MainActivity}: java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number. |
这句话的意思是说,数据库的schema,也就是结构发生了改变,但是版本号没有更新,Room不知道怎么处理这些schema的修改,然后就报了个错。
数据库升级
就像刚刚那样,在版本的迭代更新中,常常会有修改数据库结构的情况。在Room中,数据库是通过version号,来管理数据库版本的,每做一次修改,version都要增加,一般增加1,你每次加2也没人能拿你怎么样。我们在创建数据库的同时,还要告诉Room版本更新做了哪些操作,这些需要通过addMigrations来添加。
只修改schema,而不增加version,程序会崩溃。
只增加version,而不提供Migration,程序会删除并重建数据库
1 | override fun onCreate(savedInstanceState: Bundle?) { |
如上,从1到2后,我们新加了一个teacher表,并在表上加了一个索引,那么这么写就可以了。
如果说,不想写这么长的SQL语句怎么办?也好办,全局搜AppDatabase_Impl.java文件,这里面就是Room已经写好的。
再次运行,发现没有再崩溃,打开app.db,里面多了一张表,表里有一条刚加的张老师的记录。
addMigrations
这里要单独说一说这个方法,程序运行后,如果本地数据库版本和代码里的版本不一致,这个时候这个方法才会派上用场,如果一致则无用。
先帖下文档里的说明:
1 | /** |
它接收可变参数,即可同时接收多个Migration。每个Migration都有个startVersion,以及一个endVersion,Room将会运行这些Migration,将本地数据库的版本一步步升级到代码里的最新版本。如果缺失当前版本到最新版本的Migration,Room将会清空数据库并重建。所以,即便在两个版本之间没有变化,仍然需要提供一个Migration给builder。
一个Migration可以处理多个版本,例如,当前是版本3,最新是版本5,你提供了3到4的Migration、4到5的Migration以及3到5的Migration,那么Room就会选择更快的Migration,即3到5,而不是由3到4再到5。
数据加密
虽然Android高版本在数据安全这一块已经提升了很多,未root的手机基本看不到其他应用独立存储空间里的内容,但为了以防万一,可以进一步将数据库文件,也就是app.db,进行加密。
当前有很多种实现思路,如每次写之前,将数据加密,读之后,将数据解密,等。
这里介绍个第三方加密库,使用很方便,当然也有缺点,就是包体会变大,因为用到了native库,增大6M左右
- 引入依赖
1
2
3
4
5// room cypher maven
maven { url "https://s3.amazonaws.com/repo.commonsware.com" }
// room cypher
implementation "com.commonsware.cwac:saferoom.x:1.2.1" - 增加Factory,openHeloperFactory,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val db = Room.databaseBuilder(this, AppDatabase::class.java, "app.db")
.allowMainThreadQueries()
.openHelperFactory(SafeHelperFactory("your_database_password".toCharArray()))
.addMigrations(object : Migration(1, 2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `teacher` (`id` INTEGER NOT NULL, `num` INTEGER NOT NULL, `name` TEXT NOT NULL, `course` TEXT NOT NULL, PRIMARY KEY(`id`))")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_teacher_num` ON `teacher` (`num`)")
}
})
.build()
// val dao = db.studentDao()
// dao.insert(Student(1, 1, "John", 14))
// val teacherDao = db.teacherDao()
// teacherDao.insert(Teacher(1, 1001, "Miss Zhang", "Math"))
} - 加密之后,再导出来的app.db就无法打开了
总结
以上,Room的简单使用就这些了,相比于SQLite,简单、方便了不少,本地化数据可以多考虑使用。
另外还有些更高级的用法,比如和ViewModel的结合、和Hilt的结合、异步操作数据流,等等,这里就就不做说明了。
附上Github地址:https://github.com/oynix/RoomSample