Android Jetpack组件之数据库Room详解(三)
本文涉及Library的版本如下:
- androidx.room:room-runtime:2.1.0-alpha03
- androidx.room:room-compiler:2.1.0-alpha03(注解编译器)
Room对LiveData扩展
下面先列一个room中使用livedata的例子:
@Dao
public interface UserDao {@Query("SELECT * FROM user")LiveData<List<User>> getUsersLiveData();
} public class RoomActivity extends AppCompatActivity {protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mRoomModel = ViewModelProviders.of(this).get(RoomModel.class);mRoomModel.getUsersLiveData().observe(this, new Observer<List<User>>() {@Overridepublic void onChanged(List<User> users) {adapter.setData(users); // 数据回调,这里只要数据库User有数据变化,每次都会回调}});}
} public class RoomModel extends AndroidViewModel {private final AppDatabase mAppDatabase; //AppDatabase是继承RoomDatabase的抽象类public RoomModel(@NonNull Application application) {super(application);mAppDatabase = AppDatabase.getInstance(this.getApplication());}public LiveData<List<User>> getUsersLiveData() {return mAppDatabase.userDao().getUsersLiveData();}
}
只要数据库的数据有变化, 上面代码中onChanged就会回调,但是, 不是什么时候都回调,当activity处理onstop是不会回调,但是activity重新走onstart后,数据库有增删改还是会回调的。这里的效果有点类似安卓里的Loader, 使用过Loader的都知道,Loader是会监听contentprovier的一条uri, 有数据变更, 处于onstart状态的activity,Loader会重新加载一个数据。接下来看一下Room是怎么监听数据库变化的。
UserDao_Impl.getUsersLiveData方法代码如下:
@Overridepublic LiveData<List<User>> getUsersLiveData() {final String _sql = "SELECT * FROM user";//通过sql创建SQLite查询执行程序final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);//__db.getInvalidationTracker()返回是InvalidationTracker类,是RoomDatabase的一个成员变量//调用InvalidationTracker.createLiveData方法创建LiveData对象return __db.getInvalidationTracker().createLiveData(new String[]{"user"}, new Callable<List<User>>() {@Overridepublic List<User> call() throws Exception {//下面代码不多说,执行sql语句,组装成List<User>返回final Cursor _cursor = DBUtil.query(__db, _statement, false);try {final int _cursorIndexOfFirstName = CursorUtil.getColumnIndexOrThrow(_cursor, "first_name");final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name");final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");final List<User> _result = new ArrayList<User>(_cursor.getCount());while(_cursor.moveToNext()) {final User _item;_item = new User();_item.firstName = _cursor.getString(_cursorIndexOfFirstName);_item.name = _cursor.getString(_cursorIndexOfName);_item.id = _cursor.getInt(_cursorIndexOfId);_result.add(_item);}return _result;} finally {_cursor.close();}}@Overrideprotected void finalize() {_statement.release();}});}
从上面的代码可以看出监听数据库变化核心类是InvalidationTracker,InvalidationTracker类是RoomDatabase的构造器创建的, RoomDatabase中的createInvalidationTracker方法是抽象类,是由开发者继承RoomDatabase,最终createInvalidationTracker实现是apt编译时期自动生成的类实现的, 接着看代码:
//本文createInvalidationTracker()的真正实现是AppDatabase_Impl类,不了解的可以看一下上一篇文章@Overrideprotected InvalidationTracker createInvalidationTracker() {final HashMap<String, String> _shadowTablesMap = new HashMap<String, String>(0);HashMap<String, Set<String>> _viewTables = new HashMap<String, Set<String>>(0);//User, Favorite是表名return new InvalidationTracker(this, _shadowTablesMap, _viewTables, "User","Favorite");}//InvalidationTracker的构造器
public InvalidationTracker(RoomDatabase database, Map<String, String> shadowTablesMap,Map<String, Set<String>> viewTables, String... tableNames) {mDatabase = database;//看名字ObservedTableTracker是一个观察表的跟踪者, 先跳过mObservedTableTracker = new ObservedTableTracker(tableNames.length);//一个Map, Key是表名,Value是表idmTableIdLookup = new ArrayMap<>();mShadowTableLookup = new SparseArrayCompat<>(shadowTablesMap.size());mViewTables = viewTables;//看名字是一个失效LiveData容器,先跳过mInvalidationLiveDataContainer = new InvalidationLiveDataContainer(mDatabase);final int size = tableNames.length;mTableNames = new String[size];// 遍历数据表个数for (int id = 0; id < size; id++) { final String tableName = tableNames[id].toLowerCase(Locale.US);/Key是表名,并且转成全小写,Value是表id, id是数据表数组里的indexmTableIdLookup.put(tableName, id);mTableNames[id] = tableName;String shadowTableName = shadowTablesMap.get(tableNames[id]);if (shadowTableName != null) {mShadowTableLookup.append(id, shadowTableName.toLowerCase(Locale.US));}}//一个set, 存储booleanmTableInvalidStatus = new BitSet(tableNames.length);
}//InvalidationTracker的internalInit方法
//该方法会在数据库打开的时候调用, 是在SQLiteOpenHelper.onOpen方法时调用
void internalInit(SupportSQLiteDatabase database) {synchronized (this) {if (mInitialized) {Log.e(Room.LOG_TAG, "Invalidation tracker is initialized twice :/.");return;}database.beginTransaction();try {//PRAGMA是一个特殊命令,通常用于改变数据库的设置//临时存储设置为内存模式database.execSQL("PRAGMA temp_store = MEMORY;");//启用递归触发器database.execSQL("PRAGMA recursive_triggers='ON';");//CREATE_TRACKING_TABLE_SQL是个sql语句字符串, 语句如下://CREATE TEMP TABLE room_table_modification_log (table_id INTEGER PRIMARY //KEY, invalidated INTEGER NOT NULL DEFAULT 0 )//创建一个临时表room_table_modification_logdatabase.execSQL(CREATE_TRACKING_TABLE_SQL);database.setTransactionSuccessful();} finally {database.endTransaction();}//同步数据库触发器syncTriggers(database);mCleanupStatement = database.compileStatement(RESET_UPDATED_TABLES_SQL);mInitialized = true; // 初始化的标志,只初始化一次}}
从上面的代码可以知道当数据打开是调用internalInit方法,执行sql语句把临时存储设置为内存模式, 创建了一个名叫room_table_modification_log的临时表,临时表使用CREATE TEMP TABLE 语句创建的,临时表不会持久化,数据库关闭就不存在啦。这个临时表只有两个字段,分别是table_id和invalidated(是否无效的标志)。接着看syncTriggers方法
void syncTriggers(SupportSQLiteDatabase database) {while (true) {Lock closeLock = mDatabase.getCloseLock();closeLock.lock();try {//tablesToSync存储了表相应触发器的状态final int[] tablesToSync = mObservedTableTracker.getTablesToSync();// 首次初始化tablesToSync为null, 当mObservedTableTracker观察一个表时就不为nullif (tablesToSync == null) { return;}final int 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();}}
}//开始跟踪表
private void startTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {//给临时表room_table_modification_log插入数据,(tableId, 0)writableDb.execSQL("INSERT OR IGNORE INTO " + UPDATE_TABLE_NAME + " VALUES(" + tableId + ", 0)");final String tableName = mShadowTableLookup.get(tableId, mTableNames[tableId]);StringBuilder stringBuilder = new StringBuilder();//TRIGGERS是一个字符串数组,分别是UPDATE、DELETE、INSERT//遍历TRIGGERS,分别为该表创建3个触发器,分别是更新触发器、删除触发器、插入触发器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 UPDATE ").append(UPDATE_TABLE_NAME).append(" SET ").append(INVALIDATED_COLUMN_NAME).append(" = 1").append(" WHERE ").append(TABLE_ID_COLUMN_NAME).append(" = ").append(tableId).append(" AND ").append(INVALIDATED_COLUMN_NAME).append(" = 0").append("; END");writableDb.execSQL(stringBuilder.toString());}}
startTrackingTable方法为一个表创建3个触发器,分别是更新触发器、删除触发器、插入触发器。对应stopTrackingTablefang方法就是删除触发器,对sqlite触发器本文不详细说明,结合上面代码,以更新触发器为例,简单介绍一下:
//结合上面代码, 创建更新触发器的sql如下:
CREATE TEMP TRIGGER IF NOT EXISTS room_table_modification_trigger_表名_类型 AFTER UPDATE ON 表名
BEGIN UPDATE room_table_modification_log SET invalidated = 1 WHERE table_id = ${tableId} AND invalidated = 0;
END
上面sql语句中room_table_modification_trigger 加表名加类型是触发器名字, "AFTER UPDATE ON 表名"意思是当某个表更新数据后触发, 语句中 BEGIN 与 END之间语句是表更新后执行什么操作。细看BEGIN 与 END之间语句很好理解,根据tableId把临时room_table_modification_log表的invalidated由0改1。所以当某个表数据有更新、删除、插入操作时,利用触发器去修改一个临时日志表的一个值说明是该表的数据有改变,然后去监听这个表临时的表invalidated这个值就可以知道哪个表数据改变啦, 不多说接着看一下源码怎么监听临时表的。
//InvalidationTracker的refreshVersionsSync方法
public void refreshVersionsAsync() {// TODO we should consider doing this sync instead of async.if (mPendingRefresh.compareAndSet(false, true)) {//异步执行一个RunnablemDatabase.getQueryExecutor().execute(mRefreshRunnable);}
}Runnable mRefreshRunnable = new Runnable() {@Overridepublic void run() {...//省略了一些细节代码//checkUpdatedTable方法,检查表有没有数据变化hasUpdatedTable = checkUpdatedTable();if (hasUpdatedTable) {synchronized (mObserverMap) {//有变化就遍历ObserverWrapper观察者for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {entry.getValue().notifyByTableVersions(mTableInvalidStatus);}}}}private boolean checkUpdatedTable() {boolean hasUpdatedTable = false;//执行一个sql语句, 这个语句是://SELECT * FROM room_table_modification_log WHERE invalidated = 1//查询有变化的数据Cursor cursor = mDatabase.query(new SimpleSQLiteQuery(SELECT_UPDATED_TABLES_SQL));try {while (cursor.moveToNext()) {final int tableId = cursor.getInt(0);mTableInvalidStatus.set(tableId);//一个标志,hasUpdatedTable=true,表有增删改的变化hasUpdatedTable = true;}} finally {cursor.close();}if (hasUpdatedTable) {//mCleanupStatement在internalInit方法里初始化, mCleanupStatement是SupportSQLiteStatement对于,调用executeUpdateDelete()执行一个语句, 语句如下://UPDATE room_table_modification_log SET invalidated = 0 //WHERE invalidated = 1//从sql语句来看,是还原invalidated,还原临时表的状态mCleanupStatement.executeUpdateDelete();}return hasUpdatedTable;}}
经过上面源码,思路已经比较清晰啦,利用触发器监听某个表的更新、删除、插入, 监听到日志记录在一个临时日志表里,然后再去不停地监听临时日志表就可以知道某个表数据是否改变啦。InvalidationTracker的refreshVersionsSync方法就是监听临时日志表的方法,这个方法调用时机是在RoomDatabase.endTransaction方法里,为什么要放事务结束的方法里呢?再简单仔细看了一源码,发现Room的所有增删改的操作都是通过开启事务来执行的。 回头看一下LiveData的创建过程。
//UserDao_Impl.getUsersLiveData方法
@Overridepublic LiveData<List<User>> getUsersLiveData() {...//调用InvalidationTracker.createLiveData方法创建LiveDatareturn __db.getInvalidationTracker().createLiveData(new String[]{"user"}, new Callable<List<User>>() {@Overridepublic List<User> call() throws Exception {....//省略这代码,这里方法逻辑,执行sql语句查询,组装成List<User>返回}});}//InvalidationTracker.createLiveData方法会调用InvalidationLiveDataContainer.create
<T> LiveData<T> create(String[] tableNames, Callable<T> computeFunction) {//computeFunction是Callable接口回调, tableNames是表名, RoomTrackingLiveData继承LiveDatareturn new RoomTrackingLiveData<>(mDatabase, this, computeFunction, tableNames);
}//RoomTrackingLiveData类
class RoomTrackingLiveData<T> extends LiveData<T> {//RoomTrackingLiveData构造器RoomTrackingLiveData(RoomDatabase database,InvalidationLiveDataContainer container,Callable<T> computeFunction,String[] tableNames) {mDatabase = database;mComputeFunction = computeFunction;mContainer = container;//初始化一个InvalidationTracker的观察者mObserver = new InvalidationTracker.Observer(tableNames) {@Overridepublic void onInvalidated(@NonNull Set<String> tables) {//主线程执行一个Runnable, onInvalidated方法在某个表数据有变化是会触发ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);}};}//onActive方法继承LiveData, 当activity或者fragment出入onstart会被触发一次,//关于LiveData的不细说,可以看之前的文章@Overrideprotected void onActive() {super.onActive();mContainer.onActive(this);//异步执行mRefreshRunnablemDatabase.getQueryExecutor().execute(mRefreshRunnable);}
} //RoomTrackingLiveData的mRefreshRunnable和mInvalidationRunnablefinal Runnable mRefreshRunnable = new Runnable() {@WorkerThread@Overridepublic void run() {if (mRegisteredObserver.compareAndSet(false, true)) {//添加一个观察者, 为什么需要添加一个观察者,等会解析,先往下看mDatabase.getInvalidationTracker().addWeakObserver(mObserver);}boolean computed;//do 循环do {computed = false;//mComputing是原子锁,保证多个线程执行时,只能一个线程执行循环if (mComputing.compareAndSet(false, true)) {//mComputing初始值是false, 一次能进来try {T value = null;//mInvalid是原子锁, 初始值是truewhile (mInvalid.compareAndSet(true, false)) {computed = true;try {//执行Callable接口, 这里例子就是执行sql语句查询Uservalue = mComputeFunction.call();} catch (Exception e) {}}if (computed) {//执行完查询语句,通知livedata的观察者,会把value转回主线程//看回本文的开头,会触发onChanged(value),通知ui刷新postValue(value);}} finally {// 释放锁mComputing.set(false);}}} while (computed && mInvalid.get());}};final Runnable mInvalidationRunnable = new Runnable() {@MainThread@Overridepublic void run() {boolean isActive = hasActiveObservers();// mInvalid是原子锁if (mInvalid.compareAndSet(false, true)) {if (isActive) {//异步执行mRefreshRunnablemDatabase.getQueryExecutor().execute(mRefreshRunnable);}}}};
mInvalidationRunnable 在某个表数据有变化是会触发执行,而mInvalidationRunnable的实现又是添加一个mRefreshRunnable异步执行, 而mInvalid这个原子锁, 锁得是mComputeFunction.call(), 保证多线程下查询只能执行一个。 上面代码 mDatabase.getInvalidationTracker().addWeakObserver(mObserver)还没解答,继续看InvalidationTracker.addWeakObserver方法。
public void addWeakObserver(Observer observer) {//WeakObserver是对observer一个wrapper,作用是防止内存泄露, //WeakObserver的写法不错,又学到啦, 里面是一个弱引的observeraddObserver(new WeakObserver(this, observer));
}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;}//tableIds数组存储了所有表id//ObserverWrapper是对observer进行包装ObserverWrapper wrapper = new ObserverWrapper(observer, tableIds, tableNames);ObserverWrapper currentObserver;synchronized (mObserverMap) {//mObserverMap是一个hashMap, observer为key,putIfAbsent方法,如果key不存在//就返回null, 如果key已经存在,就会返回前一个valuecurrentObserver = mObserverMap.putIfAbsent(observer, wrapper);}if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) {//syncTriggers这个方法本文前面分析过了,创建数据库监听触发器,//syncTriggers在InvalidationTracker初始化调用过,但是初始化时, mObservedTableTracker里没有表id,要等mObservedTableTracker.onAdded调用后,才真正能创建触发器监听表syncTriggers();}
}
LiveData在Room的实现整个流程分析结束。来总结一下
总结
利用触发器监听某个表的更新、删除、插入, 监听到日志记录在一个临时日志表里,然后在增删改操作后去查询临时日志表,查某个表数据有改变后,去通知Livedata重新执行Callable.call方法,然后重新查询数据库,最后通知UI更新数据。
Room还有许多额外功能还可以学习:
- Room还支持多个进程监听表变更,具体可以细看MultiInstanceInvalidationClient, MultiInstanceInvalidationClient会涉及到一些aidl等
- Room在建表时还有很多其他注解在某些场景用,还有数据库视图等, 例如@Embedded, @DatabaseView @ForeignKey等
- 数据索引的创建, 子查询等。
转载于:https://juejin.im/post/5cb5534df265da039a3d64b6
Android Jetpack组件之数据库Room详解(三)相关推荐
- Android Jetpack组件之数据库Room详解(二)
本文涉及Library的版本如下: androidx.room:room-runtime:2.1.0-alpha03 androidx.room:room-compiler:2.1.0-alpha03 ...
- 基于sqlite的android数据库编程,Android编程之SQLite数据库操作方法详解
Android编程之SQLite数据库操作方法详解 发布时间:2020-09-07 12:33:04 来源:脚本之家 阅读:85 作者:低调小一 本文实例讲述了Android SQLite数据库操作方 ...
- Android四大组件Service之AIDL详解
Android四大组件Service之AIDL详解 前言 简介 基础知识 AIDL 服务端 定义AIDL文件规则 创建 .aidl 文件 清单注册 通过 IPC 传递对象 调用 IPC 方法 Andr ...
- Android init.rc文件解析过程详解(三)
Android init.rc文件解析过程详解(三) 三.相关结构体 1.listnode listnode结构体用于建立双向链表,这种结构广泛用于kernel代码中, android源代码中定义了l ...
- android线性布局设置控件固定在底部,Android UI组件LinearLayout线性布局详解
LinearLayout 线性布局,该布局的继承关系: 1. 什么是线性布局 通俗的说感觉起来和线有关,参照线的特点,有么是横向的,要么是竖向的. LinearLayout是线性布局控件,它包含的子控 ...
- 【Android Camera1】Camera1 Parameters参数详解(一)—— Size (preview/picture/thumbnail)
1.简介 本篇文章将对Camera1和Size相关的参数,逐个进行详细解析.在Camera1源码分析文章里.已阐述过相关源码.并提供了读取和更新Parameters的2个方法如下: 读取Paramet ...
- Android Jetpack组件DataStore之Proto与Preferences存储详解与使用
一.介绍 Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象.DataStore 使用 Kotlin 协程和 Flow 以异步.一致的事务方式存储数 ...
- ios navigation的返回按钮长按_Android Jetpack架构组件 — Navigation入坑详解 [转]
前言 这是最近看见的觉得比较有意思的文,希望对大家的学习有帮助. Navigation 直接翻译即为导航,它是 Android Jetpack 组件之一,让单 Activity 应用成为首选架构.应用 ...
- Android 开发架构-MVC MVP MVVM详解
何为架构 架构,即程序的逻辑组织结构,是指导开发过程中划分程序逻辑模块的关键,好的架构要使程序达到高内聚低耦合的设计目标.例如一个人,身体的骨骼即为身体的架构,有了基本骨架之后,才可以决定在头颅里开发 ...
最新文章
- 浅谈图网络在视觉任务中的应用
- PTA 基础编程题目集 7-22 龟兔赛跑 C语言
- python的类程序的结构_Python程序员学习路径之数据结构篇
- 如何根据SAP Spartacus的页面快速找到实现的Angular Component
- 快速傅里叶变换(FFT)——按时间抽取DIT的基
- 从图森未来的数据处理平台,看Serverless工作流应用场景
- 多个Email的JS检测正刚表达式.
- JavaScript基础 -- js常用内置方法和对象
- 【华为云技术分享】一行代码就能写一个日志打印组件,你信吗?为你揭晓LiteOS中日志打印组件的核心
- Linux 杀掉所有Java进程
- docker构建mysql容器及Navicat 远程连接
- OpenCV :(-5:Bad argument) Matrix operand is an empty matrix. in function ‘checkOperandsExist‘
- Predator:比微软Kinect更强的视频追踪算法——来自捷克博士论文
- ps使用仿制图章工具,图案图章工具
- ImportREC输入表重建工具
- 档案管理制度计算机管理制度,计算机管理档案制度
- Latex \TexStudio 设置数学符号
- android o bln-al10,华为荣耀BLN-AL10是什么型号
- 利用python爬虫技术实现基于本福特定律_基于Python技术的校园网搜索引擎的设计与实现...
- 运用java打印出菱形