1、前言
最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。
Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:
- Android Jetpack组件总览
- Android Jetpack 组件之 Lifecycle使用
- Android Jetpack 组件之 Lifecycle源码
- Android Jetpack组件之ViewModel使用
- Android Jetpack组件之 LiveData使用-源码
- Android Jetpack组件之 Paging使用-源码
- Android Jetpack组件之 Room使用-源码
- Android Jetpack组件之Navigation使用-源码
- Android Jetpack组件之WorkManger使用介绍
- Android Jetpack组件App Startup简析
- Android Jetpack组件之Hilt使用
本系列文章是各处copy过来的,个人感觉所有的开发者都应该尽早的熟悉Jetpack组件,相信一定会被它的魅力所吸引,最近也在完成一个使用以上所有组件实现的项目,作为对Jetpack组件的项目实践,下面来分析一下每个组件对项目开发的帮助。
2、Room 简介
Room是Google提供的一个ORM库。Room提供了三个主要的组件:
- @Database:@Database用来注解类,并且注解的类必须是继承自RoomDatabase的抽象类。该类主要作用是创建数据库和创建Daos(data access objects,数据访问对象)。
- @Entity:@Entity用来注解实体类,@Database通过entities属性引用被@Entity注解的类,并利用该类的所有字段作为表的列名来创建表。
- @Dao:@Dao用来注解一个接口或者抽象方法,该类的作用是提供访问数据库的方法。在使用@Database注解的类中必须定一个不带参数的方法,这个方法返回使用@Dao注解的类
3、Room数据库使用
数据库的创建
- 包含数据库持有者,并作为应用程序持久关系数据的基础连接的主要访问点,使用@Database注解,注解类应满足以下条件:
- 数据库必须是一个抽象类 RoomDatabase的扩展类
- 在注释中包括与数据库关联的实体列表
- 必须包含一个具有0个参数且返回带@Dao注释的类的抽象方法
- 通过调用 Room.databaseBuilder()或 获取实例Room.inMemoryDatabaseBuilder()创建数据库实例
- 使用单例实例化数据库对象
@Database(entities = {User.class}, version = 1) // 注释
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao(); // 抽象方法
}
- 以单例形式对外提供RoomDataBase实例
public static UserDataBase getInstance(Context context) {
if (userDataBase == null) {
synchronized (UserDataBase.class) {
if (userDataBase == null) {
userDataBase = Room.databaseBuilder(context.getApplicationContext()
, UserDataBase.class, "user_data").build();
}
}
}
return userDataBase;
}
定义实体数据:表示数据库中的表
- @Entity
- 使用@Entity注解实体类,Room会为实体中定义的每个字段创建一列,如果想避免使用@Ignore注解
- Room默认使用类名作为数据库表名,要修改表名使用 @Entity 的 tableName属性
- 主键
- @PrimaryKey :至少定义一个字段作为主键
- 如果自增长ID 使用设置@PrimaryKey的 autoGenerate 属性
- 使用组合主键 使用@Entity 的@primaryKeys属性
- Room 默认使用字段名成作为列名,要修改使用 @ColumnInfo(name = "***")
@Entity(tableName = "userDataBase")
class User {
@PrimaryKey(autoGenerate = true) // 单个主键设置为自增长
public var id = 0
@ColumnInfo(name = "nameUser") // 定义列名
public var name: String? = null
}
@Entity(primaryKeys = ["id", "name"]) // 组合组件
添加索引@Entity
- 使用 @Entity 的indices 属性,列出要包含在索引或复合索引中的列的名称
@Entity(indices = [Index("nameUser"), Index(value = ["name"])]) // 创建索引
@Entity(indices = [Index("nameUser"), Index(value = ["name"] ,unique = true)]) //唯一索引
外键约束@ForeignKey
- 使用@ForeignKey 注释定义其与实体的关系;ForeignKey中 entity 为要关联的父实体类;parentColumns 为关联父实体类的列名;childColumns此实体类中的列名
@Entity(foreignKeys = [ForeignKey(entity = User::class,
parentColumns = ["id"],
childColumns = ["user_id"])])
class Book {
@PrimaryKey
var bookId: Int = 0
var title: String? = null
@ColumnInfo(name = "user_id")
var userId: Int = 0
}
嵌套对象@Embedded
- 使用 @Embedded 注释来表示要分解到表中子字段的对象(此时数据库的列为两个类中所有的字段)
class Address {
public var street: String? = null
public var state: String? = null
public var city: String? = null
@ColumnInfo(name = "post_code")
public var postCode = 0
}
// 在User实体中引入Address
@Embedded
public var address: Address? = null
访问数据库
- 使用@DAO注解:包含用于访问数据库的方法
@Dao
public interface UserDao {
@Insert // 添加数据注解
void insertAll(User... users);
@Delete // 删除数据注解
void delete(User user);
}
4、实例实战
- insert:使用注解@Insert,Room会自动将所有参数在单个事物中插入数据库
@Insert
public fun inertUser(user: User) // 单个参数可以返回 long
@Insert
public fun insertUserList(array: Array<User>) // 参数为集合可以返回long[]
- 数据库添加User
val user = User()
user.name = "赵云 编号 = $number"
val address = Address()
address.street = "成都接头"
address.state = "蜀汉"
address.city = "常山"
address.postCode = 10010
user.address = address
userDao.inertUser(user) // 添加User
添加数据结果:
- upadte:使用 @Update注解
@Update
public fun update(user: User) // 可以让此方法返回一个int值,表示数据库中更新的行数
val user = User()
user.id = 1
user.name = "张翼德"
address.city = "涿郡"
.....
userDao.update(user)
点击 Update 后再查询结果:此时的赵云已经改为张翼徳了:
- delete:使用@Delete注解
@Delete
public fun delete(user: User) //可以返回一个int值,表示从数据库中删除的行数
val user = User()
user.id = 1 // 要删除的主键 id
userDao.delete(user)
点击delete后再次查询数据:编号为1的数据已被删除。
- 查询信息 :@Query注解对数据库执行读/写操作
@Query("SELECT * FROM user")
public fun selectAll(): Array<User> // 查询所有数据
@Query("SELECT * FROM user WHERE name = :name")
public fun selectUser(name:String): Array<User> // 条件查询
- 返回列的子集:创建子类在每个属性中使用@ColumnInfo(name = "name")标记对应数据库中的列名
public class UserTuple{ // 1、根据要查询的字段创建POJO对象
@ColumnInfo(name = "name")
public var name: String? = null
@ColumnInfo(name = "city")
public var city: String? = null
}
@Query("SELECT name ,city FROM user") // 2、查询的结果会映射到创建的对象中
public List<UserTuple> loadFullName();
val userList = userDao.loadFullName()
for (userTuple in userList) {
stringBuilder.append(userTuple.name)
.append(" ")
.append(userTuple.city)
.append("\n")
}
输出的结果:只有name和city两列
- 范围条件查询 :查询城市中所有用户
@Query("SELECT name ,street FROM user WHERE city IN (:cityArray)")
fun loadUserInCity(cityArray: Array<String>): List<UserTuple>
val userList = userDao.loadUserInCity(arrayOf("常山")) // 查询常山,只会出现赵云不会出现张翼德
- Observable查询:使用LiveData作为查询方法的返回值,注册观察者后,数据表更改时自动更新UI
@Query("SELECT name ,street FROM user WHERE city IN (:cityArray"))
fun loadUserInCityLive(cityArray: Array<String>): LiveData<List<UserTuple>>
private lateinit var liveData: LiveData<Array<UserTuple>> // 定义一个LiveData
get() {
return userDao.loadUserInCityLive(arrayOf("常山"))
}
val observer = Observer<Array<UserTuple>> { // 定义一个观察者
val stringBuilder = StringBuilder()
for (index in it!!.indices) {
val userTuple = it[index]
stringBuilder.append(userTuple.name)
.append(" ")
.append(userTuple.name)
.append(" \n")
}
tv_main_show.text = stringBuilder.toString()
}
liveData.observe(this, observer) // 注册观察者
运行结果:此时当添加数据时,UI会自动更新:
- RxJava 查询 :返回Observable实例可以使用RxJava订阅观察者
@Query("SELECT * FROM user WHERE id = :id LIMIT 1")
fun loadUserRxJava(id:Int) : Flowable<User>
userDao.loadUserRxJava(4)
.subscribe(Consumer {
val stringBuilder = StringBuilder()
stringBuilder.append(it.id)
.append(" ")
.append(it.name)
.append(" \n")
tv_main_show.text = stringBuilder.toString()
})
- Cursor查询:返回Cursor对象
fun loadUserCursor(id:Int) : Cursor
- 多表查询:根据表的外键多表查询
@Query("SELECT user.name AS userName, pet.name AS petName "
+ "FROM user, pet "
+ "WHERE user.id = pet.user_id")
5、更新数据库
- 编写 Migration 的实例。每个 Migration 类指定一个startVersion和endVersion
- Room运行每个 Migration 类的 migrate() 方法,使用正确的顺序将数据库迁移到更高版本
static final Migration MIGRATION_1_2 = new Migration(1, 2) { //由1升级到版本2
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE book (id INTEGER , name TEXT )")
}
};
static final Migration MIGRATION_2_3 = new Migration(2, 3) { //由2升级到版本3
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE user ADD COLUMN strength INTEGER NOT NUll DEFAULT 0") //添加strength列
}
};
Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();
升级完数据库后再次查询,结果显示数据库增加了strength列名:
6、引用复杂数据
Room提供了在原始类型和目标类型之间进行转换的功能,但不允许实体之间的对象引用,对于其他类型之间的使用需要自定义转换器
使用类型转换器
使用TypeConverter,它将自定义类转换为Room可以保留的已知类型,如:想保存Date类型,而Room无法持久化实例Date却可以实例long,因此提供和long的相互转换
public class Converters {
@TypeConverter
public static Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}
@TypeConverter
public static Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
- 在抽象数据库类中添加转换注解
@TypeConverters({Converters.class})
- 使用 类型转换器
@Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
List findUsersBornBetweenDates(Date from, Date to);
以上就是数据库Room的使用简介了,基本数据库的增删改查以及常见的设置都在其中了,下面我们来看看Room是如何实现这些过程的,从源码角度分析数据库。
7、源码分析
数据库的创建和升级
Room数据库实例的创建由Room.databaseBuilder(context.applicationContext,RoomTestData::class.java, "Sample.db").build()开始的,从代码中看出时使用Builder模式创建DataBase,所以我们先看看RoomDatabase.Builde类
- RoomDatabase.Builder:除了包含Room的实现类、数据库名称的常规设置外,也包含了数据库的升级信息
@NonNull
public Builder<T> addMigrations(@NonNull Migration... migrations) { // 添加数据库版本升级信息
if (mMigrationStartAndEndVersions == null) {
mMigrationStartAndEndVersions = new HashSet<>();
}
for (Migration migration: migrations) {
mMigrationStartAndEndVersions.add(migration.startVersion);
mMigrationStartAndEndVersions.add(migration.endVersion);
}
mMigrationContainer.addMigrations(migrations);
return this;
}
- build():创建并初始化数据库
private static final String DB_IMPL_SUFFIX = "_Impl"
。。。。。。
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX); // 创建DataBase实现类的实例
db.init(configuration); // 初始化数据库
- getGeneratedImplementation():反射创建DataBase的实现类
static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
final String fullPackage = klass.getPackage().getName();
String name = klass.getCanonicalName();
final String postPackageName = fullPackage.isEmpty()
? name
: (name.substring(fullPackage.length() + 1)); // 获取类名
final String implName = postPackageName.replace('.', '_') + suffix; // 拼接类名
//noinspection TryWithIdenticalCatches
try {
@SuppressWarnings("unchecked")
final Class<T> aClass = (Class<T>) Class.forName(
fullPackage.isEmpty() ? implName : fullPackage + "." + implName); // 获取自动生成的类文件
return aClass.newInstance(); // 创建并返回实例
} catch (ClassNotFoundException e) {
。。。。。。
}
}
此处获取到的是系统根据注解自动创建的是实现类RoomDataBase_Impl,Room采用的是注解自动生成代码方式,根据@DataBase和@Dao的注解,自动生成这两个注解标记的实现类,系统创建类如下图:
- RoomTestData_Impl:系统自动生成的实现类
public class RoomTestData_Impl extends RoomTestData {
private volatile UserDao _userDao;
......
@Override
public UserDao userDao() {
if (_userDao != null) {
return _userDao;
} else {
synchronized(this) {
if(_userDao == null) {
_userDao = new UserDao_Impl(this); // 创建并返回UserDao的实例
}
return _userDao;
}
}
}
}
从上面的代码中看出,系统自动创建了RoomTestData的实现类,并重写了抽象方法userDao(),在userDao()中使用单例的方式提供UserDao的实现类UserDao_Impl,UserDao_Impl的形成和RoomTestData_Impl的生成一样,在代码中从DataBase中调用userDao返回的就是UserDao_Impl的实例;
接着分析数据库的创建,在上面的代码中有一句数据库的初始化代码db.init(),在db.init()的方法中会调用RoomDataBase中的抽象方法createOpenHelper(),这里调用的是createOpenHelper()就是RoomTestData_Impl自动实现的方法:
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(3) {
@Override
public void createAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `strength` INTEGER NOT NULL, `name` TEXT, `street` TEXT, `state` TEXT, `city` TEXT, `post_code` INTEGER)"); // 创建数据库
_db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
_db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"8ece9a1581b767a0f460940849e9b463\")");
}
@Override
public void dropAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("DROP TABLE IF EXISTS `user`"); // 删除数据库
}
@Override
protected void validateMigration(SupportSQLiteDatabase _db) { // 处理数据库的版本升级
。。。。。。
}
}, "8ece9a1581b767a0f460940849e9b463", "061261cef54147a569851cbbb906c3be");
}
。。。。。。
return _helper;
}
上面的代码中执行一下操作:
- 创建SupportSQLiteOpenHelper.Callback 的实例并重写方法
- 在onCreate()中Sql语句创建user表和room_master_table表
- 在dropAllTables()中创建删除数据库的SQL语句
- 在validateMigration()中完成数据库的升级
上面SupportSQLiteOpenHelper.Callback 的实现类为RoomOpenHelper,下面一起看看RoomOpenHelper源码:
@Override
public void onCreate(SupportSQLiteDatabase db) {
updateIdentity(db);
mDelegate.createAllTables(db); // mDelegate为上面创建的RoomOpenHelper.Delegate实例
mDelegate.onCreate(db);
}
@Override
public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
if (mConfiguration != null) {
List<Migration> migrations = mConfiguration.migrationContainer.findMigrationPath(
oldVersion, newVersion);
if (migrations != null) {
for (Migration migration : migrations) {
migration.migrate(db);
}
mDelegate.validateMigration(db); // 调用validateMigration方法处理数据库的更新
updateIdentity(db);
migrated = true;
}
}
}
@Override
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, oldVersion, newVersion);
}
从上面代码中可以看出,在onCreate()方法中调用了mDelegate.createAllTables(db),这里的mDelegate就是上面创建RoomOpenHelper方法中第二个参数RoomOpenHelper.Delegate,所以这里就是在onCreate()中创建了数据库,在onUPgrade()中调用 mDelegate.validateMigration(db)完成数据库的升级,到这里数据库的创建和升级已经介绍完毕了,下面就一起看看Room是如何访问数据库的。
数据库的访问
- @Dao数据库的实现类: UserDao_Impl
private final RoomDatabase __db; // 传入的数据库
private final EntityInsertionAdapter __insertionAdapterOfUser; // 处理insert方法
private final EntityDeletionOrUpdateAdapter __deletionAdapterOfUser; // 处理delete方法
private final EntityDeletionOrUpdateAdapter __updateAdapterOfUser; // 处理update方法
在UserDao_Impl的类中除了数据库RoomDataBase实例外,还有三个成员变量分别为:__insertionAdapterOfUser、__deletionAdapterOfUser、__updateAdapterOfUser,从名字上可以看出来他们三个分别对应数据库增、删、改的三个操作,我们以insert操作为例,查看insert方法:
@Override
public void inertUser(User user) {
__db.beginTransaction();
try {
__insertionAdapterOfUser.insert(user);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
insert()方法的实现是在__insertionAdapterOfUser中执行的,查看__insertionAdapterOfUser的实现:
this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
@Override
public String createQuery() { // 创建SupportSQLiteStatement时传入的Sql语句
return "INSERT OR ABORT INTO `user`(`id`,`strength`,`name`,`street`,`state`,`city`,`post_code`) VALUES (nullif(?, 0),?,?,?,?,?,?)";
}
@Override
public void bind(SupportSQLiteStatement stmt, User value) {
stmt.bindLong(1, value.getId());
stmt.bindLong(2, value.getStrength());
if (value.getName() == null) { // 判断此列是否为null,部位Null则设置数据
stmt.bindNull(3);
} else {
stmt.bindString(3, value.getName());
}
final Address _tmpAddress = value.getAddress();
if(_tmpAddress != null) {
if (_tmpAddress.getStreet() == null) {
stmt.bindNull(4);
} else {
stmt.bindString(4, _tmpAddress.getStreet());
}
if (_tmpAddress.getState() == null) {
stmt.bindNull(5);
} else {
stmt.bindString(5, _tmpAddress.getState());
}
if (_tmpAddress.getCity() == null) {
stmt.bindNull(6);
} else {
stmt.bindString(6, _tmpAddress.getCity());
}
stmt.bindLong(7, _tmpAddress.getPostCode());
} else {
stmt.bindNull(4);
stmt.bindNull(5);
stmt.bindNull(6);
stmt.bindNull(7);
}
}
};
__insertionAdapterOfUser的实例重写了两个方法:
- createQuery():创建数据库插入数据的sql语句
- bind():绑定数据库中每个列对应的值
__insertionAdapterOfUser.insert()
insert()方法中创建SupportSQLiteStatement的实例,并调用bind()完成数据的绑定,然后执行stmt.executeInsert()插入数据
public final void insert(T entity) {
final SupportSQLiteStatement stmt = acquire(); // 最终创建的是FrameworkSQLiteStatement的包装的SQLiteStatement实例
try {
bind(stmt, entity); // 绑定要插入的数据
stmt.executeInsert(); // 提交保存数据,执行
} finally {
release(stmt);
}
}
@Override
public long executeInsert() { // 最终执行数据库的插入操作
return mDelegate.executeInsert();
}
- 查寻数据库
在UserDao_Impl中自动实现了查询的方法selectUser:
@Override
public User[] selectUser(String name) {
final String _sql = "SELECT * FROM user WHERE name = ?";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1); // 创建RoomSQLiteQuery
int _argIndex = 1;
if (name == null) {
_statement.bindNull(_argIndex);
} else {
_statement.bindString(_argIndex, name);
}
final Cursor _cursor = __db.query(_statement); // 执行查询反会Cursor
try {
final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
final int _cursorIndexOfStrength = _cursor.getColumnIndexOrThrow("strength");
final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
final int _cursorIndexOfStreet = _cursor.getColumnIndexOrThrow("street");
final int _cursorIndexOfState = _cursor.getColumnIndexOrThrow("state");
final int _cursorIndexOfCity = _cursor.getColumnIndexOrThrow("city");
final int _cursorIndexOfPostCode = _cursor.getColumnIndexOrThrow("post_code");
final User[] _result = new User[_cursor.getCount()];
int _index = 0;
while(_cursor.moveToNext()) {
final User _item;
final Address _tmpAddress;
if (! (_cursor.isNull(_cursorIndexOfStreet) && _cursor.isNull(_cursorIndexOfState) && _cursor.isNull(_cursorIndexOfCity) && _cursor.isNull(_cursorIndexOfPostCode))) {
_tmpAddress = new Address();
final String _tmpStreet;
_tmpStreet = _cursor.getString(_cursorIndexOfStreet);
_tmpAddress.setStreet(_tmpStreet);
final String _tmpState;
_tmpState = _cursor.getString(_cursorIndexOfState);
_tmpAddress.setState(_tmpState);
final String _tmpCity;
_tmpCity = _cursor.getString(_cursorIndexOfCity);
_tmpAddress.setCity(_tmpCity);
final int _tmpPostCode;
_tmpPostCode = _cursor.getInt(_cursorIndexOfPostCode);
_tmpAddress.setPostCode(_tmpPostCode);
} else {
_tmpAddress = null;
}
_item = new User();
final int _tmpId;
_tmpId = _cursor.getInt(_cursorIndexOfId);
_item.setId(_tmpId);
final int _tmpStrength;
_tmpStrength = _cursor.getInt(_cursorIndexOfStrength);
_item.setStrength(_tmpStrength);
final String _tmpName;
_tmpName = _cursor.getString(_cursorIndexOfName);
_item.setName(_tmpName);
_item.setAddress(_tmpAddress);
_result[_index] = _item;
_index ++;
}
return _result;
} finally {
_cursor.close();
_statement.release();
}
}
上面执行的也是数据库的正常操作,先创建了RoomSQLiteQuery的实例,在调用db。query()执行查询,查询返回Cursor实例,最终从Cursor中获取信息转换为对象并返回数据。
到此Room的使用和源码执行流程就到此结束了,本文旨在执行的流程分析,具体的如何使用SQLite数据库操作的读者可以自己点击源码查看,不过使用的SQLite的查询和添加方法和平时使用的不同,读者想分析的话就会找到了,好了,希望本篇文章对想了解和使用Room组件的同学有所帮助!