LiveData是如何感知Room数据变化的

一  Room数据变化LiveData如何收到onChanged回调的?

1.1  LiveData是如何创建的

这里讨论的LiveData的创建是特指Dao定义的方法的返回类型,而不是所有的LiveData。

NoteDao 举例:

@Dao
public interface NoteDao {
    @Query("select * from note")
    LiveData<List<EntityNote>> getAll();

    @Update
    int update(EntityNote note);

    @Delete
    int delete(EntityNote note);

    @Insert
    void insert(EntityNote note);
}

 Room会通过APT自动为NoteDao 创建实体类NoteDao_Impl.java

package com.example.sourcecode.jetpack.dao;

import android.database.Cursor;
import androidx.lifecycle.LiveData;
import androidx.room.EntityDeletionOrUpdateAdapter;
import androidx.room.EntityInsertionAdapter;
import androidx.room.RoomDatabase;
import androidx.room.RoomSQLiteQuery;
import androidx.room.util.CursorUtil;
import androidx.room.util.DBUtil;
import androidx.sqlite.db.SupportSQLiteStatement;
import com.example.sourcecode.jetpack.entity.EntityNote;
import java.lang.Class;
import java.lang.Exception;
import java.lang.Override;
import java.lang.String;
import java.lang.SuppressWarnings;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;

@SuppressWarnings({"unchecked", "deprecation"})
public final class NoteDao_Impl implements NoteDao {
  private final RoomDatabase __db;

  private final EntityInsertionAdapter<EntityNote> __insertionAdapterOfEntityNote;

  private final EntityDeletionOrUpdateAdapter<EntityNote> __deletionAdapterOfEntityNote;

  private final EntityDeletionOrUpdateAdapter<EntityNote> __updateAdapterOfEntityNote;

  public NoteDao_Impl(RoomDatabase __db) {
    this.__db = __db;
    this.__insertionAdapterOfEntityNote = new EntityInsertionAdapter<EntityNote>(__db) {
      @Override
      public String createQuery() {
        return "INSERT OR ABORT INTO `note` (`id`,`uuid`,`title`,`content`,`searchContent`) VALUES (nullif(?, 0),?,?,?,?)";
      }

      @Override
      public void bind(SupportSQLiteStatement stmt, EntityNote value) {
        stmt.bindLong(1, value.id);
        if (value.uuid == null) {
          stmt.bindNull(2);
        } else {
          stmt.bindString(2, value.uuid);
        }
        if (value.title == null) {
          stmt.bindNull(3);
        } else {
          stmt.bindString(3, value.title);
        }
        if (value.content == null) {
          stmt.bindNull(4);
        } else {
          stmt.bindString(4, value.content);
        }
        if (value.searchContent == null) {
          stmt.bindNull(5);
        } else {
          stmt.bindString(5, value.searchContent);
        }
      }
    };
    this.__deletionAdapterOfEntityNote = new EntityDeletionOrUpdateAdapter<EntityNote>(__db) {
      @Override
      public String createQuery() {
        return "DELETE FROM `note` WHERE `id` = ?";
      }

      @Override
      public void bind(SupportSQLiteStatement stmt, EntityNote value) {
        stmt.bindLong(1, value.id);
      }
    };
    this.__updateAdapterOfEntityNote = new EntityDeletionOrUpdateAdapter<EntityNote>(__db) {
      @Override
      public String createQuery() {
        return "UPDATE OR ABORT `note` SET `id` = ?,`uuid` = ?,`title` = ?,`content` = ?,`searchContent` = ? WHERE `id` = ?";
      }

      @Override
      public void bind(SupportSQLiteStatement stmt, EntityNote value) {
        stmt.bindLong(1, value.id);
        if (value.uuid == null) {
          stmt.bindNull(2);
        } else {
          stmt.bindString(2, value.uuid);
        }
        if (value.title == null) {
          stmt.bindNull(3);
        } else {
          stmt.bindString(3, value.title);
        }
        if (value.content == null) {
          stmt.bindNull(4);
        } else {
          stmt.bindString(4, value.content);
        }
        if (value.searchContent == null) {
          stmt.bindNull(5);
        } else {
          stmt.bindString(5, value.searchContent);
        }
        stmt.bindLong(6, value.id);
      }
    };
  }

  @Override
  public void insert(final EntityNote note) {
    __db.assertNotSuspendingTransaction();
    __db.beginTransaction();
    try {
      __insertionAdapterOfEntityNote.insert(note);
      __db.setTransactionSuccessful();
    } finally {
      __db.endTransaction();
    }
  }

  @Override
  public int delete(final EntityNote note) {
    __db.assertNotSuspendingTransaction();
    int _total = 0;
    __db.beginTransaction();
    try {
      _total +=__deletionAdapterOfEntityNote.handle(note);
      __db.setTransactionSuccessful();
      return _total;
    } finally {
      __db.endTransaction();
    }
  }

  @Override
  public int update(final EntityNote note) {
    __db.assertNotSuspendingTransaction();
    int _total = 0;
    __db.beginTransaction();
    try {
      _total +=__updateAdapterOfEntityNote.handle(note);
      __db.setTransactionSuccessful();
      return _total;
    } finally {
      __db.endTransaction();
    }
  }

