【安卓开发系列 -- 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 实现层就是通过注解 + 编译时处理器的形式生成相应的实现类完成相应的功能;

android room 没有 entity 安卓the room_数据库

【3.2】Room 数据库创建流程

android room 没有 entity 安卓the room_字段_02

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 数据懒加载

android room 没有 entity 安卓the room_数据库_03

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 监听数据库数据变更

android room 没有 entity 安卓the room_SQL_04

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);
        }
    }
}