一、背景

某天在看到google推荐的官方架构中对数据层的介绍提到:

数据层的最佳实践是离线优先,即单一数据来历来自数据库,以数据库的数据为驱动UI改动的中心

Room监听本地数据变化原理

所以当网络数据返回刺进数据库今后,怎么监听数据库的改动成为了解决问题的要害

二、监听数据库改动

咱们知道在Android中能够运用ContentResolver监听URI的改动来完成对数据库改动的监听 /post/699330…

所以就想集成room今后应该能够快速创建URI来帮助咱们完成对数据的监听,经过一顿查资料一顿搜索发现有点南辕北辙

按理说,伴随着MVVM架构一同出现的jitpack一部分的room理应也想到了这儿,是否咱们在返回的时分直接添加一个LiveData/Flow就能监听数据库的改动了呢,尝试了一下的确如此,所以好奇心趋势我开始探究完成原理

三、原理探究

首先room是一款ORM(目标-关系映射)型数据库,用法参考developer.android.com/training/da…,根据注解的方式也就意味着肯定是根据APT,会在编译时主动生成代码

咱们编译之后看代码

Room监听本地数据变化原理
Room监听本地数据变化原理
能够看到要害点在于InvalidationTracker(无效跟踪器),咱们追踪源码看到经过SQL界说了增修改的三个触发器(触发器是一种机制,能够在insert,update,delete之前或之后触发并履行触发器中界说的SQL句子,具体的操作能够自行百度)

private static final String[] TRIGGERS = new String[]{"UPDATE", "DELETE", "INSERT"};
private static final String UPDATE_TABLE_NAME = "room_table_modification_log";
private static final String TABLE_ID_COLUMN_NAME = "table_id";
private static final String INVALIDATED_COLUMN_NAME = "invalidated";
@VisibleForTesting
static final String RESET_UPDATED_TABLES_SQL = "UPDATE " + UPDATE_TABLE_NAME
        + " SET " + INVALIDATED_COLUMN_NAME + " = 0 WHERE " + INVALIDATED_COLUMN_NAME + " = 1 ";
@VisibleForTesting
static final String SELECT_UPDATED_TABLES_SQL = "SELECT * FROM " + UPDATE_TABLE_NAME
        + " WHERE " + INVALIDATED_COLUMN_NAME + " = 1;";
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());
    }
}
private static void appendTriggerName(StringBuilder builder, String tableName,
        String triggerType) {
    builder.append("`")
            .append("room_table_modification_trigger_")
            .append(tableName)
            .append("_")
            .append(triggerType)
            .append("`");
}
private void stopTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
    final String tableName = mTableNames[tableId];
    StringBuilder stringBuilder = new StringBuilder();
    for (String trigger : TRIGGERS) {
        stringBuilder.setLength(0);
        stringBuilder.append("DROP TRIGGER IF EXISTS ");
        appendTriggerName(stringBuilder, tableName, trigger);
        writableDb.execSQL(stringBuilder.toString());
    }
}

并且在数据改动的时分对room_table_modification_log这个表进行了更新,标识哪个tab发生了改动

既然如此,下一步肯定便是查询更新的表并想办法通知LiveData的改动,所以经过SELECT_UPDATED_TABLES_SQL的调用栈反向追踪到refreshVersionsAsync,注释中也指明了源头是来自endTransaction() 办法的调用

Room监听本地数据变化原理

endTransation望文生义便是每次履行完SQL的事务结束办法,来自代码生成的dao层生成的文件中

Room监听本地数据变化原理

所以整个链路就清晰了,每次数据库进行了增修改查操作之后,在真正刺进数据之前触发器就会履行相关操作修改room_table_modification_log表中的记载,记载发生变动的表的id和对应状态,等刺进完成履行__db.endTransaction()操作时,再去查询room_table_modification_log表履行observer中的逻辑

Room监听本地数据变化原理

那么observe是从什么地方注册的呢?

让咱们再回到文章一开始的截图生成查询句子的地方,在刚开始会履行createLiveData,创建一个RoomTrackingLiveData并把咱们的查询办法经过computeFunction传进去

Room监听本地数据变化原理
Room监听本地数据变化原理
Room监听本地数据变化原理

读到这儿不由要吐槽一下google的研发应该也会copy…好几处的runnable命名都一样不仔细看真容易陷进去…

四、总结

当咱们履行数据库操作(增修改)时,Room 框架会主动触发 LiveData 通知数据的改动。LiveData 会主动检测数据库改动并通知已注册的观察者进行更新,Flow跟Rxjava也是同理(需求导入相对应的扩展包)。

Room监听本地数据变化原理
图片参考自www.bmabk.com/index.php/p…