一、背景
某天在看到google推荐的官方架构中对数据层的介绍提到:
数据层的最佳实践是离线优先,即单一数据来历来自数据库,以数据库的数据为驱动UI改动的中心
所以当网络数据返回刺进数据库今后,怎么监听数据库的改动成为了解决问题的要害
二、监听数据库改动
咱们知道在Android中能够运用ContentResolver监听URI的改动来完成对数据库改动的监听 /post/699330…
所以就想集成room今后应该能够快速创建URI来帮助咱们完成对数据的监听,经过一顿查资料一顿搜索发现有点南辕北辙
按理说,伴随着MVVM架构一同出现的jitpack一部分的room理应也想到了这儿,是否咱们在返回的时分直接添加一个LiveData/Flow就能监听数据库的改动了呢,尝试了一下的确如此,所以好奇心趋势我开始探究完成原理
三、原理探究
首先room是一款ORM(目标-关系映射)型数据库,用法参考developer.android.com/training/da…,根据注解的方式也就意味着肯定是根据APT,会在编译时主动生成代码
咱们编译之后看代码
能够看到要害点在于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()
办法的调用
而endTransation
望文生义便是每次履行完SQL的事务结束办法,来自代码生成的dao层生成的文件中
所以整个链路就清晰了,每次数据库进行了增修改查操作之后,在真正刺进数据之前触发器就会履行相关操作修改room_table_modification_log
表中的记载,记载发生变动的表的id和对应状态,等刺进完成履行__db.endTransaction()
操作时,再去查询room_table_modification_log
表履行observer
中的逻辑
那么observe是从什么地方注册的呢?
让咱们再回到文章一开始的截图生成查询句子的地方,在刚开始会履行createLiveData,创建一个RoomTrackingLiveData
并把咱们的查询办法经过computeFunction
传进去
读到这儿不由要吐槽一下google的研发应该也会copy…好几处的runnable命名都一样不仔细看真容易陷进去…
四、总结
当咱们履行数据库操作(增修改)时,Room 框架会主动触发 LiveData 通知数据的改动。LiveData 会主动检测数据库改动并通知已注册的观察者进行更新,Flow跟Rxjava也是同理(需求导入相对应的扩展包)。
图片参考自www.bmabk.com/index.php/p…