  @Override
  public LiveData<List<EntityNote>> getAll() {
    final String _sql = "select * from note";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
    return __db.getInvalidationTracker().createLiveData(new String[]{"note"}, false, new Callable<List<EntityNote>>() {
      @Override
      public List<EntityNote> call() throws Exception {
        final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
        try {
          final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
          final int _cursorIndexOfUuid = CursorUtil.getColumnIndexOrThrow(_cursor, "uuid");
          final int _cursorIndexOfTitle = CursorUtil.getColumnIndexOrThrow(_cursor, "title");
          final int _cursorIndexOfContent = CursorUtil.getColumnIndexOrThrow(_cursor, "content");
          final int _cursorIndexOfSearchContent = CursorUtil.getColumnIndexOrThrow(_cursor, "searchContent");
          final List<EntityNote> _result = new ArrayList<EntityNote>(_cursor.getCount());
          while(_cursor.moveToNext()) {
            final EntityNote _item;
            _item = new EntityNote();
            _item.id = _cursor.getInt(_cursorIndexOfId);
            if (_cursor.isNull(_cursorIndexOfUuid)) {
              _item.uuid = null;
            } else {
              _item.uuid = _cursor.getString(_cursorIndexOfUuid);
            }
            if (_cursor.isNull(_cursorIndexOfTitle)) {
              _item.title = null;
            } else {
              _item.title = _cursor.getString(_cursorIndexOfTitle);
            }
            if (_cursor.isNull(_cursorIndexOfContent)) {
              _item.content = null;
            } else {
              _item.content = _cursor.getString(_cursorIndexOfContent);
            }
            if (_cursor.isNull(_cursorIndexOfSearchContent)) {
              _item.searchContent = null;
            } else {
              _item.searchContent = _cursor.getString(_cursorIndexOfSearchContent);
            }
            _result.add(_item);
          }
          return _result;
        } finally {
          _cursor.close();
        }
      }

      @Override
      protected void finalize() {
        _statement.release();
      }
    });
  }

  public static List<Class<?>> getRequiredConverters() {
    return Collections.emptyList();
  }
}

通过InvalidationTracker#createLiveData方法创建需要返回的LiveData对象。

// InvalidationTracker.java

public <T> LiveData<T> createLiveData(String[] tableNames, Callable<T> computeFunction) {
    return createLiveData(tableNames, false, computeFunction);
}

public <T> LiveData<T> createLiveData(String[] tableNames, boolean inTransaction,
        Callable<T> computeFunction) {
    return mInvalidationLiveDataContainer.create(
            validateAndResolveTableNames(tableNames), inTransaction, computeFunction);
}
// InvalidationLiveDataContainer.java

<T> LiveData<T> create(String[] tableNames, boolean inTransaction,
        Callable<T> computeFunction) {
    return new RoomTrackingLiveData<>(mDatabase, this, inTransaction, computeFunction,
            tableNames);
}

InvalidationLiveDataContainer的功能比较简单:

  • 创建RoomTrackingLiveData对象;
  • 维护一个装载LiveData对象的set集合。

总结:

  1. room会根据开发者定义的dataBae和各个dao类自动创建各自的对应的实体类;
  2. DAO_Impl的实体方法会委托InvalidationTracker类创建需要返回的LiveData对象,并将数据库操作方法以参数的形式向下传递。
  3. InvalidationTracker类委托InvalidationLiveDataContainer类创建RoomTrackingLiveData对象。自此LiveData对象创建成功。

1.2 RoomTrackingLiveData有何作用

class RoomTrackingLiveData<T> extends LiveData<T> {
   
    final RoomDatabase mDatabase;

    final boolean mInTransaction;

    final Callable<T> mComputeFunction;

    private final InvalidationLiveDataContainer mContainer;

    final InvalidationTracker.Observer mObserver;

    final AtomicBoolean mInvalid = new AtomicBoolean(true);

    final AtomicBoolean mComputing = new AtomicBoolean(false);

    final AtomicBoolean mRegisteredObserver = new AtomicBoolean(false);

    final Runnable mRefreshRunnable = new Runnable() {
        @WorkerThread
        @Override
        public void run() {
            // 向InvalidationTracker注册一个观察者
            if (mRegisteredObserver.compareAndSet(false, true)) {
                mDatabase.getInvalidationTracker().addWeakObserver(mObserver);
            }
            boolean computed;
            do {
                computed = false;
                // mComputing 初始值为 false
                if (mComputing.compareAndSet(false, true)) {
                    // as long as it is invalid, keep computing.
                    try {
                        T value = null;
                        // mInvalid初始值为 true
                        // 此while循环结束后,computed == false,mInvalid == false
                        while (mInvalid.compareAndSet(true, false)) {
                            computed = true;
                            try {
                                // 执行数据库操作方法,并返回结果
                                value = mComputeFunction.call();
                            } catch (Exception e) {
                                // 如果SQL语句执行有误,会非常粗暴的直接报错,
                                // liveData不能将错误状态上报给开发者。
                                throw new RuntimeException("Exception while computing database"
                                        + " live data.", e);
                            }
                        }
                        if (computed) {
                            // 向当前livedata的观察者们发送数据库查询结果
                            postValue(value);
                        }
                    } finally {
                        // release compute lock
                        mComputing.set(false);
                    }
                }
               
            } while (computed && mInvalid.get());
        }
    };

