Room是怎样和LiveData结合使用的?(源码分析)
前言
之前寫項(xiàng)目的時(shí)候,對于數(shù)據(jù)庫的操作不是特別多,能避免就盡量避免,并且一直想不到比較好的方法去組織網(wǎng)絡(luò)數(shù)據(jù)、本地?cái)?shù)據(jù)的邏輯。所以在最近的面試中時(shí),問及項(xiàng)目中的數(shù)據(jù)庫實(shí)現(xiàn),以及比較好用的數(shù)據(jù)庫的框架及其實(shí)現(xiàn)原理時(shí),我就只答道之前在《第一行代碼》中看到了的LitePal,但源碼就...所以這次來惡補(bǔ)一次數(shù)據(jù)庫。幾經(jīng)搜索,云比較,比較青睞官方Jetpack組件中的Room。
Room簡介
Room框架是使用生成代碼的方式在編譯時(shí)生成CRUD的代碼,因此性能是遠(yuǎn)遠(yuǎn)好過通過反射實(shí)現(xiàn)的ORM框架。但是事實(shí)上,Room最吸引我的地方不止是性能,Room對架構(gòu)組件(LiveData)、RxJava等流行框架做了適配。例如,Room中的查詢操作可以返回一個(gè)LiveData,并且,每一次RUD操作,都會更新LiveData。這可以大大簡化本地、內(nèi)存、網(wǎng)絡(luò)多級緩存的實(shí)現(xiàn),具體官方也給出了一系列Demo,并且隨時(shí)都在隨著框架或者根據(jù)PR更新,強(qiáng)烈推薦研究這些Demo!
注
本文主要是對Room中與LiveData的聯(lián)動作出分析,閱讀本文前建議先熟悉Room的基本使用,建議看一下與LiveData配合使用的Demo。
正文
創(chuàng)建相關(guān)類
AppDatabase.kt
abstract class AppDatabase : RoomDatabase() {abstract fun bookDao(): BookDao } 復(fù)制代碼Book.kt
interface BookDao {fun insert(book: Book): Longfun delete(book: Book)fun queryById(id: Long): LiveData<Book> } 復(fù)制代碼使用數(shù)據(jù)庫:
val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "test.db").build()db.bookDao().queryById(1).observe(this, Observer {// do something when book update}) 復(fù)制代碼這樣在Observer里面就可以接收到任何時(shí)候數(shù)據(jù)庫id=1的數(shù)據(jù)修改操作了。
生成代碼并分析
Build -> Make Project 編譯,會生成Room相關(guān)代碼,如果是kapt的話,生成代碼目錄應(yīng)該是{項(xiàng)目目錄}/app/build/generated/source/kapt/debug/{包路徑}/下。 我們可以看到生成了AppDatabase_Impl和BookDao_Impl兩個(gè)代碼文件,分別對應(yīng)前面貼出來的AppDatabase的實(shí)現(xiàn)類和BookDao的實(shí)現(xiàn)類。
AppDatabase_Impl則是表的創(chuàng)建、刪除相關(guān)代碼,Dao則是具體表的CRUD操作。這里我們重點(diǎn)關(guān)系生成的查詢方法。 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();}}); } 復(fù)制代碼注意這一行
return __db.getInvalidationTracker().createLiveData(...); 復(fù)制代碼我們跟進(jìn)去,最終創(chuàng)建的是一個(gè)RoomTrackingLiveData,是一個(gè)繼承了LiveData的類。下面是它的構(gòu)造方法。從構(gòu)造方法來看,比較可疑的對象的是InvalidationTracker.Observer這個(gè)類,并且實(shí)現(xiàn)十有八九是觀察者模式。而最后的回調(diào)也多半是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);}}; } 復(fù)制代碼而在RoomTrackingLiveData中,重寫了onActive方法。其中mContainer是InvalidationLiveDataContainer,文檔上有寫僅僅是維護(hù)LiveData的強(qiáng)引用,防止正在使用的LiveData被回收,跟本文目標(biāo)沒關(guān)系,可忽略。而后面的就有意思了,通過Excutor執(zhí)行了一個(gè)任務(wù),所以,我們來看一下這個(gè)任務(wù)把。
protected void onActive() {super.onActive();mContainer.onActive(this);mDatabase.getQueryExecutor().execute(mRefreshRunnable); } 復(fù)制代碼mRefreshRunnable#run()
// mRegisteredObserver是否注冊的標(biāo)志 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實(shí)現(xiàn)類中返回LiveData時(shí)傳入的一個(gè)參數(shù),用于查詢,并將數(shù)據(jù)組裝成一個(gè)實(shí)體類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()); 復(fù)制代碼這段代碼后段通過CAS去完成一次數(shù)據(jù)庫的查詢,組裝成實(shí)體類并postValue,即更新LiveData。 注意到這個(gè)代碼前段調(diào)用了InvalidationTracker的addWeakObserver,這個(gè)方法就應(yīng)該就是訂閱了。
InvalidationTracker#addWeakObserver
public void addWeakObserver(Observer observer) {addObserver(new WeakObserver(this, observer)); } 復(fù)制代碼InvalidationTracker#addObserver
public void addObserver( 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();} } 復(fù)制代碼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);}public void onInvalidated( Set<String> tables) {final Observer observer = mDelegateRef.get();if (observer == null) {mTracker.removeObserver(this);} else {observer.onInvalidated(tables);}} } 復(fù)制代碼可以看到,WeakObserver就是對Observer一個(gè)弱引用的包裝。而在addObserver中,根據(jù)observer中tableNames,對更新了InvalidationTracker的訂閱記錄。添加成功后,最后會調(diào)用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; } 復(fù)制代碼這里mTableObservers是對每個(gè)table的observer進(jìn)行計(jì)數(shù)。為什么要計(jì)數(shù)呢?我們接著看。在發(fā)現(xiàn)了訂閱數(shù)從0->1的table時(shí),這個(gè)方法會返回true,如果它返回true,會執(zhí)行syncTriggers()方法,經(jīng)過調(diào)用會執(zhí)行這一段代碼:
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(); } 復(fù)制代碼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;} } 復(fù)制代碼這個(gè)getTablesToSync方法很短,但這里就體現(xiàn)了observer計(jì)數(shù)的作用,它遍歷這個(gè)表,找出計(jì)數(shù)與之前不一樣的,如果由一個(gè)大于0的數(shù)變?yōu)?>0,表明現(xiàn)在沒有observer訂閱它,返回REMOVE,0->n,返回ADD,否則NO_OP。對于返回ADD的表,就應(yīng)該是會監(jiān)聽變化的表了。它會執(zhí)行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());} } 復(fù)制代碼到這里我們就很清楚了:實(shí)現(xiàn)監(jiān)聽修改的方法是觸發(fā)器。 (不過我之前僅僅是聽說過觸發(fā)器,很少用過,如果不了解,這里有一份簡易的教程)。而觸發(fā)器關(guān)心的操作是這一些:
private static final String[] TRIGGERS = new String[]{"UPDATE", "DELETE", "INSERT"}; 復(fù)制代碼對應(yīng)著更新、刪除、插入。當(dāng)有這些操作時(shí),根據(jù)上述觸發(fā)器語句,會更新一個(gè)由InvalidationTracker維護(hù)的表"UPDATE_TABLE_NAME"。 InvalidationTracker#UPDATE_TABLE_NAME
private static final String UPDATE_TABLE_NAME = "room_table_modification_log"; 復(fù)制代碼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;} } 復(fù)制代碼注意到表中有這樣一列:
INVALIDATED_COLUMN_NAME + " INTEGER NOT NULL DEFAULT 0 復(fù)制代碼在觸發(fā)器設(shè)置的是更新操作時(shí)會被設(shè)置為1。所以,應(yīng)該就是檢驗(yàn)這個(gè)值來判斷表是否有更新。那么是哪里進(jìn)行判斷呢?我們可以從一個(gè)更新操作開始找,例如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();} } 復(fù)制代碼最后發(fā)現(xiàn)在endTransaction中調(diào)用了InvalidationTracker的refreshVersionsAsync方法。而在這個(gè)方法中,最終會運(yùn)行InvalidationTracker的mRefreshRunnable對象的run方法。(注意,和上文的mRefreshRunnbale屬于不同類,不是同一個(gè)對象。) 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();} } 復(fù)制代碼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) {// 分發(fā)給Observer,最終會更新LiveDatasynchronized (mObserverMap) {for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {entry.getValue().notifyByTableVersions(mTableInvalidStatus);}}// Reset invalidated status flags.mTableInvalidStatus.clear(); } 復(fù)制代碼注意,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; } 復(fù)制代碼@VisibleForTesting static final String SELECT_UPDATED_TABLES_SQL = "SELECT * FROM " + UPDATE_TABLE_NAME+ " WHERE " + INVALIDATED_COLUMN_NAME + " = 1;"; 復(fù)制代碼果然,是查找"UPDATE_TABLE_NAME"這個(gè)表中"INVALIDATED_COLUMN_NAME"這列為1的記錄,然后設(shè)置自己的狀態(tài)。完成這個(gè)過程就分發(fā)給自己的Observers。
void notifyByTableVersions(BitSet tableInvalidStatus) {...if (invalidatedTables != null) {mObserver.onInvalidated(invalidatedTables);} } 復(fù)制代碼而在前文中有說到,注冊的Observer實(shí)際上是RoomTrackingLiveData的mObserver的包裝,最終會調(diào)用到它的onInvalidated。
mObserver = new InvalidationTracker.Observer(tableNames) {@Overridepublic void onInvalidated(@NonNull Set<String> tables) {ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);} } 復(fù)制代碼final Runnable mInvalidationRunnable = new Runnable() {@MainThread@Overridepublic void run() {boolean isActive = hasActiveObservers();if (mInvalid.compareAndSet(false, true)) {if (isActive) {mDatabase.getQueryExecutor().execute(mRefreshRunnable);}}} }; 復(fù)制代碼可見,最后會在線程池中執(zhí)行RoomTrackingLiveData的mRefreshRunnable任務(wù)。這個(gè)任務(wù)前文已經(jīng)分析過了,通過CAS的方式查詢數(shù)據(jù),并post給LiveData,這樣就實(shí)現(xiàn)了數(shù)據(jù)更新的通知。到這里,Room和LiveData聯(lián)動的工作原理就大致分析完畢。
寫文章不易,轉(zhuǎn)載請注明出處@漁船Mr_Liu
轉(zhuǎn)載于:https://juejin.im/post/5c8910fc6fb9a04a0956e7ab
總結(jié)
以上是生活随笔為你收集整理的Room是怎样和LiveData结合使用的?(源码分析)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: zabbix4.2学习笔记--新建用户组
- 下一篇: Flutter学习之认知基础组件