15
10

More than 5 years have passed since last update.

AACのRoomでLiveDataなどがどうやって変更を流しているのか

Last updated at Posted at 2018-03-19

AACのRoomでLiveDataなどでどうやって変更を流しているのか読んでみてちょっと面白かったので、書いておきます。
読んだだけなので間違っていたら教えてください。

テーブルごとの変更のバージョン管理

メモリ内にデータのバージョンを管理するテーブルがある

カラムはversionIdとtableId。versionIdはPRIMARY KEYでAUTO INCREMENTになっている。

InvalidationTracker.java
    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”がはいっている。

InvalidationTracker.java
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());
        }

これをするために特殊なことをしているみたい

InvalidationTracker.java
                database.execSQL("PRAGMA temp_store = MEMORY;");
                database.execSQL("PRAGMA recursive_triggers='ON';");

変更があったかもしれないタイミングにどうやって動くか?

@Updateなどのメソッドを呼んだ時に自動的にTransactionが行われ、そのTransactionが終わった時にテーブルを使って、変更があるかを確認します。

RoomDatabase.java
    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の内容を比較して変更があればを呼び出す。

InvalidationTracker.java
                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 {
...
15
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
10