    @SuppressWarnings("WeakerAccess")
    final Runnable mInvalidationRunnable = new Runnable() {
        @MainThread
        @Override
        public void run() {
            // 当前livedata是否有存活的观察者
            boolean isActive = hasActiveObservers();
            // 如果 mRefreshRunnable正在运行 mInvalid == true,条件不成立。
            // 如果 mRefreshRunnable运行结束 mInvalid == false,条件成立,重新开启任务。
            if (mInvalid.compareAndSet(false, true)) {
                if (isActive) {
                    getQueryExecutor().execute(mRefreshRunnable);
                }
            }
        }
    };
    @SuppressLint("RestrictedApi")
    RoomTrackingLiveData(
            RoomDatabase database,
            InvalidationLiveDataContainer container,
            boolean inTransaction,
            Callable<T> computeFunction,
            String[] tableNames) {
        mDatabase = database;
        mInTransaction = inTransaction;
        mComputeFunction = computeFunction;
        mContainer = container;
        mObserver = new InvalidationTracker.Observer(tableNames) {
            @Override
            public void onInvalidated(@NonNull Set<String> tables) {
                ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
            }
        };
    }

    @Override
    protected void onActive() {
        super.onActive();
        mContainer.onActive(this);
        getQueryExecutor().execute(mRefreshRunnable);
    }

    @Override
    protected void onInactive() {
        super.onInactive();
        mContainer.onInactive(this);
    }

    Executor getQueryExecutor() {
        if (mInTransaction) {
            return mDatabase.getTransactionExecutor();
        } else {
            return mDatabase.getQueryExecutor();
        }
    }
}
  1. 当开发者向RoomTrackingLiveData注册了观察者后(即调用了livedata.observe方法),会调用onActive方法,在子线程里执行mRefreshRunnable任务。
  2. mRefreshRunnable在初次执行时会向InvalidationTracker注册一个观察者。然后会根据SQL语句循环查询数据库,并向开发者返回查询结果。
    a. SQL语句是通过开发者在创建DAO层方法的注解自动生成的,并以方法入参的方式最终传递给RoomTrackingLiveData对象。
    b. 这里的循环不是一直执行的。在没有外界干扰情况下(指循环条件的值在没有被其他方法修改的情况),循环体只会执行一次。
  3. 构造函数里创建了mObserver 对象,当mObserver被触发时,会在主线程执行mInvalidationRunnable任务。
  4. mInvalidationRunnable会在子线程里开启mRefreshRunnable任务,重新查询数据库,并返回数据。

总结:

  1. RoomTrackingLiveData有三个比较重要的任务:mRefreshRunnablemInvalidationRunnablemObserver
  2. mRefreshRunnable主要负责向数据库查询数据,并将结果返回给开发者注册的观察者。
  3. mObserver负责唤醒mInvalidationRunnable
  4. mInvalidationRunnable任务分两种情况:
    • mRefreshRunnable还在运行时,会要求mRefreshRunnable再执行一次数据库查询任务,并按要求将结果上报。(这个逻辑是在mRefreshRunnable里实现的。)
    • mRefreshRunnable停止运行时,会在子线程里重新开启mRefreshRunnable任务。

由上可知,room配合livedata使用时,之所以livedata能够自动感知数据库数据变化,是由mObservermInvalidationRunnablemRefreshRunnable三方共同配合的结果。

1.3 数据库变化时,是如何通知RoomTrackingLiveData

由上文可以推断出,当数据库发生变化时,是通过mObserver来启动数据库查询任务,并将结果通过
RoomTrackingLiveData#postValue方法传递给订阅者。接下来就要研究一下mObserver的调用链。

   // RoomTrackingLiveData.java
    final Runnable mRefreshRunnable = new Runnable() {
        @WorkerThread
        @Override
        public void run() {
            // 1. 向InvalidationTracker注册一个观察者
            if (mRegisteredObserver.compareAndSet(false, true)) {
                mDatabase.getInvalidationTracker().addWeakObserver(mObserver);
            }
          ....
        }
    };
// InvalidationTracker.java

public void addWeakObserver(Observer observer) {
    // 2
    addObserver(new WeakObserver(this, observer));
}

public void addObserver(@NonNull Observer observer) {
    final String[] tableNames = resolveViews(observer.mTables);    
    int[] tableIds = new int[tableNames.length];     
    final int size = tableNames.length;      
    for (int i = 0; i < size; i++) {         
        Integer tableId = mTableIdLookup.get(tableNames[i].toLowerCase(Locale.US));         
        if (tableId == null) {             
            throw new IllegalArgumentException("There is no table with name " + tableNames[i]);         
        }         
        tableIds[i] = tableId;     
    }     
    ObserverWrapper wrapper = new ObserverWrapper(observer, tableIds, tableNames);     
    ObserverWrapper currentObserver;     
    synchronized (mObserverMap) {  
        // 3       
        currentObserver = mObserverMap.putIfAbsent(observer, wrapper);     
    }     
    if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) {         
        syncTriggers();     
    }
}
  • RoomTrackingLiveData创建mObserver对象,并一步步将mObserver进行包装,并存放在InvalidationTrackermObserverMap中。
  • 接下来则需要调查源码里在哪些情况下会遍历mObserverMap,并去调用mObserverMapitem的方法。
