The changes will be rolled back if any transaction is ended without being marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed.
WAL (Write-Ahead Log): 作法与 Rollback Journal 刚好相反。原内容仍保留在原 DB 之中,但新的变动则 append 至 WAL 文件。而当 COMMIT 发生时,仅代表某个 Transaction 已 append 进 WAL 文件了,但并不一定有写入原 DB (当 WAL 文件大小到达 checkpoint 的阈值时才会写入)。如此可让其他 DB 连接继续对原 DB 内容进行读取操作,而其他连接也可同时将变动 COMMIT 进 WAL 文件。
这种模式下的文件目录为一个db文件+一个db-shm文件(all SQLite database connections associated with the same database file need to share some memory that is used as an index for the WAL file)+一个db-wal文件
ROOM 对 API16 以上机型默认开启 WAL 模式
getDao 方法都是线程安全的
例如:
1 2 3 4 5 6 7 8 9 10 11 12
public ConfigDao getConfigDao(){ if (_configDao != null) { return _configDao; } else { synchronized(this) { if(_configDao == null) { _configDao = new ConfigDao_Impl(this); } return _configDao; } } }
public SQLiteDatabase getReadableDatabase(){ synchronized (this) { return getDatabaseLocked(false); } }
@SuppressWarnings("unused") private SQLiteDatabase getDatabaseLocked(boolean writable){ if (mDatabase != null) { if (!mDatabase.isOpen()) { // Darn! The user closed the database by calling mDatabase.close(). mDatabase = null; } elseif (!writable || !mDatabase.isReadOnly()) { // The database is already open for business. return mDatabase; } }
if (mIsInitializing) { thrownew IllegalStateException("getDatabase called recursively"); }
SQLiteDatabase db = mDatabase; try { mIsInitializing = true;
if (db != null) { if (writable && db.isReadOnly()) { db.reopenReadWrite(); } } elseif (mName == null) { db = SQLiteDatabase.create(null); } else { int connectionPoolSize = mForcedSingleConnection ? 1 : 0; try { if (DEBUG_STRICT_READONLY && !writable) { final String path = mContext.getDatabasePath(mName).getPath(); db = SQLiteDatabase.openDatabase(path, mPassword, mCipher, mFactory, SQLiteDatabase.OPEN_READONLY, mErrorHandler, connectionPoolSize); } else { mNeedMode = true; mMode = mEnableWriteAheadLogging ? Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0; db = Context.openOrCreateDatabase(mContext, mName, mPassword, mCipher, mMode, mFactory, mErrorHandler, connectionPoolSize); } } catch (SQLiteException ex) { if (writable) { throw ex; } Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", ex); final String path = mContext.getDatabasePath(mName).getPath(); db = SQLiteDatabase.openDatabase(path, mPassword, mCipher, mFactory, SQLiteDatabase.OPEN_READONLY, mErrorHandler); } }
return getDatabaseLockedLast(db);
} finally { mIsInitializing = false; if (db != null && db != mDatabase) { db.close(); } } }
voidsyncTriggers(SupportSQLiteDatabase database){ if (database.inTransaction()) { // we won't run this inside another transaction. return; } try { // This method runs in a while loop because while changes are synced to db, another // runnable may be skipped. If we cause it to skip, we need to do its work. while (true) { Lock closeLock = mDatabase.getCloseLock(); closeLock.lock(); try { // there is a potential race condition where another mSyncTriggers runnable // can start running right after we get the tables list to sync. finalint[] tablesToSync = mObservedTableTracker.getTablesToSync(); if (tablesToSync == null) { return; } finalint limit = tablesToSync.length; database.beginTransaction(); try { 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(); } mObservedTableTracker.onSyncCompleted(); } finally { closeLock.unlock(); } } } 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); } }
A different temporary file is created each time, so that just like as with the special ":memory:" string, two database connections to temporary databases each have their own private database. Temporary databases are automatically deleted when the connection that created them closes.
voidinternalInit(SupportSQLiteDatabase database){ synchronized (this) { if (mInitialized) { Log.e(Room.LOG_TAG, "Invalidation tracker is initialized twice :/."); return; }
// These actions are not in a transaction because temp_store is not allowed to be // performed on a transaction, and recursive_triggers is not affected by transactions. database.execSQL("PRAGMA temp_store = MEMORY;"); database.execSQL("PRAGMA recursive_triggers='ON';"); database.execSQL(CREATE_TRACKING_TABLE_SQL); syncTriggers(database); mCleanupStatement = database.compileStatement(RESET_UPDATED_TABLES_SQL); mInitialized = true; } }
voidsyncTriggers(SupportSQLiteDatabase database){ if (database.inTransaction()) { // we won't run this inside another transaction. return; } try { // This method runs in a while loop because while changes are synced to db, another // runnable may be skipped. If we cause it to skip, we need to do its work. while (true) { Lock closeLock = mDatabase.getCloseLock(); closeLock.lock(); try { // there is a potential race condition where another mSyncTriggers runnable // can start running right after we get the tables list to sync. finalint[] tablesToSync = mObservedTableTracker.getTablesToSync(); // #1 if (tablesToSync == null) { return; } finalint limit = tablesToSync.length; database.beginTransaction(); try { 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(); } mObservedTableTracker.onSyncCompleted(); } finally { closeLock.unlock(); } } } 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); } }
Runnable mRefreshRunnable = new Runnable() { @Override publicvoidrun(){ final Lock closeLock = mDatabase.getCloseLock(); Set<Integer> invalidatedTableIds = null; try { closeLock.lock();
if (!ensureInitialization()) { return; }
if (!mPendingRefresh.compareAndSet(true, false)) { // no pending refresh // 防止重入 return; }
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; }
if (mDatabase.mWriteAheadLoggingEnabled) { // #1 // 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 { invalidatedTableIds = checkUpdatedTable(); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } else { invalidatedTableIds = 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 (invalidatedTableIds != null && !invalidatedTableIds.isEmpty()) { synchronized (mObserverMap) { for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) { entry.getValue().notifyByTableInvalidStatus(invalidatedTableIds); // #2 } } } }
final Runnable mInvalidationRunnable = new Runnable() { @MainThread publicvoidrun(){ boolean isActive = RoomTrackingLiveData.this.hasActiveObservers(); if (RoomTrackingLiveData.this.mInvalid.compareAndSet(false, true) && isActive) { RoomTrackingLiveData.this.getQueryExecutor().execute(RoomTrackingLiveData.this.mRefreshRunnable); // #2 }
} };
final Runnable mRefreshRunnable = new Runnable() { @WorkerThread publicvoidrun(){ if (RoomTrackingLiveData.this.mRegisteredObserver.compareAndSet(false, true)) { RoomTrackingLiveData.this.mDatabase.getInvalidationTracker().addWeakObserver(RoomTrackingLiveData.this.mObserver); }
boolean computed; do { computed = false; if (RoomTrackingLiveData.this.mComputing.compareAndSet(false, true)) { try { Object value = null;
@SuppressWarnings("WeakerAccess") publicvoidrefreshVersionsAsync(){ // TODO we should consider doing this sync instead of async. if (mPendingRefresh.compareAndSet(false, true)) { mDatabase.getQueryExecutor().execute(mRefreshRunnable); } }
实际上,在[ROOM 的线程安全是如何保证的]这节的第三部分,我们已经能发现 Dao 层的 DB 操作都是依赖 beginTransaction, endTransaction 来完成 DB 事务的。也就是说,只要 DAO 层有 DB 事务发生,那么 ROOM 必定会在 getQueryExecutor()的线程池中,执行 mRefreshRunnable, 如果发现了有数据更新的 table,就将这些 table 信息全部抛给Observer#onInvalidated处理
InvalidationTracker 小结
InvalidationTracker的职责即建立了业务对 DB 数据写的观察者模式, 业务的 Observer 被InvalidationTracker聚合持有,同时建立一个临时表room_table_modification_log
DAO 层业务代码委托RoomDatabase通过beginTransaction, endTransaction来完成 DB 的事务提交,这些函数被调用时,顺带触发了InvalidationTracker内部对表room_table_modification_log的异步查询,查询到有数据更新(invalidated=1)的表名信息, 将这些表名信息带给Observer#onInvalidated的参数中。例如 RoomTrackingLiveData 中, 利用InvalidationTracker建立的这套机制,实现了 DAO 层 LiveData 的持有数据实时更新的特性