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

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

1.1  LiveData是如何创建的

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

NoteDao 举例:

@Dao
public interface NoteDao {@Query("select * from note")LiveData<List<EntityNote>> getAll();@Updateint update(EntityNote note);@Deleteint delete(EntityNote note);@Insertvoid 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) {@Overridepublic String createQuery() {return "INSERT OR ABORT INTO `note` (`id`,`uuid`,`title`,`content`,`searchContent`) VALUES (nullif(?, 0),?,?,?,?)";}@Overridepublic 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) {@Overridepublic String createQuery() {return "DELETE FROM `note` WHERE `id` = ?";}@Overridepublic void bind(SupportSQLiteStatement stmt, EntityNote value) {stmt.bindLong(1, value.id);}};this.__updateAdapterOfEntityNote = new EntityDeletionOrUpdateAdapter<EntityNote>(__db) {@Overridepublic String createQuery() {return "UPDATE OR ABORT `note` SET `id` = ?,`uuid` = ?,`title` = ?,`content` = ?,`searchContent` = ? WHERE `id` = ?";}@Overridepublic 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);}};}@Overridepublic void insert(final EntityNote note) {__db.assertNotSuspendingTransaction();__db.beginTransaction();try {__insertionAdapterOfEntityNote.insert(note);__db.setTransactionSuccessful();} finally {__db.endTransaction();}}@Overridepublic int delete(final EntityNote note) {__db.assertNotSuspendingTransaction();int _total = 0;__db.beginTransaction();try {_total +=__deletionAdapterOfEntityNote.handle(note);__db.setTransactionSuccessful();return _total;} finally {__db.endTransaction();}}@Overridepublic int update(final EntityNote note) {__db.assertNotSuspendingTransaction();int _total = 0;__db.beginTransaction();try {_total +=__updateAdapterOfEntityNote.handle(note);__db.setTransactionSuccessful();return _total;} finally {__db.endTransaction();}}@Overridepublic 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>>() {@Overridepublic 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();}}@Overrideprotected void finalize() {_statement.release();}});}public static List<Class<?>> getRequiredConverters() {return Collections.emptyList();}
}

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