// InvalidationTracker.java

Runnable mRefreshRunnable = new Runnable() {
    @Override
    public void run() {
        ......
        if (invalidatedTableIds != null && !invalidatedTableIds.isEmpty()) {
            synchronized (mObserverMap) {
                // 1. 遍历了 mObserverMap
                for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {
                    entry.getValue().notifyByTableInvalidStatus(invalidatedTableIds);
                }
            }
        }
    }
    ......
};

public void notifyObserversByTableNames(String... tables) {
    synchronized (mObserverMap) {
        // 2. 遍历了 mObserverMap
        for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {
            if (!entry.getKey().isRemote()) {
                entry.getValue().notifyByTableNames(tables);
            }
        }
    }
}

由源码可知,共有两处遍历了mObserverMap,我们先研究一下mRefreshRunnable的调用链。

/**
 * Enqueues a task to refresh the list of updated tables.
 * <p>
 * This method is automatically called when {@link RoomDatabase#endTransaction()} is called but
 * if you have another connection to the database or directly use {@link
 * SupportSQLiteDatabase}, you may need to call this manually.
 */
public void refreshVersionsAsync() {
    // TODO we should consider doing this sync instead of async.
    if (mPendingRefresh.compareAndSet(false, true)) {
        if (mAutoCloser != null) {
            mAutoCloser.incrementCountAndEnsureDbIsOpen();
        }
        // 启动 mRefreshRunnable 任务
        mDatabase.getQueryExecutor().execute(mRefreshRunnable);
    }
}
  • 从方法说明上可以看出,当RoomDatabase#endTransaction()被调用时,会启动mRefreshRunnable任务。继续跟踪refreshVersionsAsync的调用链也能发现这点。
  • 接下来让我们回头研究一下room框架自动为开发者定义的dao类自动生成的xxxDAO_Impl.java。仔细研究一下各个方法的实现会发现,只要涉及到对数据库进行增、删、改的操作,都会调用到__db.endTransaction()。这里的__db就是RoomDatabase的对象。例如:
  @Override
  public void insert(final EntityNote note) {
    __db.assertNotSuspendingTransaction();
    __db.beginTransaction();
    try {
      __insertionAdapterOfEntityNote.insert(note);
      __db.setTransactionSuccessful();
    } finally {
      __db.endTransaction();
    }
  }

  @Override
  public int delete(final EntityNote note) {
    __db.assertNotSuspendingTransaction();
    int _total = 0;
    __db.beginTransaction();
    try {
      _total +=__deletionAdapterOfEntityNote.handle(note);
      __db.setTransactionSuccessful();
      return _total;
    } finally {
      __db.endTransaction();
    }
  }

  @Override
  public int update(final EntityNote note) {
    __db.assertNotSuspendingTransaction();
    int _total = 0;
    __db.beginTransaction();
    try {
      _total +=__updateAdapterOfEntityNote.handle(note);
      __db.setTransactionSuccessful();
      return _total;
    } finally {
      __db.endTransaction();
    }
  }
 1.3.1 __db.endTransaction()中调用internalEndTransaction()
    public void endTransaction() {
        if (mAutoCloser == null) {
            internalEndTransaction();
        } else {
            mAutoCloser.executeRefCountingFunction(db -> {
                internalEndTransaction();
                return null;
            });
        }
    }
1.3.2 mInvalidationTracker.refreshVersionsAsync()
    private void internalEndTransaction() {
        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();
        }
    }
 1.3.3 mDatabase.getQueryExecutor().execute(mRefreshRunnable)
    public void refreshVersionsAsync() {
        // TODO we should consider doing this sync instead of async.
        if (mPendingRefresh.compareAndSet(false, true)) {
            if (mAutoCloser != null) {
                // refreshVersionsAsync is called with the ref count incremented from
                // RoomDatabase, so the db can't be closed here, but we need to be sure that our
                // db isn't closed until refresh is completed. This increment call must be
                // matched with a corresponding call in mRefreshRunnable.
                mAutoCloser.incrementCountAndEnsureDbIsOpen();
            }
            mDatabase.getQueryExecutor().execute(mRefreshRunnable);
        }
    }
    Runnable mRefreshRunnable = new Runnable() {
        @Override
        public void run() {
            final Lock closeLock = mDatabase.getCloseLock();
            Set<Integer> invalidatedTableIds = null;
            closeLock.lock();
            try {

                if (!ensureInitialization()) {
                    return;
                }

                if (!mPendingRefresh.compareAndSet(true, false)) {
                    // no pending refresh
                    return;
                }

                if (mDatabase.inTransaction()) {
                    // current thread is in a transaction. when it ends, it will invoke
                    // refreshRunnable again. mPendingRefresh is left as false on purpose
                    // so that the last transaction can flip it on again.
                    return;
                }

                // This transaction has to be on the underlying DB rather than the RoomDatabase
                // in order to avoid a recursive loop after endTransaction.
                SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase();
                db.beginTransactionNonExclusive();
                try {
                    invalidatedTableIds = checkUpdatedTable();
                    db.setTransactionSuccessful();
                } finally {
                    db.endTransaction();
                }
            } catch (IllegalStateException | SQLiteException exception) {
                // may happen if db is closed. just log.
                Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?",
                        exception);
            } finally {
                closeLock.unlock();

                if (mAutoCloser != null) {
                    mAutoCloser.decrementCountAndScheduleClose();
                }
            }
            if (invalidatedTableIds != null && !invalidatedTableIds.isEmpty()) {
                synchronized (mObserverMap) {
                    for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {
                        entry.getValue().notifyByTableInvalidStatus(invalidatedTableIds);
                    }
                }
            }
        }

        private Set<Integer> checkUpdatedTable() {
            HashSet<Integer> invalidatedTableIds = new HashSet<>();
            Cursor cursor = mDatabase.query(new SimpleSQLiteQuery(SELECT_UPDATED_TABLES_SQL));
            //noinspection TryFinallyCanBeTryWithResources
            try {
                while (cursor.moveToNext()) {
                    final int tableId = cursor.getInt(0);
                    invalidatedTableIds.add(tableId);
                }
            } finally {
                cursor.close();
            }
            if (!invalidatedTableIds.isEmpty()) {
                mCleanupStatement.executeUpdateDelete();
            }
            return invalidatedTableIds;
        }
    };
