【安卓开发系列 -- APP】JetPack -- Room
【1】Room 概念
Room 是一个轻量级 orm 数据库(对象关系映射,Object Relational Mapping, ORM),本质上是一个 SQLite 抽象层,但是使用起来会更加简单,类似于 Retrofit 库,Room 在开发阶段通过注解的方式标记相关功能,编译时自动生成相应的 impl 实现类,同时编译阶段会有丰富的语法校验,错误提示;由于 Room 是对原生 SQLite 的封装,所以其性能几乎和 SQLite 相当;
【2】Room 的使用示例
ROOM 三大注解
ROOM 三大注解
@Entity : 表示数据库中的表;
@DAO : 数据操作对象;
@Database : 必须是扩展 RoomDatabase 的抽象类,在注解中添加与数据库关联的数据表,
包含使用 @Dao 注解标记的的类的抽象方法;
定义数据库的表
// 定义表非常简单,只需要创建一个 class 并标记上 Entity 注解,
// 可以使用它的 `tableName` 属性声明该表的名称
@Entity(tableName = "table_cache")
class Cache {
// 1. 对于一个表必须存在一个不为空的主键,即必须要标记 PrimaryKey 和 NonNull 两个注解
// PrimaryKey 注解的 `autoGenerate` 属性意味该主键的值,是否由数据库自动生成
@PrimaryKey(autoGenerate = false)
@NonNull
var key: String = ""
// 2. 该字段在数据库表中的列名称,不指定的默认就等于该字段的名字
@ColumnInfo(name="cache_data", defaultValue = "default value")
var data: String? = null
//3. 如果不想让该字段映射成表的列,可以使用该注解标记
@Ignore
var timeStamp:Long?=null
// 4. 如果想让内嵌对象中的字段也一同映射成数据库表的字段,可以使用 Embedded 注解;
// 此时 User 对象中所有字段也会一同出现在 cache 表中
// 注意 User 对象必须也使用Entity注解标记,并且拥有一个不为空的主键
@Embedded
var user: User? = null
//5. 对于一个 Room 数据库的表而言,还有很多其他注解和属性可以使用,诸如索引,外键,关系数据支持的特性 room 都支持;
}
@Entity(tableName = "table_user")
class User {
@PrimaryKey
@NonNull
var name: String = ""
var age = 10
}
定义数据库数据操作对象
// 全称(data access object)
@Dao
interface CacheDao {
// 1. 如果是插入数据,需要标记上 Insert 注解,并指明插入数据时如果已存在一条主键一样的数据,执行的策略
// REPLACE : 直接替换老数据
// ABORT : 终止操作,并回滚事务,即老数据不影响
// IGNORE : 忽略冲突,但是会插入失败
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveCache(cache: Cache): Long
// 2. 常规查询操作,需要写 sql 语句
@Query("select * from table_cache where `key`=:primaryKey")
fun getCache(primaryKey: String): Cache?
// 3. 高级查询操作,可以通过 livedata 以观察者的形式获取数据库数据,可以避免不必要的 npe
// 更重要的是其可以监听数据库表中的数据的变化,一旦发生了 insert update delete
// room 会自动读取表中最新的数据,发送给 UI 层刷新页面
@Query("select * from table_cache")
fun query2(): LiveData<List<Cache>> // 同样支持 rxjava observer
//4. 删除操作非常简单,也可以执行 sql 语句删除数据
@Delete(entity = Cache::class)
fun deleteCache(key: String)
//5. 更新操作,表中对应的这一行所有数据会被替换成 Cache 对象的字段值
@Update()
fun update(cache: Cache)
}
定义数据库并关联表和数据操作实体
// TypeConverters 用以声明该数据库支持的类型转换,比如下面定义的 DateConvert 里面就定义 Date 类型的字段
// 存储数据库的时候会被转换成 Long, 而该字段被读取的时候,会被转换成Date类型
@TypeConverters(DateConvert::class)
@Database(entities = [Cache::class], version = 1)
abstract class CacheDatabase : RoomDatabase() {
//1. 创建内存数据库,即这种数据库中存储的数据,只会存留在内存中,进程被杀死之后,数据随之丢失
val database = Room.inMemoryDatabaseBuilder(context, CacheDatabase::class.java).build()
//2. 创建本地持久化的数据库
val database = Room.databaseBuilder(context, CacheDatabase::class.java, "howow_cache").
// 是否允许在主线程上操作数据库,默认false。
// 相比 sqlite 无法明文禁止即可为来说,Room 给出了规范
.allowMainThreadQueries()
// 数据库创建和打开的事件会回调到这里,可以再次操作数据库
.addCallback(callback)
// 指定数据查询数据时候的线程池
.setQueryExecutor(cacheThreadPool)
// 它是用来创建 supportsqliteopenhelper
// 可以利用它实现自定义的 sqliteOpenHelper,来实现数据库的加密存储,默认是不加密的
.openHelperFactory()
// 数据库升级 1---2
.addMigrations(migration1_2)
//3. 以抽象方法的形式声明数据操作对象 Dao
abstract val cacheDao: CacheDao
// 数据库从 version1->version2 的升级过程
// 注意,一旦数据库被创建,只要任意对象的任意字段有改动
// Database 注解的 version 字段都需要升级,同时需要指定升级的行为 migration
val migration1_2 = object :Migration(1,2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table table_cache add column cache_time LONG")
}
}
}
class DateConvert {
// 每个类可以拥有多个 TypeConverter 方法,但都必须要有返回值,可空
@TypeConverter
fun date2Long(date: Date): Long {
return date.time
}
@TypeConverter
fun long2Date(timestamp: Long): Date {
return Date(timestamp)
}
}
fun queryLiveData(owner: LifecycleOwner) {
CacheDatabase.get().cacheDao.query2().observe(owner, Observer {
})
}
【3】Room + LiveData 相关源码分析
【3.1】Room 整体设计
Room 实际上是通过抽象接口的形式,对数据库常用的操作做了适配,Room 实现层就是通过注解 + 编译时处理器的形式生成相应的实现类完成相应的功能;
【3.2】Room 数据库创建流程
public class Room {
@NonNull
public static <T extends RoomDatabase> RoomDatabase.Builder<T> databaseBuilder(
@NonNull Context context, @NonNull Class<T> klass, @NonNull String name) {
//noinspection ConstantConditions
if (name == null || name.trim().length() == 0) {
throw new IllegalArgumentException("Cannot build a database with null or empty name."
+ " If you are trying to create an in memory database, use Room"
+ ".inMemoryDatabaseBuilder");
}
// 返回 RoomDatabase 的构造器实例
return new RoomDatabase.Builder<>(context, klass, name);
}
}
public abstract class RoomDatabase {
public static class Builder<T extends RoomDatabase> {
private final Class<T> mDatabaseClass;
// inMemoryDatabaseBuilder 方法不需要指定数据库名称
private final String mName;
private final Context mContext;
private JournalMode mJournalMode;
private boolean mRequireMigration;
private final MigrationContainer mMigrationContainer;
private SupportSQLiteOpenHelper.Factory mFactory;
/** The Executor used to run database queries. This should be background-threaded. */
// 用于运行数据库请求操作
private Executor mQueryExecutor;
/** The Executor used to run database transactions. This should be background-threaded. */
// 用于运行数据库事务操作
private Executor mTransactionExecutor;
// Configures Room to create and open the database using a pre-packaged database
// located in the application 'assets/' folder
private String mCopyFromAssetPath;
// Configures Room to create and open the database using a pre-packaged database file
private File mCopyFromFile;
Builder(@NonNull Context context, @NonNull Class<T> klass, @Nullable String name) {
mContext = context;
mDatabaseClass = klass;
mName = name;
mJournalMode = JournalMode.AUTOMATIC;
mRequireMigration = true;
mMigrationContainer = new MigrationContainer();
}
@SuppressLint("RestrictedApi")
@NonNull
public T build() {
//noinspection ConstantConditions
// mContext 不能为 null
if (mContext == null) {
throw new IllegalArgumentException("Cannot provide null context for the database.");
}
//noinspection ConstantConditions
// mDatabaseClass(数据库的类类型) 不能为 null
if (mDatabaseClass == null) {
throw new IllegalArgumentException("Must provide an abstract class that"
+ " extends RoomDatabase");
}
// 新建执行器,用于数据库请求与事务处理
if (mQueryExecutor == null && mTransactionExecutor == null) {
mQueryExecutor = mTransactionExecutor = ArchTaskExecutor.getIOThreadExecutor();
} else if (mQueryExecutor != null && mTransactionExecutor == null) {
mTransactionExecutor = mQueryExecutor;
} else if (mQueryExecutor == null && mTransactionExecutor != null) {
mQueryExecutor = mTransactionExecutor;
}
// 数据库版本升级与降级的相关控制判断
if (mMigrationStartAndEndVersions != null && mMigrationsNotRequiredFrom != null) {
for (Integer version : mMigrationStartAndEndVersions) {
if (mMigrationsNotRequiredFrom.contains(version)) {
throw new IllegalArgumentException(
"Inconsistency detected. A Migration was supplied to "
+ "addMigration(Migration... migrations) that has a start "
+ "or end version equal to a start version supplied to "
+ "fallbackToDestructiveMigrationFrom(int... "
+ "startVersions). Start version: "
+ version);
}
}
}
// 新建 FrameworkSQLiteOpenHelperFactory 工厂实例,用于创建 FrameworkSQLiteOpenHelper 实例
if (mFactory == null) {
mFactory = new FrameworkSQLiteOpenHelperFactory();
}
if (mCopyFromAssetPath != null || mCopyFromFile != null) {
// inMemoryDatabaseBuilder 方法不需要指定数据库名称
// 此处使用的数据库时持久化的数据库
if (mName == null) {
throw new IllegalArgumentException("Cannot create from asset or file for an "
+ "in-memory database.");
}
if (mCopyFromAssetPath != null && mCopyFromFile != null) {
throw new IllegalArgumentException("Both createFromAsset() and "
+ "createFromFile() was called on this Builder but the database can "
+ "only be created using one of the two configurations.");
}
// 新建 SQLiteCopyOpenHelperFactory 工厂实例,用于创建 SQLiteCopyOpenHelper 实例
mFactory = new SQLiteCopyOpenHelperFactory(mCopyFromAssetPath, mCopyFromFile,
mFactory);
}
// 构建数据库参数配置对象
DatabaseConfiguration configuration =
new DatabaseConfiguration(
mContext,
mName,
mFactory,
mMigrationContainer,
mCallbacks,
mAllowMainThreadQueries,
mJournalMode.resolve(mContext),
mQueryExecutor,
mTransactionExecutor,
mMultiInstanceInvalidation,
mRequireMigration,
mAllowDestructiveMigrationOnDowngrade,
mMigrationsNotRequiredFrom,
mCopyFromAssetPath,
mCopyFromFile);
// 查找数据库编译生成的实现类 _impl
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
// 数据库初始化操作
db.init(configuration);
return db;
}
}
}
public abstract class RoomDatabase {
@CallSuper
public void init(@NonNull DatabaseConfiguration configuration) {
// createOpenHelper 抽象方法,具体实现由每个数据库实现类完成
// 在 ROOM 中的实现类是通过注解编译生成的
//
// 返回 FrameworkSQLiteOpenHelper 实例,
// Room 数据库的各种操作由 FrameworkSQLiteOpenHelper 通知 sqLite 完成
mOpenHelper = createOpenHelper(configuration);
if (mOpenHelper instanceof SQLiteCopyOpenHelper) {
SQLiteCopyOpenHelper copyOpenHelper = (SQLiteCopyOpenHelper) mOpenHelper;
copyOpenHelper.setDatabaseConfiguration(configuration);
}
boolean wal = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// 是否开启预写日志功能
wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING;
mOpenHelper.setWriteAheadLoggingEnabled(wal);
}
mCallbacks = configuration.callbacks;
mQueryExecutor = configuration.queryExecutor;
mTransactionExecutor = new TransactionExecutor(configuration.transactionExecutor);
// 是否允许主线程操作数据库
mAllowMainThreadQueries = configuration.allowMainThreadQueries;
// 标记是否开启预写日志功能(WAL)
mWriteAheadLoggingEnabled = wal;
/**
* If true, table invalidation in an instance of {@link RoomDatabase} is broadcast and
* synchronized with other instances of the same {@link RoomDatabase} file, including those
* in a separate process.
*/
// public final boolean multiInstanceInvalidation;
if (configuration.multiInstanceInvalidation) {
mInvalidationTracker.startMultiInstanceInvalidation(configuration.context,
configuration.name);
}
}
}
【3.3】Room + LiveData 监听数据变更并自动刷新页面分析
Room + LiveData 数据懒加载
public final class CacheDao_Impl implements CacheDao {
@Override
public LiveData<List<Cache>> query2() {
return __db.getInvalidationTracker().createLiveData(new String[]{"table_cache"}, false,
new Callable<List<Cache>>() {
@Override
public List<Cache> call() throws Exception {
// 一旦向 LiveData 注册第一个观察者的时候,才会触发这个回调
// 即通过 RoomDatabase 去查询 table_cache 最新的数据
}
@Override
protected void finalize() {
}
}
}
}
// RoomDatabase 类的构造方法中初始化
public class InvalidationTracker {
// 在 InvalidationTracker 的构造函数中初始化
private final InvalidationLiveDataContainer mInvalidationLiveDataContainer;
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public <T> LiveData<T> createLiveData(String[] tableNames, boolean inTransaction,
Callable<T> computeFunction) {
// 创建 RoomTrackingLiveData 实例
return mInvalidationLiveDataContainer.create(
validateAndResolveTableNames(tableNames), inTransaction, computeFunction);
}
}
class InvalidationLiveDataContainer {
// 构造 InvalidationTracker 时传入
private final RoomDatabase mDatabase;
<T> LiveData<T> create(String[] tableNames, boolean inTransaction,
Callable<T> computeFunction) {
// 构造 RoomTrackingLiveData 实例
return new RoomTrackingLiveData<>(mDatabase, this, inTransaction, computeFunction,
tableNames);
}
}
class RoomTrackingLiveData<T> extends LiveData<T> {
@SuppressLint("RestrictedApi")
RoomTrackingLiveData(
RoomDatabase database,
InvalidationLiveDataContainer container,
boolean inTransaction,
Callable<T> computeFunction,
String[] tableNames) {
mDatabase = database;
mInTransaction = inTransaction;
// 首先保存 Callable 类型的 callback 回调并等待需要加载数据的时候触发回调处理
mComputeFunction = computeFunction;
mContainer = container;
// 创建 InvalidationTracker.Observer
// 用于接收表数据变更的事件回调,进而触发 refreshRunnable 加载数据
mObserver = new InvalidationTracker.Observer(tableNames) {
@Override
public void onInvalidated(@NonNull Set<String> tables) {
ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
}
};
}
@Override
protected void onActive() {
super.onActive();
mContainer.onActive(this);
// 第一次注册观察者时,便会触发 mRefreshRunnable 去加载首次数据
getQueryExecutor().execute(mRefreshRunnable);
}
@SuppressWarnings("WeakerAccess")
final Runnable mInvalidationRunnable = new Runnable() {
@MainThread
@Override
public void run() {
boolean isActive = hasActiveObservers();
if (mInvalid.compareAndSet(false, true)) {
if (isActive) {
// 调用 mRefreshRunnable 中的方法
getQueryExecutor().execute(mRefreshRunnable);
}
}
}
};
@SuppressWarnings("WeakerAccess")
final Runnable mRefreshRunnable = new Runnable() {
@WorkerThread
@Override
public void run() {
// final AtomicBoolean mRegisteredObserver = new AtomicBoolean(false);
// mRegisteredObserver 初始化为 false 并在此处赋值为 true,此后便没有再发生改变
// 从而确保了 addWeakObserver 方法只会执行一次
//
// 通过 AtomicBoolean 的 CAS 保证多线程同步,向 InvalidationTracker 注册观察者
if (mRegisteredObserver.compareAndSet(false, true)) {
// 向 InvalidationTracker 注册 InvalidationTracker.Observer
// 第一次向 InvalidationTracker 注册时会触发 onActive 方法,进入触发 mRefreshRunnable 加载首次数据
mDatabase.getInvalidationTracker().addWeakObserver(mObserver);
}
boolean computed;
do {
computed = false;
// compute can happen only in 1 thread but no reason to lock others.
if (mComputing.compareAndSet(false, true)) {
// as long as it is invalid, keep computing.
try {
T value = null;
while (mInvalid.compareAndSet(true, false)) {
computed = true;
try {
// 回调 mComputeFunction 中的 call() 方法
// 在该方法中加载最新的数据并保存到 value 中
value = mComputeFunction.call();
} catch (Exception e) {
throw new RuntimeException("Exception while computing database"
+ " live data.", e);
}
}
if (computed) {
// LiveData 发送数据实现更新
postValue(value);
}
} finally {
// release compute lock
mComputing.set(false);
}
}
// check invalid after releasing compute lock to avoid the following scenario.
// Thread A runs compute()
// Thread A checks invalid, it is false
// Main thread sets invalid to true
// Thread B runs, fails to acquire compute lock and skips
// Thread A releases compute lock
// We've left invalid in set state. The check below recovers.
} while (computed && mInvalid.get());
}
};
}
Room + LiveData 监听数据库数据变更
public final class CacheDao_Impl implements CacheDao {
@Override
public void save(final Cache cache) {
try {
} finally {
__db.endTransaction();
}
}
@Override
public void delete(final Cache cache) {
try {
} finally {
__db.endTransaction();
}
}
@Override
public void update(final Cache cache) {
try {
} finally {
__db.endTransaction();
}
}
}
public abstract class RoomDatabase {
public RoomDatabase() {
// 在构造函数中构建数据库 InvalidationTracker
// 用以追踪、记录数据发生变化了的表
//
// createInvalidationTracker() 是抽象方法由子类实现
mInvalidationTracker = createInvalidationTracker();
}
@Deprecated
public void beginTransaction() {
assertNotMainThread();
SupportSQLiteDatabase database = mOpenHelper.getWritableDatabase();
// 通知 InvalidationTracker 去更新数据即将发生变化的表的状态
mInvalidationTracker.syncTriggers(database);
database.beginTransaction();
}
public void endTransaction() {
// 通过 FrameworkSQLiteOpenHelper 通知 sqLite 结束事务
mOpenHelper.getWritableDatabase().endTransaction();
// inTransaction()
// Returns true if current thread is in a transaction
if (!inTransaction()) {
// enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
// endTransaction call to do it.
//
// 通知 InvalidationTracker 去查询出那些数据发生变化的表
mInvalidationTracker.refreshVersionsAsync();
}
}
}
public class InvalidationTracker {
// InvalidationTracker 在创建之初就会获取数据库所有的表
// syncTriggers 方法会去遍历每个表是否已注册数据变更的 obsever
// 如果有,则开启对这个表的状态记录,即向 room_table_modification_log 这张表中写入记录
//
// 表的结构形如
// table_id列 invalidated列
// table_cache 1 // 为1,就被认为数据生了变化,
// table_id 这列实际上记录的是表的 Id(1,2,3)
// table_cache2 0
// table_cache3 0
private void startTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
writableDb.execSQL(
"INSERT OR IGNORE INTO " + UPDATE_TABLE_NAME + " VALUES(" + tableId + ", 0)");
final String tableName = mTableNames[tableId];
StringBuilder stringBuilder = new StringBuilder();
for (String trigger : TRIGGERS) {
stringBuilder.setLength(0);
stringBuilder.append("CREATE TEMP TRIGGER IF NOT EXISTS ");
appendTriggerName(stringBuilder, tableName, trigger);
stringBuilder.append(" AFTER ")
.append(trigger)
.append(" ON `")
.append(tableName)
.append("` BEGIN UPDATE ")
.append(UPDATE_TABLE_NAME)
.append(" SET ").append(INVALIDATED_COLUMN_NAME).append(" = 1")
.append(" WHERE ").append(TABLE_ID_COLUMN_NAME).append(" = ").append(tableId)
.append(" AND ").append(INVALIDATED_COLUMN_NAME).append(" = 0")
.append("; END");
writableDb.execSQL(stringBuilder.toString());
}
}
// Enqueues a task to refresh the list of updated tables
public void refreshVersionsAsync() {
// TODO we should consider doing this sync instead of async
if (mPendingRefresh.compareAndSet(false, true)) {
// 执行 mRefreshRunnable 中的方法
mDatabase.getQueryExecutor().execute(mRefreshRunnable);
}
}
@VisibleForTesting
Runnable mRefreshRunnable = new Runnable() {
@Override
public void run() {
final Lock closeLock = mDatabase.getCloseLock();
Set<Integer> invalidatedTableIds = null;
try {
closeLock.lock();
if (!ensureInitialization()) {
return;
}
if (!mPendingRefresh.compareAndSet(true, false)) {
// no pending refresh
return;
}
if (mDatabase.inTransaction()) {
// current thread is in a transaction. when it ends, it will invoke
// refreshRunnable again. mPendingRefresh is left as false on purpose
// so that the last transaction can flip it on again.
return;
}
// // 调用 checkUpdatedTable 方法检查改动表的集合
if (mDatabase.mWriteAheadLoggingEnabled) {
// This transaction has to be on the underlying DB rather than the RoomDatabase
// in order to avoid a recursive loop after endTransaction.
SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase();
db.beginTransaction();
try {
invalidatedTableIds = checkUpdatedTable();
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} else {
invalidatedTableIds = checkUpdatedTable();
}
} catch (IllegalStateException | SQLiteException exception) {
// may happen if db is closed. just log.
Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
exception);
} finally {
closeLock.unlock();
}
if (invalidatedTableIds != null && !invalidatedTableIds.isEmpty()) {
synchronized (mObserverMap) {
for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {
// 遍历并通知观察者
entry.getValue().notifyByTableInvalidStatus(invalidatedTableIds);
}
}
}
}
private Set<Integer> checkUpdatedTable() {
HashSet<Integer> invalidatedTableIds = new HashSet<>();
Cursor cursor = mDatabase.query(new SimpleSQLiteQuery(SELECT_UPDATED_TABLES_SQL));
//noinspection TryFinallyCanBeTryWithResources
try {
while (cursor.moveToNext()) {
final int tableId = cursor.getInt(0);
invalidatedTableIds.add(tableId);
}
} finally {
cursor.close();
}
if (!invalidatedTableIds.isEmpty()) {
mCleanupStatement.executeUpdateDelete();
}
return invalidatedTableIds;
}
};
// notifyByTableInvalidStatus 向 room_table_modification_log 表中,
// 读取出所有 invalidated 列的值为 1 的数据
void notifyByTableInvalidStatus(Set<Integer> invalidatedTablesIds) {
Set<String> invalidatedTables = null;
final int size = mTableIds.length;
for (int index = 0; index < size; index++) {
final int tableId = mTableIds[index];
if (invalidatedTablesIds.contains(tableId)) {
if (size == 1) {
// Optimization for a single-table observer
invalidatedTables = mSingleTableSet;
} else {
if (invalidatedTables == null) {
invalidatedTables = new HashSet<>(size);
}
invalidatedTables.add(mTableNames[index]);
}
}
}
if (invalidatedTables != null) {
// 向每个注册进来的 observer 回调本次事件
// 从而回调了 RoomTrackingLiveData 中 mObserver 成员的 onInvalidated 的方法
// 在该方法中执行了 mInvalidationRunnable 中的方法
mObserver.onInvalidated(invalidatedTables);
}
}
}