// InvalidationTracker.javapublic <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@Overridepublic void run() {// 向InvalidationTracker注册一个观察者if (mRegisteredObserver.compareAndSet(false, true)) {mDatabase.getInvalidationTracker().addWeakObserver(mObserver);}boolean computed;do {computed = false;// mComputing 初始值为 falseif (mComputing.compareAndSet(false, true)) {// as long as it is invalid, keep computing.try {T value = null;// mInvalid初始值为 true// 此while循环结束后,computed == false,mInvalid == falsewhile (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 lockmComputing.set(false);}}} while (computed && mInvalid.get());}};@SuppressWarnings("WeakerAccess")final Runnable mInvalidationRunnable = new Runnable() {@MainThread@Overridepublic 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) {@Overridepublic void onInvalidated(@NonNull Set<String> tables) {ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);}};}@Overrideprotected void onActive() {super.onActive();mContainer.onActive(this);getQueryExecutor().execute(mRefreshRunnable);}@Overrideprotected 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.javafinal Runnable mRefreshRunnable = new Runnable() {@WorkerThread@Overridepublic void run() {// 1. 向InvalidationTracker注册一个观察者if (mRegisteredObserver.compareAndSet(false, true)) {mDatabase.getInvalidationTracker().addWeakObserver(mObserver);}....}};
// InvalidationTracker.javapublic void addWeakObserver(Observer observer) {// 2addObserver(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.javaRunnable mRefreshRunnable = new Runnable() {@Overridepublic void run() {......if (invalidatedTableIds != null && !invalidatedTableIds.isEmpty()) {synchronized (mObserverMap) {// 1. 遍历了 mObserverMapfor (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {entry.getValue().notifyByTableInvalidStatus(invalidatedTableIds);}}}}......
};public void notifyObserversByTableNames(String... tables) {synchronized (mObserverMap) {// 2. 遍历了 mObserverMapfor (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的对象。例如:
  @Overridepublic void insert(final EntityNote note) {__db.assertNotSuspendingTransaction();__db.beginTransaction();try {__insertionAdapterOfEntityNote.insert(note);__db.setTransactionSuccessful();} finally {__db.endTransaction();}}@Overridepublic int delete(final EntityNote note) {__db.assertNotSuspendingTransaction();int _total = 0;__db.beginTransaction();try {_total +=__deletionAdapterOfEntityNote.handle(note);__db.setTransactionSuccessful();return _total;} finally {__db.endTransaction();}}@Overridepublic 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() {@Overridepublic void run() {final Lock closeLock = mDatabase.getCloseLock();Set<Integer> invalidatedTableIds = null;closeLock.lock();try {if (!ensureInitialization()) {return;}if (!mPendingRefresh.compareAndSet(true, false)) {// no pending refreshreturn;}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 TryFinallyCanBeTryWithResourcestry {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 observerinvalidatedTables = 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) {@Overridepublic void onInvalidated(@NonNull Set<String> tables) {ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);}};}
1.3.6 ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable)
    final Runnable mInvalidationRunnable = new Runnable() {@MainThread@Overridepublic 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@Overridepublic void run() {// 向InvalidationTracker注册一个观察者if (mRegisteredObserver.compareAndSet(false, true)) {mDatabase.getInvalidationTracker().addWeakObserver(mObserver);}boolean computed;do {computed = false;// mComputing 初始值为 falseif (mComputing.compareAndSet(false, true)) {// as long as it is invalid, keep computing.try {T value = null;// mInvalid初始值为 true// 此while循环结束后,computed == false,mInvalid == falsewhile (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 lockmComputing.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;@Overridepublic 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不触发onChangedmLastEntityNoteList = 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.javapublic void notifyObserversByTableNames(String... tables) {synchronized (mObserverMap) {//  遍历了 mObserverMapfor (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {if (!entry.getKey().isRemote()) {entry.getValue().notifyByTableNames(tables);}}}
}
// MultiInstanceInvalidationClient.javafinal IMultiInstanceInvalidationCallback mCallback =new IMultiInstanceInvalidationCallback.Stub() {@Overridepublic void onInvalidation(final String[] tables) {mExecutor.execute(new Runnable() {@Overridepublic void run() {//1.调用了 nvalidationTracker#notifyObserversByTableNames()mInvalidationTracker.notifyObserversByTableNames(tables);}});}};final Runnable mSetUpRunnable = new Runnable() {@Overridepublic void run() {try {final IMultiInstanceInvalidationService service = mService;if (service != null) {//2. 向 service 注册 mCallbackmClientId = 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() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {mService = IMultiInstanceInvalidationService.Stub.asInterface(service);// 3. 执行 mSetUpRunnable 任务 mExecutor.execute(mSetUpRunnable);}@Overridepublic void onServiceDisconnected(ComponentName name) {mExecutor.execute(mRemoveObserverRunnable);mService = null;}};        
  1. 由上可见,在MultiInstanceInvalidationClient类里绑定了一个service,并向service注册mCallback。这个mCallback会通过InvalidationTracker#notifyObserversByTableNames()通知RoomTrackingLiveData该干活了(查询和上报数据库新值)。

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

// MultiInstanceInvalidationService.javapublic class MultiInstanceInvalidationService extends Service {int mMaxClientId = 0;final HashMap<Integer, String> mClientNames = new HashMap<>();// 1. 可以理解成这是一个装载 callBack的集合final RemoteCallbackList<IMultiInstanceInvalidationCallback> mCallbackList =new RemoteCallbackList<IMultiInstanceInvalidationCallback>() {@Overridepublic void onCallbackDied(IMultiInstanceInvalidationCallback callback,Object cookie) {mClientNames.remove((int) cookie);}};private final IMultiInstanceInvalidationService.Stub mBinder =new IMultiInstanceInvalidationService.Stub() {@Overridepublic 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;}}}@Overridepublic void unregisterCallback(IMultiInstanceInvalidationCallback callback,int clientId) {synchronized (mCallbackList) {mCallbackList.unregister(callback);mClientNames.remove(clientId);}}@Overridepublic 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@Overridepublic IBinder onBind(@NonNull Intent intent) {return mBinder;}
}
  1. 由以上源码可以推断出这个service主要做了两件事:
    • 在内存中维护一个集合,这个集合装载的是所有client注册的callBack
    • 在合适的时机调用所有client注册的callBack。这个合适的时机,就是调用broadcastInvalidation()的时候。

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

// MultiInstanceInvalidationClient.javafinal Runnable mSetUpRunnable = new Runnable() {@Overridepublic void run() {try {final IMultiInstanceInvalidationService service = mService;if (service != null) {// 1. 向service注册mCallbackmClientId = 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() {@Overridepublic void onInvalidation(final String[] tables) {mExecutor.execute(new Runnable() {@Overridepublic void run() {// 2. 这个方法是干什么的来着?//    是拜托InvalidationTracker通知RoomTrackingLiveData该干活了。//    上文有介绍mInvalidationTracker.notifyObserversByTableNames(tables);}});}};

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

// MultiInstanceInvalidationClient.javaMultiInstanceInvalidationClient(Context context, String name, Intent serviceIntent,InvalidationTracker invalidationTracker, Executor executor) {......mObserver = new InvalidationTracker.Observer(tableNames.toArray(new String[0])) {@Overridepublic 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);}}@Overrideboolean isRemote() {return true;}};mAppContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
}final Runnable mSetUpRunnable = new Runnable() {@Overridepublic void run() {try {final IMultiInstanceInvalidationService service = mService;if (service != null) {mClientId = service.registerCallback(mCallback, mName);// 2. 将mObserver传递给InvalidationTrackermInvalidationTracker.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建立绑定关系。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/848944.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【贡献度分析(帕累托图)】

文章目录 前言一、贡献度分析是什么&#xff1f;二、使用步骤1. 准备数据2. 排序数据3. 绘制帕累托图4. 分析结果5. 实际应用 三、示例代码 前言 贡献度分析也称为帕累托分析。它可以帮助我们理解数据集中各个因素对整体影响的程度&#xff0c;从而优先处理最重要的因素&#…

代码随想录算法训练营第四十九天| 139.单词拆分、背包问题总结

139.单词拆分 题目链接&#xff1a;139.单词拆分 文档讲解&#xff1a;代码随想录/单词拆分 视频讲解&#xff1a;视频讲解-单词拆分 状态&#xff1a;已完成&#xff08;0遍&#xff09; 解题过程 这几天博主忙着面试和入职&#xff0c;一晃已经周四了&#xff0c;这个礼拜…

大模型微调工具LLaMA-Factory docker安装、大模型lora微调训练

参考: https://github.com/hiyouga/LLaMA-Factory 报错解决: 1)Docker 构建报错 RuntimeError: can’t start new thread: https://github.com/hiyouga/LLaMA-Factory/issues/3859 修改后的Dockerfile: FROM nvcr.io/nvidia/pytorch:24.01-py3WORKDIR /appCOPY requirem…

oracle数据库通过impdp导入数据时提示,ORA-31684:对象类型用户xxx已存在,和ORA-39151:表xxx存在的解决办法

前提条件&#xff1a;首先备份原数据库中此用户对应的schemas 比如名为cams_wf的schemas 以便出了问题后还可以恢复原数据。 解决办法一、 通过命令或者数据库管理工具删除掉此schemas下的所有表&#xff0c;然后在impdp中加入ignorey 来忽略ORA-31684&#xff1a;对象类型用…

Python logging 模块详解

Python 的 logging 模块提供了一个强大而灵活的日志系统。它是 Python 标准库的一部分&#xff0c;因此可以在任何 Python 程序中使用。logging 模块提供了许多有用的功能&#xff0c;包括日志消息的级别设置、日志消息的格式设置、将日志消息输出到不同的目标&#xff0c;以及…

深度解读ChatGPT

技术基础&#xff1a; ChatGPT是一种基于人工智能技术的自然语言处理工具&#xff0c;特别是自然语言生成&#xff08;NLG&#xff09;模型。它采用了Transformer架构&#xff0c;这是一种深度学习模型&#xff0c;特别适用于处理序列数据&#xff0c;如自然语言文本。 工作原理…

UE5刷植物悬空了

UE5系列文章目录 文章目录 UE5系列文章目录前言一、解决办法 前言 在Unreal Engine5.3中使用植物模式刷各种植物时&#xff0c;有时会发现有的植物要么悬空&#xff0c;要不有刷不上地板的情况。而且悬空的植物还不能接触到地面&#xff0c;感觉很奇怪&#xff0c;就像下图所示…

mmdetection的生物图像实例分割三:自定义数据集的测试与分析

mmdetection的生物图像实例分割全流程记录 第三章 自定义数据集的测试、重建与分析 文章目录 mmdetection的生物图像实例分割全流程记录前言一、测试集的推理1.模型测试2.测试数据解析 二、测试结果的数据整合三、生物结构的重建效果 前言 mmdetection是一个比较容易入门且上…

Kotlin 引用(双冒号::)

文章目录 双冒号::引用函数普通函数成员函数类构造函数 引用变量&#xff08;很少用&#xff09;普通变量成员变量 双冒号:: Kotlin 中可以使用双冒号::对某一变量、函数进行引用。 Note&#xff1a;MyClass::class可用于获取KClass<MyClass>&#xff0c;此时的双冒号::…

c time(NULL) time(time_t *p) 区别

chatgpt 的回答&#xff1a; time(NULL) 和 time(time_t *p) 都是用于获取当前系统时间的函数&#xff0c;但它们的用法略有不同。 time(NULL)&#xff1a; 这是 time 函数的简化版本&#xff0c;用于获取当前的系统时间&#xff0c;返回的结果是自 "Epoch"&#xf…

【研发日记】Matlab/Simulink软件优化(二)——通信负载柔性均衡算法

文章目录 前言 背景介绍 初始代码 优化代码 分析和应用 总结 前言 见《【研发日记】Matlab/Simulink软件优化(一)——动态内存负荷压缩》 背景介绍 在一个嵌入式软件开发项目中&#xff0c;需要设计一个ECU节点的CAN网路数据发送&#xff0c;需求是在500k的通信波特率上&a…

机器人舵机:关键要素解析与选择指南

在机器人技术日新月异的今天&#xff0c;舵机作为机器人的核心部件之一&#xff0c;扮演着至关重要的角色。它的性能直接关系到机器人的运动控制、稳定性以及精度等方面。那么&#xff0c;在选择和使用机器人舵机时&#xff0c;我们需要关注哪些关键要素呢&#xff1f;本文将为…

使用Vue.js将form表单传递到后端

一.form表单 <form submit.prevent"submitForm"></form> form表单像这样写出来&#xff0c;然后把需要用户填写的内容写在form表单内。 二.表单内数据绑定 <div class"input-container"><div style"margin-left: 9px;"&…

Gradle和Maven都是广泛使用的项目自动化构建工具

Gradle和Maven都是广泛使用的项目自动化构建工具&#xff0c;但它们在多个方面存在差异。以下是关于Gradle和Maven的详细对比&#xff1a; 一、构建脚本语言 Maven&#xff1a;使用XML作为构建脚本语言。XML的语法较为繁琐&#xff0c;不够灵活&#xff0c;对于复杂的构建逻辑…

【FPGA约束】如何确定FPGA和SDI驱动芯片之间io的时序约束值

确定FPGA和SDI&#xff08;Serial Digital Interface&#xff09;驱动芯片之间的I/O时序约束值&#xff0c;需要考虑多个因素&#xff0c;包括信号的传输特性、FPGA的I/O标准、以及SDI接口规范。以下是一些步骤和考虑因素&#xff1a; 理解SDI接口规范&#xff1a;首先&#xf…

WALT算法简介

WALT(Windows-Assist Load Tracing)算法是由Qcom开发&#xff0c; 通过把时间划分为窗口&#xff0c;对 task运行时间和CPU负载进行跟踪计算的方法。为任务调度、迁移、负载均衡及CPU调频 提供输入。 WALT相对PELT算法&#xff0c;更能及时反映负载变化&#xff0c; 更适用于…

String类知识

目录 一、String存在意义 二、字符串为何不可变 三、String类常用方法 1、字符串构造 2、String对象的比较 3、字符串查找 4、转化 &#xff08;1&#xff09;数值和字符转化 &#xff08;2&#xff09;大小写转换 &#xff08;3&#xff09;字符串转数组 &#xff08;4&…

系统架构设计师【补充知识】: 应用数学 (核心总结)

24.1 图论之最小生成树 (1)定义: 在连通的带权图的所有生成树中&#xff0c;权值和最小的那棵生成树(包含图中所有顶点的树)&#xff0c;称作最小生成树。 (2)针对问题: 带权图的最短路径问题。 (3)最小生成树的解法有普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法&#xff0c;我…

重复文件怎么查找并清理,试试这5个文件去重方法(新)

重复文件怎么查找并清理&#xff1f;日常工作中&#xff0c;我们使用电脑的时间长了&#xff0c;都会累积大量好的文件&#xff0c;这其中难免会出现重复文件。这些重复文件不仅占用了电脑磁盘空间&#xff0c;还会降低电脑性能。因此&#xff0c;我们必须定期对重复文件查找出…

2020年09月C语言二级真题

目录 单词倒排 题目描述 样例 细菌的繁殖与扩散 题目描述 样例 高精度加法 题目描述 样例 单词倒排 题目描述 编写程序&#xff0c;读入一行英文(只包含字母和空格&#xff0c;单词间以单个空格分隔)&#xff0c;将所有单词的顺序倒排并输出&#xff0c;依然以单个空格…