jetpack源码-room篇

·  阅读 709

1 前言

谷歌I/O 发布了一系列辅助android开发者的实用工具,合称Jetpack,其通过提供现代化应用架构以及提供强健的向后兼容能力等方式,让开发者能够快速、轻松地创造拥有卓越性能的高质量应用。本系列文章会介绍一下5个方面:

  • arch:线程管理、灵活增加删除的map 链接
  • 生命周期lifecycle 链接
  • 数据驱动:livedata、viewmodel 链接
  • 数据库Room 链接
  • 后台作业调度

本文介绍Room框架,主要涉及以下三方引用:

  1. sqlite2.0.1、sqlite-framework2.0.1

    sqlite框架封装

  2. room-common2.2.5

    sql注解以及涉及语句

  3. 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个角色:

  1. Database注解:数据库管理者,包括了数据库增删改查操作,和数据库的获取等操作
  2. Entity注解:表示数据库中的表。
  3. 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基础框架并不复杂,只是进行了一些列封装、角色抽象;类图如下:

sqlite.png

这里主要使用了代理模式,对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;注解中方法含义

  1. String tableName() default ""

    表名

  2. Index[] indices() default {}

    索引列表,每个索引都是Index对象:sql语句 CREATE [UNIQUE] INDEX index_name

ON table_name (column_name)

  1. boolean inheritSuperIndices() default false

    表明索引内容是否从父类中继承

  2. String[] primaryKeys() default {}

    主键,并不会自动生成,如果需要则使用PrimaryKey注解

  3. ForeignKey[] foreignKeys() default {}

    外键列表,外键内容为ForeignKey对象;CREATE语句内容,相关部分格式如下:FOREIGN KEY (Id_P) REFERENCES Persons(Id_P)

  4. String[] ignoredColumns() default {}

    类中数据都属于表中列,此中名字的列不映射到表中列;也可以使用Ignore注解

4.2 Database注解类

数据库注解类,提供了数据库的一些信息,以及请求dao的对象

  1. Class<?>[] entities()

    数据库表列表,class为Entity注解的类,没个都是一张表

  2. Class<?>[] views() default {}

    数据库视图;class为DatabaseView注解的类;数据库视图创建sql语句:CREATE VIEW view_name AS SELECT column_name(s) FROM table_name WHERE condition;数据库视图在每次查询时,其本身内容都会重新创建,其视图数据不存在主键外键

  3. int version()

    数据库版本

  4. boolean exportSchema() default true

    一个数据库版本的纲要记录保存与否;不常用

4.3 ColumnInfo注解类

数据表列信息注解类;也包含了collate值常量、affinity值常量

  1. String name() default INHERIT_FIELD_NAME

    列名字,默认是属性名字

  2. @SQLiteTypeAffinity int typeAffinity() default UNDEFINED

    数据亲和类型;sql数据存储时类型是动态的,这里指明其存的类型;很少用

  3. boolean index() default false

    是不是索引

  4. @Collate int collate() default UNSPECIFIED

    应用于表达式、列定义或数据库定义的排序规则的名称;很少用

  5. String defaultValue() default VALUE_UNSPECIFIED 列默认值,create语句中,格式:DEFAULT 默认值

4.4 ForeignKey注解类

外键约束注解类

  1. Class<?> entity()

    对应主键表注解类,Entity注解的类

  2. String[] parentColumns()

    主键列表名称

  3. String[] childColumns()

    当前表外键名称,外键名与主键名一一对应

  4. @Action int onDelete() default NO_ACTION

    相关外键删除时,对应主键表的执行动作

  5. @Action int onUpdate() default NO_ACTION

    相关外键更新时,对应主键表的执行动作

  6. boolean deferred() default false

    是否延迟执行约束

4.5 Index注解类

索引具体信息注解,一个类对应一个索引;sql语句:CREATE [UNIQUE] INDEX index_name ON table_name (column_name)

  1. String[] value()

    索引中锁包含的所有列

  2. String name() default ""

    索引的名称

  3. boolean unique() default false

    是否唯一索引

4.6 增删改查注解

增删改查注解相对比较简单,但也有自己的规则

  1. 插入操作注解Insert

    其有两个方法,冲突解决方法int onConflict() default OnConflictStrategy.ABORT;以及插入类Class<?> entity() default Object.class,插入类为Entity注解的类、这个类的数组或者集合,或者POJO类且存在列表的部分

  2. 更新操作注解Update

    其方法同Insert

  3. 删除操作注解Delete

    仅有此方法Class<?> entity() default Object.class,解释同插入;以主键进行删除

  4. 查询操作

    存在两种方式:Query、RawQuery,不同点在于,Query依赖与语句查询,参数使用 ‘:参数名’注入,而RawQuery以SupportSQLiteQuery为方法请求参数;至于RawQuery的可观察的说法,用LiveData不是更爽

4.7 其余注解

这些注解,并不是说其不重要,只是说明其简单

  1. Dao 注解:作用与类接口,无方法

  2. Embedded注解;作用与参数方法;对于非基础数据类型处理,增加列表的列内容,并使用String prefix() default ""为其属性增加前缀

  3. Fts3,Fts4注解,为了大量数据快速查询增加的一种机制

  4. Ignore注解,作用与方法参数构造器,数据库注解处理时不进行处理

  5. SkipQueryVerification注解:作用域方法类,忽略sql语句的校验

  6. Transaction注解:作用与方法,在方法操作中,其整体作为一个事务进行操作

  7. TypeConverter注解:作用域方法,转换方法

  8. TypeConverters注解:TypeConverter相关的类;其处在不同地方有不同作用域

    • Database注解的类,则其作用与此数据库相关的表和请求方法
    • Dao类,则此类所有方法都可以使用
    • Entity类,则所有属性都可以使用
    • Entity属性,则仅仅此属性
    • Dao类方法,则此方法所有参数
    • Dao类参数,仅仅此参数