1.3.4 entry.getValue().notifyByTableInvalidStatus(invalidatedTableIds)
     void notifyByTableInvalidStatus(Set<Integer> invalidatedTablesIds) {
            Set<String> invalidatedTables = null;
            final int size = mTableIds.length;
            for (int index = 0; index < size; index++) {
                final int tableId = mTableIds[index];
                if (invalidatedTablesIds.contains(tableId)) {
                    if (size == 1) {
                        // Optimization for a single-table observer
                        invalidatedTables = mSingleTableSet;
                    } else {
                        if (invalidatedTables == null) {
                            invalidatedTables = new HashSet<>(size);
                        }
                        invalidatedTables.add(mTableNames[index]);
                    }
                }
            }
            if (invalidatedTables != null) {
                mObserver.onInvalidated(invalidatedTables);
            }
        }
1.3.5 mObserver.onInvalidated(invalidatedTables)
    RoomTrackingLiveData(
            RoomDatabase database,
            InvalidationLiveDataContainer container,
            boolean inTransaction,
            Callable<T> computeFunction,
            String[] tableNames) {
        mDatabase = database;
        mInTransaction = inTransaction;
        mComputeFunction = computeFunction;
        mContainer = container;
        mObserver = new InvalidationTracker.Observer(tableNames) {
            @Override
            public void onInvalidated(@NonNull Set<String> tables) {
                ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
            }
        };
    }
1.3.6 ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable)
    final Runnable mInvalidationRunnable = new Runnable() {
        @MainThread
        @Override
        public void run() {
            boolean isActive = hasActiveObservers();
            if (mInvalid.compareAndSet(false, true)) {
                if (isActive) {
                    getQueryExecutor().execute(mRefreshRunnable);
                }
            }
        }
    }
 1.3.7 mRefreshRunnable.run  postValue(value)
   final Runnable mRefreshRunnable = new Runnable() {
        @WorkerThread
        @Override
        public void run() {
            // 向InvalidationTracker注册一个观察者
            if (mRegisteredObserver.compareAndSet(false, true)) {
                mDatabase.getInvalidationTracker().addWeakObserver(mObserver);
            }
            boolean computed;
            do {
                computed = false;
                // mComputing 初始值为 false
                if (mComputing.compareAndSet(false, true)) {
                    // as long as it is invalid, keep computing.
                    try {
                        T value = null;
                        // mInvalid初始值为 true
                        // 此while循环结束后,computed == false,mInvalid == false
                        while (mInvalid.compareAndSet(true, false)) {
                            computed = true;
                            try {
                                // 执行数据库操作方法,并返回结果
                                value = mComputeFunction.call();
                            } catch (Exception e) {
                                // 如果SQL语句执行有误,会非常粗暴的直接报错,
                                // liveData不能将错误状态上报给开发者。
                                throw new RuntimeException("Exception while computing database"
                                        + " live data.", e);
                            }
                        }
                        if (computed) {
                            // 向当前livedata的观察者们发送数据库查询结果
                            postValue(value);
                        }
                    } finally {
                        // release compute lock
                        mComputing.set(false);
                    }
                }
               
            } while (computed && mInvalid.get());
        }
    };
1.3.8 observer收到onChanged回调        

LiveData<List<EntityNote>> EntityNoteLiveData = AppDatabase.getInstance().noteDao().getAll()注册的RoomLiveData的observer会回调onChanged

// 继承AndroidViewModel,带有Application环境
public class NoteViewModel extends AndroidViewModel {
    private MediatorLiveData<List<EntityNote >> mMediatorLiveData;

