前言

之前写项目的时候,对于数据库的操作不是特别多,能避免就尽量避免,并且一直想不到比较好的方法去组织网络数据、本地数据的逻辑。所以在最近的面试中时,问及项目中的数据库实现,以及比较好用的数据库的框架及其实现原理时,我就只答道之前在《第一行代码》中看到了的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结合使用的?(源码分析)相关推荐

  1. Android Jetpack组件之 LiveData使用-源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  2. LiveData 源码解析(2.4.1 版本)

    文章目录 1.LiveData 简介 2.LiveData 配置与基本用法 2.1 依赖引入与配置 2.2 基本用法 2.2.1 LiveData 简单使用 2.2.2 LiveData 扩展 2.2 ...

  3. Android Jetpack LiveData 源码解析

    是什么 LiveData 是具备生命周期的数据,当数据放生变化的时候,如果页面已经销毁,那么就不会回调给监听者. 有什么用? 当我们获取到网络请求的数据,如果页面已经销毁了,就不会调用更新Ui 的方法 ...

  4. 带你领略Android Jetpack组件的魅力

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  5. LiveData详细分析

    目录介绍 01.LiveData是什么东西 02.使用LiveData的优势 03.使用LiveData的步骤 04.简单使用LiveData 05.observe()和observerForever ...

  6. LiveData的使用及详解

    1. LiveData简单使用 本篇文章代码实现部分主要使用Java进行讲解. LiveData主要方便用于数据的观察,进行UI更新或者业务处理等操作. 如下为LiveData的简单代码实现 // 创 ...

  7. LiveData的使用和原理

    一.LiveData的基本使用 //创建一个LiveData对象 private val livedata = MutableLiveData<String>();//为LiveData设 ...

  8. LiveData原理解析

    什么是LiveData LiveData是Android Architecture Components 其中的一个组件.主要用于更新UI数据和组件之间传值. 1.LiveData是一种持有可被观察数 ...

  9. Jetpack 架构组件:LiveData

    简介 LiveData 是一种可观察的数据存储器类.与常规的可观察类不同,LiveData具有生命周期感知能力,意指它遵循其他应用组件(如Activity/Fragment)的生命周期.这种感知能力可 ...

最新文章

  1. 参加第十六届智能车竞赛学生提出的问题-05-10
  2. Cannot change version of project facet Dynamic Web Module to 3.0.
  3. SD-WAN成本节省取决于基础WAN技术
  4. 广度优先搜索_广度优先搜索(BFS)
  5. 如何查看哪些参数被新版本抛弃了?
  6. 【数据分析】可视化分类型变量,我一般使用这6种图形
  7. python dtype什么意思_DataFrame中的字符串,但dtype是对象
  8. opencv之计算机视觉介绍(笔记01)
  9. php重度写如何优化,win10玩游戏掉帧严重怎么处理
  10. Mac自带嗅探器和Wireshake抓包(三)
  11. android课程设计闹钟,EDA课程设计---数字时钟(闹钟)
  12. laravel项目出现Non-static method Redis::hGet() cannot be called statically的解决方法
  13. G盘格式化了,要怎样恢复文件
  14. 屏幕适配遇到P30 的坑
  15. 6月24日BEA成都User Group活动PPT下载
  16. 西北农林科技大学计算机考研好考吗,西北农林科技大学考研好考吗?
  17. 2021黑马web前端
  18. HDU1435 Stable Match 【稳定婚姻问题】
  19. 空间三角形_如何改造三角形小屋,营造舒适空间?
  20. 3D空间 圆柱体画线

热门文章

  1. 中国靶材行业需求前景分析及发展形势研究报告2021版
  2. 计算机生活工作原理,计算机基本工作原理是什么?
  3. openresty开发系列10--openresty的简单介绍及安装
  4. vue-cli3.0 Typescript 项目集成环信WebIM 群组聊天
  5. Redis数据结构:字典(hash表)
  6. 系统服务描述表指针查找
  7. 深入理解Openstack自动化部署
  8. [hdu1847]博弈,推理
  9. STL源码剖析——P142关于list::sort函数
  10. 网易哲学气质H5看似眼熟,为什么大家还是忍不住转发?