5 room库框架

从功能角度来看,分为2大块

  1. 数据库操作封装
  2. 数据库监听

但从使用角度来说,涉及

  1. 数据库创建、销毁
  2. 数据库的增删改查,也有可能涉及查询时动态更新
  3. 数据库的升级

框架类图如下:

room.png

下面介绍以下主要类内容代码

5.1 DatabaseConfiguration类

数据库的基础配置类,其中有一些配置需要注意;

  • SupportSQLiteOpenHelper.Factory,可以进行自我实现,也可以不做处理,这个根据是否有默认数据文件(变量copyFromAssetPath,copyFromFile),而进行选择FrameworkSQLiteOpenHelperFactory或者SQLiteCopyOpenHelperFactory
  • 迁移状态:由三个变量决定,这个迁移状态不影响按照Migration来进行升降级,但在Migration失败后,来判断是否需要销毁重构;这个状态在获取读写数据库时,会在数据库版本冲突、版本号不一致且不进行迁移时,进行预置文件替换当前数据库操作;状态决定优先级:allowDestructiveMigrationOnDowngrade > requireMigration 或者 mMigrationNotRequiredFrom
    1. mMigrationNotRequiredFrom:Set,数据库迁移不包括的版本,默认为null
    2. allowDestructiveMigrationOnDowngrade:Boolean,是否允许在数据库降级时迁移状态,默认false
    3. requireMigration:boolean值,是否允许迁移的状态,默认true
  • multiInstanceInvalidation:boolean值,默认文件处理时,是否对数据库文件'进程锁'来同步处理,是否开启进程间同步策略,默认false
  • RoomDatabase.JournalMode:WAL模式是否开启,默认是让数据库自己决定,如果我们设置不是AUTOMATIC,则系统版本大于16且不是低RAM设备,则使用WAL模式
  • allowMainThreadQueries:boolean值,是否允许非主线程数据库处理;默认false不允许,在非主线程时处理抛出异常

5.2 RoomDatabase类

RoomDatabase:抽象类,数据库封装抽象主题类,也是整个功能的入口;其实现方法规定用户的主要操作,而且用户可以增加对Dao注解对象的获取抽象方法,这样,注解处理就会生成RoomDatabase相应实现子类以及Dao注解的类的实现;

  1. 封装了数据库查询、执行、语句编译,事务编译
  2. 规定了InvalidationTracker的开启关闭以及数据变化刷新时机

存在下面三个抽象方法

  • abstract void clearAllTables():清空数据库,需要考虑使用WAL模式的数据处理
  • abstract InvalidationTracker createInvalidationTracker():数据变化监听者
  • abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config):数据库表建立销毁等操作

createOpenHelper抽象方法

通过抽象方法createOpenHelper(DatabaseConfiguration config)来获取SupportSQLiteOpenHelper对象,来实现数据库创建、清除以及更新等操作;已经提供的实现类有两个

  1. FrameworkSQLiteOpenHelper:代理了SQLiteOpenHelper的功能,并增加了主要生命周期回调
  2. 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对回调进行了初步实现,其实现了下列功能

  1. 通过RoomDatabase.MigrationContainer管理数据库升降级问题
  2. 首次通过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类

这个类是监听的整体把控着,掌管了以下功能

  1. 本地事务的变化管理
  2. 本地监听者管理
  3. 跨进程监听以及发布消息

本地事务

主要通过syncTriggers方法来触发,这个调用场景:关联RoomDatabase对象时,增加/移除监听,开启事务以及数据库分页处理

主要是通过数据表中对应监听者的变化来处理:从0到有,则启动本地表变化监听,从1到0则移除本地表变化监听;监听通过临时触发器来执行数据库room_table_modification_log的数据改动,进而在结束事务时,查询room_table_modification_log表中数据进而得知是否变动

跨进程处理

这里采用AIDL的方式,这是android提供给开发者最基础且效率最高的通信方式; IMultiInstanceInvalidationCallback规定跨进程回调实例,IMultiInstanceInvalidationServiceu规定了跨进程回到管理与通知 MultiInstanceInvalidationService中实现了跨进程的回调管理以及通知,并通过service进行绑定连接;并实现了跨进程回调,这里是通知本进程监听者进行更新;而且增加了数据库变化监听,这里变化了通过跨进程方式发布通知消息;

那这里就有一个问题,这个消息如何避免循环?通过注册跨进程回调时,返回一个int值,来标志进程,这样,就可以在发布消息时,不进行发布消息的回调处理

6 结语

在本篇文章中,没有进行具体的代码分析,也没有对一些基础内容,涉及技术进行归纳;不过这些都需要自我体悟,并从性能等方面进行理解探究,其中还是有许多的想法和一些技术点的,比如

  1. 数据库语法
  2. 触发器
  3. android数据库创建时,会自动创建哪些表,结构是什么
  4. AIDL使用以及实现原理
  5. WAL、事务、执行语句编译、索引等数据库优化手段,在这里是如何运用的

技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!

分类:
Android
标签:
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改