    public NoteViewModel(@NonNull Application application) {
        super(application);
        mMediatorLiveData = new MediatorLiveData<>();
        LiveData<List<EntityNote>> EntityNoteLiveData = AppDatabase.getInstance().noteDao().getAll();
        mMediatorLiveData.addSource(EntityNoteLiveData, new Observer<List<EntityNote>>() {
            private List<EntityNote> mLastEntityNoteList;

            @Override
            public void onChanged(List<EntityNote> entityNotes) {
                if (mLastEntityNoteList == null) {
                    mLastEntityNoteList = entityNotes;
                    return;
                }
                if (entityNotes == null) {
                    setValue(new ArrayList<>());
                    return;
                }
                int lastSize = mLastEntityNoteList.size();
                int size = entityNotes.size();
                if (lastSize != size) {
                    setValue(entityNotes);
                    return;
                }
                for (int i = 0; i < size; i++) {
                    EntityNote lastNote = mLastEntityNoteList.get(i);
                    EntityNote note = entityNotes.get(i);
                    if (!isSameNote(lastNote, note)) {
                        setValue(entityNotes);
                        break;
                    }
                }
                // 没有变化不setValue不触发onChanged
                mLastEntityNoteList = entityNotes;
            }

            private void setValue(List<EntityNote> entityNotes) {
                mMediatorLiveData.setValue(entityNotes);
                mLastEntityNoteList = entityNotes;
            }

            private boolean isSameNote(EntityNote first, EntityNote second) {
                if (first == null || second == null) {
                    return false;
                }
                return first.uuid.equals(second.uuid) && first.title.equals(second.title)
                        && first.id == second.id && first.content.equals(second.content);
            }

        });
    }

    //查(所有)
    public MediatorLiveData<List<EntityNote>> getNoteListLiveData() {
        return mMediatorLiveData;
    }
}

 

1.4 总结:

  1. 数据库的增、删、改操作会调用RoomDatabase#endTransaction()
  2. RoomDatabase#endTransaction()会调用InvalidationTracker#refreshVersionsAsync();
  3. refreshVersionsAsync()会开启mRefreshRunnable任务。
  4. mRefreshRunnable里会遍历mObserverMap,并挨个调用其item的指定方法。
  5. RoomTrackingLiveData在构造函数里创建了mObserver对象,并将此对象放置于InvalidationTrackermObserverMap中。且此对象的方法就是用来唤醒RoomTrackingLiveDatamRefreshRunnable任务。还记得这个任务是干嘛的吗?这个任务就是根据RoomTrackingLiveData持有的数据库查询语句向数据库查询数据,并将查询结果上报给开发者指定的Observer

至此,RoomTrackingLiveData完美实现了数据库发生变化时,会主动将新的数据上报给开发者的功能。

二  Room是如何通知其他进程的订阅者

如果有两个进程同时关联了同一个数据库,如果一个进程对此数据库的数据进行改变,那么另一个进程的RoomTrackingLiveData依旧能感知到数据变化,这是怎么做到的呢?
还记得上面在调查InvalidationTrackermObserverMap时,发现有两个方法遍历了这个map吗。其中mRefreshRunnable已经分析过了,接下来分析另一个方法notifyObserversByTableNames

// InvalidationTracker.java

public void notifyObserversByTableNames(String... tables) {
    synchronized (mObserverMap) {
        //  遍历了 mObserverMap
        for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {
            if (!entry.getKey().isRemote()) {
                entry.getValue().notifyByTableNames(tables);
            }
        }
    }
}
// MultiInstanceInvalidationClient.java

final IMultiInstanceInvalidationCallback mCallback =
        new IMultiInstanceInvalidationCallback.Stub() {
            @Override
            public void onInvalidation(final String[] tables) {
                mExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        //1.调用了 nvalidationTracker#notifyObserversByTableNames()
                        mInvalidationTracker.notifyObserversByTableNames(tables);
                    }
                });
            }
        };

final Runnable mSetUpRunnable = new Runnable() {
    @Override
    public void run() {
        try {
            final IMultiInstanceInvalidationService service = mService;
            if (service != null) {
                //2. 向 service 注册 mCallback
                mClientId = service.registerCallback(mCallback, mName);
                mInvalidationTracker.addObserver(mObserver);
            }
        } catch (RemoteException e) {
            Log.w(Room.LOG_TAG, "Cannot register multi-instance invalidation callback", e);
        }
    }
};

final ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mService = IMultiInstanceInvalidationService.Stub.asInterface(service);
        // 3. 执行 mSetUpRunnable 任务 
        mExecutor.execute(mSetUpRunnable);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mExecutor.execute(mRemoveObserverRunnable);
        mService = null;
    }

};        
  1. 由上可见,在MultiInstanceInvalidationClient类里绑定了一个service,并向service注册mCallback。这个mCallback会通过InvalidationTracker#notifyObserversByTableNames()通知RoomTrackingLiveData该干活了(查询和上报数据库新值)。

看到IMultiInstanceInvalidationService.Stub可以大胆猜测这里涉及到了跨进程通信。
接下来研究MultiInstanceInvalidationService

// MultiInstanceInvalidationService.java

public class MultiInstanceInvalidationService extends Service {

    int mMaxClientId = 0;

    final HashMap<Integer, String> mClientNames = new HashMap<>();

    // 1. 可以理解成这是一个装载 callBack的集合
    final RemoteCallbackList<IMultiInstanceInvalidationCallback> mCallbackList =
            new RemoteCallbackList<IMultiInstanceInvalidationCallback>() {
                @Override
                public void onCallbackDied(IMultiInstanceInvalidationCallback callback,
                        Object cookie) {
                    mClientNames.remove((int) cookie);
                }
            };

