日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Room是怎样和LiveData结合使用的?(源码分析)

發布時間:2025/3/20 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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结合使用的?(源码分析)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。