Room监听本地数据变化原理

一、背景

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

数据层的最佳实践是离线优先,即单一数据来源来自数据库,以数据库的数据为驱动UI变化的核心

image.png

所以当网络数据返回插入数据库以后,怎么监听数据库的变化成为了解决问题的关键

二、监听数据库变化

我们知道在Android中可以使用ContentResolver监听URI的变化来实现对数据库变化的监听 juejin.cn/post/699330…

于是就想集成room以后应该可以快速创建URI来帮助我们实现对数据的监听,经过一顿查资料一顿搜索发现有点南辕北辙

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

三、原理探究

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

我们编译之后看代码
image.pngimage.png
可以看到关键点在于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() 方法的调用
image.png

endTransation顾名思义就是每次执行完SQL的事务结束方法,来自代码生成的dao层生成的文件中
image.png

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

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

让我们再回到文章一开始的截图生成查询语句的地方,在刚开始会执行createLiveData,创建一个RoomTrackingLiveData并把我们的查询方法通过computeFunction传进去
image.pngimage.pngimage.png

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

四、总结

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

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYEtY7OD' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片