    private final IMultiInstanceInvalidationService.Stub mBinder =
            new IMultiInstanceInvalidationService.Stub() {

                @Override
                public int registerCallback(IMultiInstanceInvalidationCallback callback,
                        String name) {
                    if (name == null) {
                        return 0;
                    }
                    synchronized (mCallbackList) {
                        int clientId = ++mMaxClientId;
                        // 2. 将 callback 放入 mCallbackList 集合中
                        if (mCallbackList.register(callback, clientId)) {
                            mClientNames.put(clientId, name);
                            return clientId;
                        } else {
                            --mMaxClientId;
                            return 0;
                        }
                    }
                }

                @Override
                public void unregisterCallback(IMultiInstanceInvalidationCallback callback,
                        int clientId) {
                    synchronized (mCallbackList) {
                        mCallbackList.unregister(callback);
                        mClientNames.remove(clientId);
                    }
                }

               
                @Override
                public void broadcastInvalidation(int clientId, String[] tables) {
                    synchronized (mCallbackList) {
                        String name = mClientNames.get(clientId);
                        if (name == null) {
                            Log.w(Room.LOG_TAG, "Remote invalidation client ID not registered");
                            return;
                        }
                        int count = mCallbackList.beginBroadcast();
                        try {
                            // 这个for循环,可以理解成取出mCallbackList集合中的所有callBack,
                            // 并调用各自的 onInvalidation方法。
                            for (int i = 0; i < count; i++) {
                                int targetClientId = (int) mCallbackList.getBroadcastCookie(i);
                                String targetName = mClientNames.get(targetClientId);
                                if (clientId == targetClientId // This is the caller itself.
                                        || !name.equals(targetName)) { // Not the same file.
                                    continue;
                                }
                                try {
                                    IMultiInstanceInvalidationCallback callback =
                                            mCallbackList.getBroadcastItem(i);
                                    callback.onInvalidation(tables);
                                } catch (RemoteException e) {
                                    Log.w(Room.LOG_TAG, "Error invoking a remote callback", e);
                                }
                            }
                        } finally {
                            mCallbackList.finishBroadcast();
                        }
                    }
                }
            };

    @Nullable
    @Override
    public IBinder onBind(@NonNull Intent intent) {
        return mBinder;
    }
}
  1. 由以上源码可以推断出这个service主要做了两件事:
    • 在内存中维护一个集合,这个集合装载的是所有client注册的callBack
    • 在合适的时机调用所有client注册的callBack。这个合适的时机,就是调用broadcastInvalidation()的时候。

回到MultiInstanceInvalidationClient,回想一下这个clientservice注册了个什么玩意。

// MultiInstanceInvalidationClient.java

final Runnable mSetUpRunnable = new Runnable() {
    @Override
    public void run() {
        try {
            final IMultiInstanceInvalidationService service = mService;
            if (service != null) {
                // 1. 向service注册mCallback
                mClientId = service.registerCallback(mCallback, mName);
                mInvalidationTracker.addObserver(mObserver);
            }
        } catch (RemoteException e) {
            Log.w(Room.LOG_TAG, "Cannot register multi-instance invalidation callback", e);
        }
    }
};

final IMultiInstanceInvalidationCallback mCallback =
        new IMultiInstanceInvalidationCallback.Stub() {
            @Override
            public void onInvalidation(final String[] tables) {
                mExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        // 2. 这个方法是干什么的来着?
                        //    是拜托InvalidationTracker通知RoomTrackingLiveData该干活了。
                        //    上文有介绍
                        mInvalidationTracker.notifyObserversByTableNames(tables);
                    }
                });
            }
        };

接下来追踪一下MultiInstanceInvalidationService#broadcastInvalidation()

// MultiInstanceInvalidationClient.java

MultiInstanceInvalidationClient(Context context, String name, Intent serviceIntent,
        InvalidationTracker invalidationTracker, Executor executor) {
    ......
    mObserver = new InvalidationTracker.Observer(tableNames.toArray(new String[0])) {
        @Override
        public void onInvalidated(@NonNull Set<String> tables) {
            if (mStopped.get()) {
                return;
            }
            try {
                final IMultiInstanceInvalidationService service = mService;
                if (service != null) {
                    // 1. 调用了MultiInstanceInvalidationService#broadcastInvalidation()
                    service.broadcastInvalidation(mClientId, tables.toArray(new String[0]));
                }
            } catch (RemoteException e) {
                Log.w(Room.LOG_TAG, "Cannot broadcast invalidation", e);
            }
        }

        @Override
        boolean isRemote() {
            return true;
        }
    };
    mAppContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
}

final Runnable mSetUpRunnable = new Runnable() {
    @Override
    public void run() {
        try {
            final IMultiInstanceInvalidationService service = mService;
            if (service != null) {
                mClientId = service.registerCallback(mCallback, mName);
                // 2. 将mObserver传递给InvalidationTracker
                mInvalidationTracker.addObserver(mObserver);
            }
        } catch (RemoteException e) {
            Log.w(Room.LOG_TAG, "Cannot register multi-instance invalidation callback", e);
        }
    }
};

看了以上2个步骤是不是似曾相识?还记得RoomTrackingLiveDatamObserver对象吗?和这里的套路是一模一样。接下来很明显,InvalidationTracker里面会有一个map来装载这个mObserver。然后会有两个方法去遍历这个map。其中一个Runnable方法会在调用数据库的增删改方法时触发,另一个方法notifyObserversByTableNames会在...会在...???
我不是在研究notifyObserversByTableNames的调用链吗?怎么又绕回来了?

