1 前言
谷歌I/O 发布了一系列辅助android开发者的实用工具,合称Jetpack,其通过提供现代化应用架构以及提供强健的向后兼容能力等方式,让开发者能够快速、轻松地创造拥有卓越性能的高质量应用。本系列文章会介绍一下5个方面:
本文介绍Room框架,主要涉及以下三方引用:
-
sqlite2.0.1、sqlite-framework2.0.1
sqlite框架封装
-
room-common2.2.5
sql注解以及涉及语句
-
room-runtime2.2.5
room实现逻辑;以及跨进程监听数据库变化逻辑
sqlite为基础抽象封装;room库为代码提供基础以及生成代码,较少了重复代码编写;这里先简单介绍下使用,在分别介绍涉及引用库原理分析
2 使用
应用插件
apply plugin: 'kotlin-kapt'
复制代码
远程依赖
implementation "androidx.room:room-runtime:2.2.5"
implementation "androidx.room:room-ktx:2.2.5"
kapt("androidx.room:room-compiler:2.2.5")
复制代码
java编码部分,主要抽象了3个角色:
- Database注解:数据库管理者,包括了数据库增删改查操作,和数据库的获取等操作
- Entity注解:表示数据库中的表。
- Dao注解:数据库操作抽象,包含用于访问数据库的方法。
注解使用示例如下:
表注解
@Entity(
tableName = "people",
indices = [Index(value = ["id", "name"], unique = false)],
foreignKeys = [ForeignKey(entity = Test::class, parentColumns = ["id"], childColumns = ["id"])]
)
data class People(@PrimaryKey @ColumnInfo(name = "id") var mId : Int,
@ColumnInfo(name = "name", defaultValue = "3434") var mName : String,
@ColumnInfo(name = "age") var mAge : Int)
复制代码
视图注解
@DatabaseView(
value = "SELECT id from people",
viewName = "identify"
)
data class Man(@ColumnInfo(name = "id") var mId : Int)
复制代码
Dao注解
@Dao
interface PeopleRequest {
@Query("SELECT * from people where id = :id")
fun getPeoples(id : Int) : LiveData<List<People>>
@Insert(onConflict = OnConflictStrategy.ABORT)
fun insert(people: People)
@Delete(entity = Test::class)
fun delete(people: Man)
@Update(onConflict = OnConflictStrategy.ABORT)
fun update(people: Test)
@Query("SELECT * from identify")
fun getMan() : List<Man>
}
复制代码
数据库注解
@Database(
entities = [People::class, Test::class, Wan::class], version = 2, views = [Man::class]
)
@TypeConverters(value = [PeopleConvert::class])
abstract class PeopleDatabase : RoomDatabase() {
abstract fun getPeopleRequest() : PeopleRequest
}
复制代码
数据库使用
//获取数据库对象
val database : PeopleDatabase = Room.databaseBuilder(this, PeopleDatabase::class.java, "hello-cite")
.fallbackToDestructiveMigration()
.setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
.build()
//获取Dao对象
val request :PeopleRequest = database.getPeopleRequest()
//通过Dao方法获取
request.getMan()
复制代码
3 sqlite基础框架
sqlite基础框架并不复杂,只是进行了一些列封装、角色抽象;类图如下:
这里主要使用了代理模式,对android包中的sqlite内容进一步封装抽象;相对与android包增加了数据库语句的抽象,这里的数据库语句只是涉及查询、插入、删除、更新四个操作;SimpleSQLiteQuery是最简单基本的实现,在这四个操作中,查询操作语句是最复杂的,所以使用了SupportSQLiteQueryBuilder类来进行辅助构造;
其时,其对数据库语句的抽象并不完善:
1. 没有create、drop、alter等操作语句的抽象、辅助生成
2. 对语句抽象并没有达到只传参数,还需要写数据库语句
复制代码
不过其并没有完全摒弃android包中的使用方式,而抽象的也是最频繁的场景,也算是用心良苦吧;在room中,也把create、drop等操作也进行了相应封装,也算是弥补了抽象吧;
通过工厂类来创建数据库管理类,并通过SupportSQLiteOpenHelper.Configuration来对数据库创建过和使用过程中的一些回调
只是对android库中sqlite进行进一步的封装,其使用时并没有进行进一步的处理,这就导致了使用起来更费劲,而其中性能等方便并没有什么变化,所以,这个包很不建议直接用,要是直接用,用android包的就行
4 sql注解
注解的处理基本有两种方式,类编译时进行处理、动态反射处理;这里的注解就是在类编译时进行处理,并生成相应的实现类;
4.1 Entity注解类
是为了创建表而来的,作用与类、接口、enum;类中属性为public或者满足POJO;注解中方法含义
-
String tableName() default ""
表名
-
Index[] indices() default {}
索引列表,每个索引都是Index对象:sql语句 CREATE [UNIQUE] INDEX index_name
ON table_name (column_name)
-
boolean inheritSuperIndices() default false
表明索引内容是否从父类中继承
-
String[] primaryKeys() default {}
主键,并不会自动生成,如果需要则使用PrimaryKey注解
-
ForeignKey[] foreignKeys() default {}
外键列表,外键内容为ForeignKey对象;CREATE语句内容,相关部分格式如下:FOREIGN KEY (Id_P) REFERENCES Persons(Id_P)
-
String[] ignoredColumns() default {}
类中数据都属于表中列,此中名字的列不映射到表中列;也可以使用Ignore注解
4.2 Database注解类
数据库注解类,提供了数据库的一些信息,以及请求dao的对象
-
Class<?>[] entities()
数据库表列表,class为Entity注解的类,没个都是一张表
-
Class<?>[] views() default {}
数据库视图;class为DatabaseView注解的类;数据库视图创建sql语句:CREATE VIEW view_name AS SELECT column_name(s) FROM table_name WHERE condition;数据库视图在每次查询时,其本身内容都会重新创建,其视图数据不存在主键外键
-
int version()
数据库版本
-
boolean exportSchema() default true
一个数据库版本的纲要记录保存与否;不常用
4.3 ColumnInfo注解类
数据表列信息注解类;也包含了collate值常量、affinity值常量
-
String name() default INHERIT_FIELD_NAME
列名字,默认是属性名字
-
@SQLiteTypeAffinity int typeAffinity() default UNDEFINED
数据亲和类型;sql数据存储时类型是动态的,这里指明其存的类型;很少用
-
boolean index() default false
是不是索引
-
@Collate int collate() default UNSPECIFIED
应用于表达式、列定义或数据库定义的排序规则的名称;很少用
-
String defaultValue() default VALUE_UNSPECIFIED 列默认值,create语句中,格式:DEFAULT 默认值
4.4 ForeignKey注解类
外键约束注解类
-
Class<?> entity()
对应主键表注解类,Entity注解的类
-
String[] parentColumns()
主键列表名称
-
String[] childColumns()
当前表外键名称,外键名与主键名一一对应
-
@Action int onDelete() default NO_ACTION
相关外键删除时,对应主键表的执行动作
-
@Action int onUpdate() default NO_ACTION
相关外键更新时,对应主键表的执行动作
-
boolean deferred() default false
是否延迟执行约束
4.5 Index注解类
索引具体信息注解,一个类对应一个索引;sql语句:CREATE [UNIQUE] INDEX index_name ON table_name (column_name)
-
String[] value()
索引中锁包含的所有列
-
String name() default ""
索引的名称
-
boolean unique() default false
是否唯一索引
4.6 增删改查注解
增删改查注解相对比较简单,但也有自己的规则
-
插入操作注解Insert
其有两个方法,冲突解决方法int onConflict() default OnConflictStrategy.ABORT;以及插入类Class<?> entity() default Object.class,插入类为Entity注解的类、这个类的数组或者集合,或者POJO类且存在列表的部分
-
更新操作注解Update
其方法同Insert
-
删除操作注解Delete
仅有此方法Class<?> entity() default Object.class,解释同插入;以主键进行删除
-
查询操作
存在两种方式:Query、RawQuery,不同点在于,Query依赖与语句查询,参数使用 ‘:参数名’注入,而RawQuery以SupportSQLiteQuery为方法请求参数;至于RawQuery的可观察的说法,用LiveData不是更爽
4.7 其余注解
这些注解,并不是说其不重要,只是说明其简单
-
Dao 注解:作用与类接口,无方法
-
Embedded注解;作用与参数方法;对于非基础数据类型处理,增加列表的列内容,并使用String prefix() default ""为其属性增加前缀
-
Fts3,Fts4注解,为了大量数据快速查询增加的一种机制
-
Ignore注解,作用与方法参数构造器,数据库注解处理时不进行处理
-
SkipQueryVerification注解:作用域方法类,忽略sql语句的校验
-
Transaction注解:作用与方法,在方法操作中,其整体作为一个事务进行操作
-
TypeConverter注解:作用域方法,转换方法
-
TypeConverters注解:TypeConverter相关的类;其处在不同地方有不同作用域
- Database注解的类,则其作用与此数据库相关的表和请求方法
- Dao类,则此类所有方法都可以使用
- Entity类,则所有属性都可以使用
- Entity属性,则仅仅此属性
- Dao类方法,则此方法所有参数
- Dao类参数,仅仅此参数
5 room库框架
从功能角度来看,分为2大块
- 数据库操作封装
- 数据库监听
但从使用角度来说,涉及
- 数据库创建、销毁
- 数据库的增删改查,也有可能涉及查询时动态更新
- 数据库的升级
框架类图如下:
下面介绍以下主要类内容代码
5.1 DatabaseConfiguration类
数据库的基础配置类,其中有一些配置需要注意;
- SupportSQLiteOpenHelper.Factory,可以进行自我实现,也可以不做处理,这个根据是否有默认数据文件(变量copyFromAssetPath,copyFromFile),而进行选择FrameworkSQLiteOpenHelperFactory或者SQLiteCopyOpenHelperFactory
- 迁移状态:由三个变量决定,这个迁移状态不影响按照Migration来进行升降级,但在Migration失败后,来判断是否需要销毁重构;这个状态在获取读写数据库时,会在数据库版本冲突、版本号不一致且不进行迁移时,进行预置文件替换当前数据库操作;状态决定优先级:allowDestructiveMigrationOnDowngrade > requireMigration 或者 mMigrationNotRequiredFrom
- mMigrationNotRequiredFrom:Set,数据库迁移不包括的版本,默认为null
- allowDestructiveMigrationOnDowngrade:Boolean,是否允许在数据库降级时迁移状态,默认false
- requireMigration:boolean值,是否允许迁移的状态,默认true
- multiInstanceInvalidation:boolean值,默认文件处理时,是否对数据库文件'进程锁'来同步处理,是否开启进程间同步策略,默认false
- RoomDatabase.JournalMode:WAL模式是否开启,默认是让数据库自己决定,如果我们设置不是AUTOMATIC,则系统版本大于16且不是低RAM设备,则使用WAL模式
- allowMainThreadQueries:boolean值,是否允许非主线程数据库处理;默认false不允许,在非主线程时处理抛出异常
5.2 RoomDatabase类
RoomDatabase:抽象类,数据库封装抽象主题类,也是整个功能的入口;其实现方法规定用户的主要操作,而且用户可以增加对Dao注解对象的获取抽象方法,这样,注解处理就会生成RoomDatabase相应实现子类以及Dao注解的类的实现;
- 封装了数据库查询、执行、语句编译,事务编译
- 规定了InvalidationTracker的开启关闭以及数据变化刷新时机
存在下面三个抽象方法
- abstract void clearAllTables():清空数据库,需要考虑使用WAL模式的数据处理
- abstract InvalidationTracker createInvalidationTracker():数据变化监听者
- abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config):数据库表建立销毁等操作
createOpenHelper抽象方法
通过抽象方法createOpenHelper(DatabaseConfiguration config)来获取SupportSQLiteOpenHelper对象,来实现数据库创建、清除以及更新等操作;已经提供的实现类有两个
- FrameworkSQLiteOpenHelper:代理了SQLiteOpenHelper的功能,并增加了主要生命周期回调
- SQLiteCopyOpenHelper:装饰了SupportSQLiteOpenHelper内容,实现了本地文件初始化功能
而生命周期回调,Room库中RoomOpenHelper类进行实现,通过抽象类Delegate预留处理,这些处理即为对数据库需要执行的操作
也即是,这个方法的实现是这样的
Callback openCallback = new RoomOpenHelper(configuration, new Delegate(6) {
public void createAllTables(SupportSQLiteDatabase db) {
....
}
public void dropAllTables(SupportSQLiteDatabase db) {
....
}
protected void onCreate(SupportSQLiteDatabase db) {
...
}
public void onOpen(SupportSQLiteDatabase db) {
...
}
public void onPreMigrate(SupportSQLiteDatabase db) {
...
}
public void onPostMigrate(SupportSQLiteDatabase db) {
...
}
protected void validateMigration(SupportSQLiteDatabase db) {
...
}
}, "c84d23ade98552f1cec71088c1f0794c", "1db8206f0da6aa81bbdd2d99a82d9e14");
Configuration sqliteConfig = Configuration.builder(configuration.context).name(configuration.name).callback(openCallback).build();
SupportSQLiteOpenHelper helper = configuration.sqliteOpenHelperFactory.create(sqliteConfig);
return helper;
}
复制代码
RoomOpenHelper对回调进行了初步实现,其实现了下列功能
- 通过RoomDatabase.MigrationContainer管理数据库升降级问题
- 首次通过onValidateSchema抽象方法验证创建合理性,非首次通过room_master_table库中hash值得存检验数据库得合法性
createInvalidationTracker抽象方法
这个返回的是InvalidationTracker对象,InvalidationTracker可以直接生成实例,按照构造器生成即可
clearAllTables抽象方法
这个方法就需要考虑WAL模式,方法实现应该这样写
@Override
public void clearAllTables() {
super.assertNotMainThread();
final SupportSQLiteDatabase db = super.getOpenHelper().getWritableDatabase();
...
db.query("PRAGMA wal_checkpoint(FULL)").close();
if (!db.inTransaction()) {
db.execSQL("VACUUM");
}
}
复制代码
Dao注解类实现
room注解处理时,需要开发者最好在RoomDatabase中声明获取Dao注解类的抽象方法,以方便自动实现增删改查功能,否则需要用户了解自动实现代码,并进行关联(现有的实现,主要是内部需要持有RoomDatabase的实例)
- 查询操作,使用RoomDatabese中Cursor query(@NonNull SupportSQLiteQuery query, @Nullable CancellationSignal signal)进行执行;也就是需要生成SupportSQLiteQuery对象;如果是LiveData结果,则需要生成RoomTrackingLiveData对象,其内部通过Callable执行获取结果,也即是Callable执行过程即为查询过程
- 删除更新操作,借助EntityDeletionOrUpdateAdapter的实现类
- 插入操作,借助EntityInsertionAdapter的实现类
5.3 RoomTrackingLiveData类
其内部实现原理和ComputableLiveData一致,都是一种计算型可观察对象;不过当前类是一种被动型计算刷新
5.4 InvalidationTracker类
这个类是监听的整体把控着,掌管了以下功能
- 本地事务的变化管理
- 本地监听者管理
- 跨进程监听以及发布消息
本地事务
主要通过syncTriggers方法来触发,这个调用场景:关联RoomDatabase对象时,增加/移除监听,开启事务以及数据库分页处理
主要是通过数据表中对应监听者的变化来处理:从0到有,则启动本地表变化监听,从1到0则移除本地表变化监听;监听通过临时触发器来执行数据库room_table_modification_log的数据改动,进而在结束事务时,查询room_table_modification_log表中数据进而得知是否变动
跨进程处理
这里采用AIDL的方式,这是android提供给开发者最基础且效率最高的通信方式; IMultiInstanceInvalidationCallback规定跨进程回调实例,IMultiInstanceInvalidationServiceu规定了跨进程回到管理与通知 MultiInstanceInvalidationService中实现了跨进程的回调管理以及通知,并通过service进行绑定连接;并实现了跨进程回调,这里是通知本进程监听者进行更新;而且增加了数据库变化监听,这里变化了通过跨进程方式发布通知消息;
那这里就有一个问题,这个消息如何避免循环?通过注册跨进程回调时,返回一个int值,来标志进程,这样,就可以在发布消息时,不进行发布消息的回调处理
6 结语
在本篇文章中,没有进行具体的代码分析,也没有对一些基础内容,涉及技术进行归纳;不过这些都需要自我体悟,并从性能等方面进行理解探究,其中还是有许多的想法和一些技术点的,比如
- 数据库语法
- 触发器
- android数据库创建时,会自动创建哪些表,结构是什么
- AIDL使用以及实现原理
- WAL、事务、执行语句编译、索引等数据库优化手段,在这里是如何运用的
技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!