AACのRoomでLiveDataなどでどうやって変更を流しているのか読んでみてちょっと面白かったので、書いておきます。
読んだだけなので間違っていたら教えてください。
テーブルごとの変更のバージョン管理
メモリ内にデータのバージョンを管理するテーブルがある
カラムはversionIdとtableId。versionIdはPRIMARY KEYでAUTO INCREMENTになっている。
private static final String CREATE_VERSION_TABLE_SQL = "CREATE TEMP TABLE " + UPDATE_TABLE_NAME
+ "(" + VERSION_COLUMN_NAME
+ " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ TABLE_ID_COLUMN_NAME
+ " INTEGER)";
SQLiteのTRIGGERを使ってバージョン管理のテーブルをアップデートする
SQLiteのTRIGGERを使って”UPDATE", "DELETE", "INSERT"を検知したら、INSERT OR REPLACE INTO
を使って、入れ替える。
それによりAUTO INCREMENTなversionIdカラムがINCREMENTする。
TRIGGERSには”UPDATE", "DELETE", "INSERT”がはいっている。
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 INSERT OR REPLACE INTO ")
.append(UPDATE_TABLE_NAME)
.append(" VALUES(null, ")
.append(tableId)
.append("); END");
writableDb.execSQL(stringBuilder.toString());
}
これをするために特殊なことをしているみたい
database.execSQL("PRAGMA temp_store = MEMORY;");
database.execSQL("PRAGMA recursive_triggers='ON';");
変更があったかもしれないタイミングにどうやって動くか?
@Update
などのメソッドを呼んだ時に自動的にTransactionが行われ、そのTransactionが終わった時にテーブルを使って、変更があるかを確認します。
public void endTransaction() {
mOpenHelper.getWritableDatabase().endTransaction();
if (!inTransaction()) {
// enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
// endTransaction call to do it.
// ***ここ***
mInvalidationTracker.refreshVersionsAsync();
}
}
あるLiveDataがどのように変更があったか送るか
LiveDataなどからobserveしており、そのObserverがLiveDataで通知したいテーブル名とそのバージョン一覧をメンバ変数で持っている。DBの内容を比較して変更があればを呼び出す。
final long newVersion = versions[tableId];
final long currentVersion = mVersions[index];
if (currentVersion < newVersion) {
mVersions[index] = newVersion;
…
invalidatedTables.add(mTableNames[index]);
…
if (invalidatedTables != null) {
mObserver.onInvalidated(invalidatedTables);
}
LiveDataでの流し方
ComputableLiveDataのinvaridate()を呼び出して、クエリを実行して、値を流す
@Override
public LiveData<List<Cheese>> all() {
final String _sql = "SELECT * FROM Cheese";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
return new ComputableLiveData<List<Cheese>>() {
private Observer _observer;
@Override
protected List<Cheese> compute() {
if (_observer == null) {
_observer = new Observer("Cheese") {
@Override
public void onInvalidated(@NonNull Set<String> tables) {
invalidate();
}
};
__db.getInvalidationTracker().addWeakObserver(_observer);
}
final Cursor _cursor = __db.query(_statement);
try {
...