这里理解起来有点绕,先明确一下前提:

  1. 针对不同的进程操作同一个数据库的场景,其实每一个进程都会拥有自己独立的RoomDatabase实例。相应的MultiInstanceInvalidationClientInvalidationTrackerRoomTrackingLiveData都是相互独立的。
  2. 只有MultiInstanceInvalidationService是共同的实例。而这个共同的实例,是保证不同进程能相互感知到数据库操作的关键。
  3. InvalidationTrackermRefreshRunnable是在单进程中调用的。
  4. InvalidationTrackernotifyObserversByTableNames是用于跨进程调用的。

下面重新捋一下思路。首先假设现在有两个进程会操作同一个数据库。那么这两个进程都会各自拥有一套自己的独立对象。即都会做一下事情:

  1. 创建RoomTrackingLiveData对象,并将mObserver委托给InvalidationTracker管理。
  2. RoomTrackingLiveData里的mRefreshRunnable会在被唤醒时重新查询数据库,并上报结果。
  3. 创建MultiInstanceInvalidationClient对象,并与唯一的MultiInstanceInvalidationService进行绑定,并将callBack委托给service管理。
  4. callBack里会调用InvalidationTracker#notifyObserversByTableNames()
  5. MultiInstanceInvalidationClient对象将mObserver委托给InvalidationTracker管理。
  6. MultiInstanceInvalidationClientmObserver会通知所有与MultiInstanceInvalidationService进行绑定的MultiInstanceInvalidationClient,告知它们数据库有变化。

针对进程1,我们重点关注3、4、5、6。针对进程2,我们重点关注1、2。现在开始发车:

  1. 当前用户在进程1操作了数据库的修改操作,那么就会触发进程1的RoomDatabase#endTransaction(),
    进而触发了InvalidationTracker#mRefreshRunnable 任务,遍历InvalidationTracker#mObserverMap(在上一节有相关介绍)。此mObserverMap里存在一个MultiInstanceInvalidationClient添加进来的mObserver(上面第5点有提到)。
  2. 进程1的MultiInstanceInvalidationClientmObserver会调用MultiInstanceInvalidationService#broadcastInvalidation()
  3. MultiInstanceInvalidationService会遍历和执行所有MultiInstanceInvalidationClient注册的callback。这其中的一个callback就是进程2的MultiInstanceInvalidationClient注册的(上面第5点有提到)。
  4. 进程2的callback会调用进程2的InvalidationTracker#notifyObserversByTableNames()。再回忆一下这个notifyObserversByTableNames()是干嘛的?没错,就是我们研究的第二个遍历InvalidationTrackermObserverMap的方法。
  5. 既然进程2已经遍历了mObserverMap,那么势必会让进程2的RoomTrackingLiveData干活(查询数据库,上报新数据)。

至此,room框架完成了一次完美的跨进程通讯。

要想当前的RoomDataBase具有跨进程通讯的能力,需要在构建databaseBuilder的时候调用enableMultiInstanceInvalidation()。例如:

@Database(entities = {EntityNote.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
    private static final String DB_NAME = "note.db";
    private static volatile AppDatabase instance;//创建单例

    public static synchronized AppDatabase getInstance() {
        if (instance == null) {
            instance = create();
        }
        return instance;
    }

    /**
     * 创建数据库
     */
    private static AppDatabase create() {
        return Room.databaseBuilder(MyApplication.getInstance(), AppDatabase.class, DB_NAME)
                .allowMainThreadQueries()
                .fallbackToDestructiveMigration()
                .enableMultiInstanceInvalidation() // 跨进程通讯的能力
                .build();
    }

    public abstract NoteDao noteDao();
}
 

从源码来看,RoomDataBase正是通过此方法来间接创建MultiInstanceInvalidationClient对象,并与MultiInstanceInvalidationService建立绑定关系。

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RoomLiveData、ViewModel结合是为了实现数据持久化、数据观察与界面更新的最佳实践。 Room是Google为Android提供的一个持久化数据库解决方案,它简化了数据库操作并提供了编译时检查,同时支持在后台线程中进行数据库访问。LiveData是一种可观察的数据容器,它可以感知生命周期,并在数据变化时自动更新。ViewModel则负责管理界面相关的数据,并且与Activity或Fragment的生命周期无关,避免了数据丢失或重复请求的问题。 将RoomLiveData结合使用,可以将Room中的数据变化LiveData的形式暴露出来。这样,在数据库发生变化时,LiveData会自动通知相关观察者进行界面更新。LiveData还具有自动清理机制,当观察者的生命周期结束时会自动停止数据的更新,避免了内存泄漏问题。 而结合ViewModel可以更好地管理界面相关的数据。ViewModel可以在配置改变时(如屏幕旋转)保留数据,并且避免了异步任务导致的内存泄漏。通过在ViewModel中缓存LiveData对象,可以在Activity或Fragment恢复时继续观察数据变化,并保持界面更新的一致性。 总结而言,RoomLiveData、ViewModel的结合可以实现一个可靠、高效、响应式的数据处理框架,使得数据的持久化、观察与界面更新变得十分简单。这样的架构设计也有利于代码的维护与扩展,提高了开发效率与用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值