Room是怎样和LiveData结合使用的?(源码分析)
前言
之前写项目的时候,对于数据库的操作不是特别多,能避免就尽量避免,并且一直想不到比较好的方法去组织网络数据、本地数据的逻辑。所以在最近的面试中时,问及项目中的数据库实现,以及比较好用的数据库的框架及其实现原理时,我就只答道之前在《第一行代码》中看到了的LitePal,但源码就...所以这次来恶补一次数据库。几经搜索,云比较,比较青睐官方Jetpack组件中的Room。
Room简介
Room框架是使用生成代码的方式在编译时生成CRUD的代码,因此性能是远远好过通过反射实现的ORM框架。但是事实上,Room最吸引我的地方不止是性能,Room对架构组件(LiveData)、RxJava等流行框架做了适配。例如,Room中的查询操作可以返回一个LiveData,并且,每一次RUD操作,都会更新LiveData。这可以大大简化本地、内存、网络多级缓存的实现,具体官方也给出了一系列Demo,并且随时都在随着框架或者根据PR更新,强烈推荐研究这些Demo!
注
本文主要是对Room中与LiveData的联动作出分析,阅读本文前建议先熟悉Room的基本使用,建议看一下与LiveData配合使用的Demo。
正文
创建相关类
AppDatabase.kt
@Database(entities = [Book::class], version = 1)
abstract class AppDatabase : RoomDatabase() {abstract fun bookDao(): BookDao
}
复制代码
Book.kt
@Dao
interface BookDao {@Insertfun insert(book: Book): Long@Deletefun delete(book: Book)@Query("select * from book where id = :id")fun queryById(id: Long): LiveData<Book>
}
复制代码
使用数据库:
val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "test.db").build()db.bookDao().queryById(1).observe(this, Observer {// do something when book update})
复制代码
这样在Observer里面就可以接收到任何时候数据库id=1的数据修改操作了。
生成代码并分析
Build -> Make Project 编译,会生成Room相关代码,如果是kapt的话,生成代码目录应该是{项目目录}/app/build/generated/source/kapt/debug/{包路径}/下。 我们可以看到生成了AppDatabase_Impl和BookDao_Impl两个代码文件,分别对应前面贴出来的AppDatabase的实现类和BookDao的实现类。
AppDatabase_Impl则是表的创建、删除相关代码,Dao则是具体表的CRUD操作。这里我们重点关系生成的查询方法。 BookDao_Impl#
@Override
public LiveData<Book> queryById(final long id) {final String _sql = "select * from book where id = ?";final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);int _argIndex = 1;_statement.bindLong(_argIndex, id);return __db.getInvalidationTracker().createLiveData(new String[]{"book"}, new Callable<Book>() {@Overridepublic Book call() throws Exception {final Cursor _cursor = DBUtil.query(__db, _statement, false);try {final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");final int _cursorIndexOfAuthor = CursorUtil.getColumnIndexOrThrow(_cursor, "author");final int _cursorIndexOfPrice = CursorUtil.getColumnIndexOrThrow(_cursor, "price");final Book _result;if (_cursor.moveToFirst()) {final long _tmpId;_tmpId = _cursor.getLong(_cursorIndexOfId);final String _tmpName;_tmpName = _cursor.getString(_cursorIndexOfName);final String _tmpAuthor;_tmpAuthor = _cursor.getString(_cursorIndexOfAuthor);final float _tmpPrice;_tmpPrice = _cursor.getFloat(_cursorIndexOfPrice);_result = new Book(_tmpId, _tmpName, _tmpAuthor, _tmpPrice);} else {_result = null;}return _result;} finally {_cursor.close();}}@Overrideprotected void finalize() {_statement.release();}});
}
复制代码
注意这一行
return __db.getInvalidationTracker().createLiveData(...);
复制代码
我们跟进去,最终创建的是一个RoomTrackingLiveData,是一个继承了LiveData的类。下面是它的构造方法。从构造方法来看,比较可疑的对象的是InvalidationTracker.Observer这个类,并且实现十有八九是观察者模式。而最后的回调也多半是onInvalidated方法。
@SuppressLint("RestrictedApi")
RoomTrackingLiveData(RoomDatabase database,InvalidationLiveDataContainer container,Callable<T> computeFunction,String[] tableNames) {mDatabase = database;mComputeFunction = computeFunction;mContainer = container;mObserver = new InvalidationTracker.Observer(tableNames) {@Overridepublic void onInvalidated(@NonNull Set<String> tables) {ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);}};
}
复制代码
而在RoomTrackingLiveData中,重写了onActive方法。其中mContainer是InvalidationLiveDataContainer,文档上有写仅仅是维护LiveData的强引用,防止正在使用的LiveData被回收,跟本文目标没关系,可忽略。而后面的就有意思了,通过Excutor执行了一个任务,所以,我们来看一下这个任务把。
@Override
protected void onActive() {super.onActive();mContainer.onActive(this);mDatabase.getQueryExecutor().execute(mRefreshRunnable);
}
复制代码
mRefreshRunnable#run()
// mRegisteredObserver是否注册的标志
if (mRegisteredObserver.compareAndSet(false, true)) {mDatabase.getInvalidationTracker().addWeakObserver(mObserver);
}
boolean computed;
do {computed = false;if (mComputing.compareAndSet(false, true)) {try {T value = null;while (mInvalid.compareAndSet(true, false)) {computed = true;try {// Dao实现类中返回LiveData时传入的一个参数,用于查询,并将数据组装成一个实体类value = mComputeFunction.call();} catch (Exception e) {throw new RuntimeException("Exception while computing database"+ " live data.", e);}}if (computed) {postValue(value);}} finally {mComputing.set(false);}}
} while (computed && mInvalid.get());
复制代码
这段代码后段通过CAS去完成一次数据库的查询,组装成实体类并postValue,即更新LiveData。 注意到这个代码前段调用了InvalidationTracker的addWeakObserver,这个方法就应该就是订阅了。
InvalidationTracker#addWeakObserver
public void addWeakObserver(Observer observer) {addObserver(new WeakObserver(this, observer));
}
复制代码
InvalidationTracker#addObserver
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) {currentObserver = mObserverMap.putIfAbsent(observer, wrapper);}if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) {syncTriggers();}
}
复制代码
InvalidationTracker$WeakObserver
static class WeakObserver extends Observer {final InvalidationTracker mTracker;final WeakReference<Observer> mDelegateRef;WeakObserver(InvalidationTracker tracker, Observer delegate) {super(delegate.mTables);mTracker = tracker;mDelegateRef = new WeakReference<>(delegate);}@Overridepublic void onInvalidated(@NonNull Set<String> tables) {final Observer observer = mDelegateRef.get();if (observer == null) {mTracker.removeObserver(this);} else {observer.onInvalidated(tables);}}
}
复制代码
可以看到,WeakObserver就是对Observer一个弱引用的包装。而在addObserver中,根据observer中tableNames,对更新了InvalidationTracker的订阅记录。添加成功后,最后会调用onAdded。
boolean onAdded(int... tableIds) {boolean needTriggerSync = false;synchronized (this) {for (int tableId : tableIds) {final long prevObserverCount = mTableObservers[tableId];mTableObservers[tableId] = prevObserverCount + 1;if (prevObserverCount == 0) {mNeedsSync = true;needTriggerSync = true;}}}return needTriggerSync;
}
复制代码
这里mTableObservers是对每个table的observer进行计数。为什么要计数呢?我们接着看。在发现了订阅数从0->1的table时,这个方法会返回true,如果它返回true,会执行syncTriggers()方法,经过调用会执行这一段代码:
final int[] tablesToSync = mObservedTableTracker.getTablesToSync();
if (tablesToSync == null) {return;
}
final int limit = tablesToSync.length;
try {database.beginTransaction();for (int tableId = 0; tableId < limit; tableId++) {switch (tablesToSync[tableId]) {case ObservedTableTracker.ADD:startTrackingTable(database, tableId);break;case ObservedTableTracker.REMOVE:stopTrackingTable(database, tableId);break;}}database.setTransactionSuccessful();
} finally {database.endTransaction();
}
复制代码
InvalidationTracker#getTablesToSync()
int[] getTablesToSync() {synchronized (this) {if (!mNeedsSync || mPendingSync) {return null;}final int tableCount = mTableObservers.length;for (int i = 0; i < tableCount; i++) {final boolean newState = mTableObservers[i] > 0;if (newState != mTriggerStates[i]) {mTriggerStateChanges[i] = newState ? ADD : REMOVE;} else {mTriggerStateChanges[i] = NO_OP;}mTriggerStates[i] = newState;}mPendingSync = true;mNeedsSync = false;return mTriggerStateChanges;}
}
复制代码
这个getTablesToSync方法很短,但这里就体现了observer计数的作用,它遍历这个表,找出计数与之前不一样的,如果由一个大于0的数变为->0,表明现在没有observer订阅它,返回REMOVE,0->n,返回ADD,否则NO_OP。对于返回ADD的表,就应该是会监听变化的表了。它会执行startTrackingTable方法。
private void startTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {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 INSERT OR REPLACE INTO ").append(UPDATE_TABLE_NAME).append(" VALUES(null, ").append(tableId).append("); END");writableDb.execSQL(stringBuilder.toString());}
}
复制代码
到这里我们就很清楚了:实现监听修改的方法是触发器。 (不过我之前仅仅是听说过触发器,很少用过,如果不了解,这里有一份简易的教程)。而触发器关心的操作是这一些:
private static final String[] TRIGGERS = new String[]{"UPDATE", "DELETE", "INSERT"};
复制代码
对应着更新、删除、插入。当有这些操作时,根据上述触发器语句,会更新一个由InvalidationTracker维护的表"UPDATE_TABLE_NAME"。 InvalidationTracker#UPDATE_TABLE_NAME
private static final String UPDATE_TABLE_NAME = "room_table_modification_log";
复制代码
InvalidationTracker#internalInit
void internalInit(SupportSQLiteDatabase database) {synchronized (this) {if (mInitialized) {Log.e(Room.LOG_TAG, "Invalidation tracker is initialized twice :/.");return;}database.beginTransaction();try {database.execSQL("PRAGMA temp_store = MEMORY;");database.execSQL("PRAGMA recursive_triggers='ON';");database.execSQL(CREATE_TRACKING_TABLE_SQL);database.setTransactionSuccessful();} finally {database.endTransaction();}syncTriggers(database);mCleanupStatement = database.compileStatement(RESET_UPDATED_TABLES_SQL);mInitialized = true;}
}
复制代码
注意到表中有这样一列:
INVALIDATED_COLUMN_NAME + " INTEGER NOT NULL DEFAULT 0
复制代码
在触发器设置的是更新操作时会被设置为1。所以,应该就是检验这个值来判断表是否有更新。那么是哪里进行判断呢?我们可以从一个更新操作开始找,例如BookDao_Impl#insert()
@Override
public long insert(final Book book) {__db.beginTransaction();try {long _result = __insertionAdapterOfBook.insertAndReturnId(book);__db.setTransactionSuccessful();return _result;} finally {__db.endTransaction();}
}
复制代码
最后发现在endTransaction中调用了InvalidationTracker的refreshVersionsAsync方法。而在这个方法中,最终会运行InvalidationTracker的mRefreshRunnable对象的run方法。(注意,和上文的mRefreshRunnbale属于不同类,不是同一个对象。) RoomDatabase#endTransaction()
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();}
}
复制代码
InvalidationTracker#mRefreshRunnable#run()
inal Lock closeLock = mDatabase.getCloseLock();
boolean hasUpdatedTable = false;
try {... 省略if (mDatabase.mWriteAheadLoggingEnabled) {// 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.beginTransaction();try {hasUpdatedTable = checkUpdatedTable();db.setTransactionSuccessful();} finally {db.endTransaction();}} else {hasUpdatedTable = checkUpdatedTable();}
} 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 (hasUpdatedTable) {// 分发给Observer,最终会更新LiveDatasynchronized (mObserverMap) {for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {entry.getValue().notifyByTableVersions(mTableInvalidStatus);}}// Reset invalidated status flags.mTableInvalidStatus.clear();
}
复制代码
注意,hasUpdatedTable = checkUpdatedTable();
private boolean checkUpdatedTable() {boolean hasUpdatedTable = false;Cursor cursor = mDatabase.query(new SimpleSQLiteQuery(SELECT_UPDATED_TABLES_SQL));//noinspection TryFinallyCanBeTryWithResourcestry {while (cursor.moveToNext()) {final int tableId = cursor.getInt(0);mTableInvalidStatus.set(tableId);hasUpdatedTable = true;}} finally {cursor.close();}if (hasUpdatedTable) {mCleanupStatement.executeUpdateDelete();}return hasUpdatedTable;
}
复制代码
@VisibleForTesting
static final String SELECT_UPDATED_TABLES_SQL = "SELECT * FROM " + UPDATE_TABLE_NAME+ " WHERE " + INVALIDATED_COLUMN_NAME + " = 1;";
复制代码
果然,是查找"UPDATE_TABLE_NAME"这个表中"INVALIDATED_COLUMN_NAME"这列为1的记录,然后设置自己的状态。完成这个过程就分发给自己的Observers。
void notifyByTableVersions(BitSet tableInvalidStatus) {...if (invalidatedTables != null) {mObserver.onInvalidated(invalidatedTables);}
}
复制代码
而在前文中有说到,注册的Observer实际上是RoomTrackingLiveData的mObserver的包装,最终会调用到它的onInvalidated。
mObserver = new InvalidationTracker.Observer(tableNames) {@Overridepublic void onInvalidated(@NonNull Set<String> tables) {ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);}
}
复制代码
final Runnable mInvalidationRunnable = new Runnable() {@MainThread@Overridepublic void run() {boolean isActive = hasActiveObservers();if (mInvalid.compareAndSet(false, true)) {if (isActive) {mDatabase.getQueryExecutor().execute(mRefreshRunnable);}}}
};
复制代码
可见,最后会在线程池中执行RoomTrackingLiveData的mRefreshRunnable任务。这个任务前文已经分析过了,通过CAS的方式查询数据,并post给LiveData,这样就实现了数据更新的通知。到这里,Room和LiveData联动的工作原理就大致分析完毕。
写文章不易,转载请注明出处@渔船Mr_Liu
转载于:https://juejin.im/post/5c8910fc6fb9a04a0956e7ab
Room是怎样和LiveData结合使用的?(源码分析)相关推荐
- Android Jetpack组件之 LiveData使用-源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- LiveData 源码解析(2.4.1 版本)
文章目录 1.LiveData 简介 2.LiveData 配置与基本用法 2.1 依赖引入与配置 2.2 基本用法 2.2.1 LiveData 简单使用 2.2.2 LiveData 扩展 2.2 ...
- Android Jetpack LiveData 源码解析
是什么 LiveData 是具备生命周期的数据,当数据放生变化的时候,如果页面已经销毁,那么就不会回调给监听者. 有什么用? 当我们获取到网络请求的数据,如果页面已经销毁了,就不会调用更新Ui 的方法 ...
- 带你领略Android Jetpack组件的魅力
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- LiveData详细分析
目录介绍 01.LiveData是什么东西 02.使用LiveData的优势 03.使用LiveData的步骤 04.简单使用LiveData 05.observe()和observerForever ...
- LiveData的使用及详解
1. LiveData简单使用 本篇文章代码实现部分主要使用Java进行讲解. LiveData主要方便用于数据的观察,进行UI更新或者业务处理等操作. 如下为LiveData的简单代码实现 // 创 ...
- LiveData的使用和原理
一.LiveData的基本使用 //创建一个LiveData对象 private val livedata = MutableLiveData<String>();//为LiveData设 ...
- LiveData原理解析
什么是LiveData LiveData是Android Architecture Components 其中的一个组件.主要用于更新UI数据和组件之间传值. 1.LiveData是一种持有可被观察数 ...
- Jetpack 架构组件:LiveData
简介 LiveData 是一种可观察的数据存储器类.与常规的可观察类不同,LiveData具有生命周期感知能力,意指它遵循其他应用组件(如Activity/Fragment)的生命周期.这种感知能力可 ...
最新文章
- 参加第十六届智能车竞赛学生提出的问题-05-10
- Cannot change version of project facet Dynamic Web Module to 3.0.
- SD-WAN成本节省取决于基础WAN技术
- 广度优先搜索_广度优先搜索(BFS)
- 如何查看哪些参数被新版本抛弃了?
- 【数据分析】可视化分类型变量,我一般使用这6种图形
- python dtype什么意思_DataFrame中的字符串,但dtype是对象
- opencv之计算机视觉介绍(笔记01)
- php重度写如何优化,win10玩游戏掉帧严重怎么处理
- Mac自带嗅探器和Wireshake抓包(三)
- android课程设计闹钟,EDA课程设计---数字时钟(闹钟)
- laravel项目出现Non-static method Redis::hGet() cannot be called statically的解决方法
- G盘格式化了,要怎样恢复文件
- 屏幕适配遇到P30 的坑
- 6月24日BEA成都User Group活动PPT下载
- 西北农林科技大学计算机考研好考吗,西北农林科技大学考研好考吗?
- 2021黑马web前端
- HDU1435 Stable Match 【稳定婚姻问题】
- 空间三角形_如何改造三角形小屋,营造舒适空间?
- 3D空间 圆柱体画线