- 一、简介
- 1、Room是什么
- 2、为什么要使用Room
- 二、Room基本用法
- 1、引入依赖
- 2、Room的三大角色
- Entity
- DAO
- Database
- 3、初始化并使用
- 三、Room配合LiveData使用
- 四、Room源码分析
- 1、AppDatabase_Impl
- 2、UserDao_Impl
- 3、Room与LiveData配合
Room框架是Android Jetpack众多组件库的一员。Jetpack的出现统一了Android开发生态,各种三方库逐渐被官方组件所取代,Room逐渐取代竞品成为最主流的数据库ORM框架。Room是SQLite数据库的抽象,让用户能够在充分利用SQLite的强大功能的同时,获享更强健的数据库访问机制。
2、为什么要使用Room相对于SQLiteOpenHelper等传统方法,使用Room操作SQLite有以下优势:
1、编译期的SQL语法检查
2、开发高效,避免大量模板代码
3、API设计友好,容易理解
4、可以LiveData关联,具备LiveData Lifecycle的能力
Room的使用,主要涉及以下3个组件
1、Database:访问底层数据库的入口
2、Entity:代表数据库中的表(table),一般用注解
3、DAO(Data Access Object):数据库访问者
这三个组件的概念也出现在其他ORM框架中,通过Database获取DAO,然后通过DAO查询并获取entities,最终通过entities对数据库table中数据进行读写,用户只需要面向DAO即可。
通过一个栗子来使用一把Room框架
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
2、Room的三大角色
Entity
一个Entity对应一张表,要创建一个User表,只需要在类上加@Entity注解就可以了,主键、列名都可以通过注解来定义出来,这里需要注意,变量不能定义为val,也不能定义为private,否则都会抛出异常~
@Entity(tableName = "User") // tableName是表名,不设置则与类名相同
class User() {
constructor(s: String, a: Int) : this() {
name = s
age = a
}
constructor(i: Int, s: String, a: Int) : this() {
uid = i
name = s
age = a
}
// 主键使用@PrimaryKey注解,autoGenerate是否自增长
@PrimaryKey(autoGenerate = true)
var uid: Int? = null
// 列名使用@ColumnInfo注解,name是别名,不设置则表中名字与字段名相同
@ColumnInfo(name = "name")
var name: String? = null
@ColumnInfo(name = "age")
var age: Int? = null
override fun toString(): String {
return "User(uid=$uid, name=$name, age=$age)"
}
}
DAO
DAO层定义为接口类,这样在用户调用时,实际调用的是实现类,实现类有Room为我们自动生成。在接口类中定义出增删改查方法,其中查询方法还是需要我们自己来写SQL语句。
@Dao
interface UserDao {
@Insert
fun insert(vararg users: User)
@Delete
fun delete(vararg users: User?)
@Update
fun update(user: User)
@Query("select * from User")
fun getAll(): MutableList
@Query("select * from User where name like :name")
fun findByName(name: String): MutableList
}
Database
创建一个抽象类来继承RoomDatabase,创建抽象类的目的同样是,让Room生成子类来实现功能。定义一个抽象方法来暴露DAO。
@Database注解带3个参数,entities是所有的Entity对象,也就是所有表,version为数据库版本,exportSchema=false为导出模式必须写上去,这个是规范代码
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao?
}
3、初始化并使用
在Activity中使用如下代码测试Room数据库的使用
val myDB = Room.databaseBuilder(applicationContext, AppDatabase::class.java,
"myDB").build()
val dao = myDB.userDao()
//数据库操作一般要在子线程中
thread {
val user1 = User("张三", 12)
val user2 = User("李四", 22)
val user3 = User("王五", 18)
val user4 = User("痦子六", 23)
dao?.insert(user1, user2, user3, user4)
var allUser = dao?.getAll()
Log.d(TAG, allUser.toString())
val user = dao?.findByName("张三")
Log.d(TAG, user.toString())
dao?.delete(user?.get(0))
allUser = dao?.getAll()
Log.d(TAG, allUser.toString())
}
打印出来结果如下,正常实现了功能
D/RoomActivity: [User(uid=1, name=张三, age=12), User(uid=2, name=李四, age=22), User(uid=3, name=王五, age=18), User(uid=4, name=痦子六, age=23)] D/RoomActivity: [User(uid=1, name=张三, age=12)] D/RoomActivity: [User(uid=2, name=李四, age=22), User(uid=3, name=王五, age=18), User(uid=4, name=痦子六, age=23)]三、Room配合LiveData使用
在DAO中新增一个方法,返回LiveData类型,包裹原类型。使用时调用此方法,并添加观察者。这里介绍的是Room最基础的用法,在实际应用中,一般还要结合ViewModel来使用,并且多封装一层Repository。
@Dao
interface UserDao {
@Query("select * from User")
fun getAllLiveData(): LiveData>
}
val myDB = Room.databaseBuilder(applicationContext, AppDatabase::class.java,
"myDB").build()
val dao = myDB.userDao()
// 观察者数据库的数据,只要敢变,就打印
dao?.getAllLiveData()?.observe(this, {
Log.d(TAG, it.toString())
})
//数据库操作一般要在子线程中
thread {
val user1 = User("张三", 12)
val user2 = User("李四", 22)
val user3 = User("王五", 18)
val user4 = User("痦子六", 23)
dao?.insert(user1, user2, user3, user4)
val user = dao?.findByName("张三")
Log.d(TAG, user.toString())
// 模拟数据被修改了,一旦数据库被修改,那么数据会驱动UI的发生改变
for (i in 0..50) {
Thread.sleep(3000)
dao?.update(User(2, "孙七$i", i))
val allUser = dao?.getAll()
Log.d(TAG, allUser.toString())
}
}
四、Room源码分析
Room大量使用APT注解处理器,通过注解在运行时生成代码和SQL语句,从而简化开发。实际上,大部分ORM框架都是这么做的,Room在运行时生成了两个具体的实现类,AppDatabase_Impl和UserDao_Impl。
1、AppDatabase_Impl首先我们从build()方法创建数据库来分析
val myDB = Room.databaseBuilder(applicationContext, AppDatabase::class.java,
"myDB").build()
进入build()方法,在RoomDatabase中,设置了各种参数,我们先只关注主线流程,可以看到最终调用了init()方法,并返回db。
public T build() {
...
db.init(configuration);
return db;
}
继续跟进init()方法
public void init(@NonNull DatabaseConfiguration configuration) {
mOpenHelper = createOpenHelper(configuration);
...
}
调用了createOpenHelper方法,createOpenHelper方法实现在AppDatabase_Impl中,创建了RoomOpenHelper,RoomOpenHelper继承SupportSQLiteOpenHelper.Callback,可以看出,Room就是对原生SQLite的封装,里面有初始化和数据库升级灯操作。
final SupportSQLiteOpenHelper.Callback _openCallback = new
RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
...
}
同时,AppDatabase_Impl还会为我们暴露一个UserDao,这也就是为什么需要定义成抽象方法,具体实现new的操作都有APT生成的代码来做了。
@Override
public UserDao userDao() {
if (_userDao != null) {
return _userDao;
} else {
synchronized(this) {
if(_userDao == null) {
_userDao = new UserDao_Impl(this);
}
return _userDao;
}
}
}
2、UserDao_Impl
AppDatabase_Impl看完以后,再来看一下UserDao_Impl类,进入UserDao_Impl后可以看到,UserDao_Impl具体实现了我们在DAO中定义的所有添加了注解的方法,并帮我们拼接生成了对应的SQL语句
@Override
public void insert(final User... users) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__insertionAdapterOfUser.insert(users);
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
@Override public ListgetAll() { final String _sql = "select * from User"; final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0); __db.assertNotSuspendingTransaction(); final Cursor _cursor = DBUtil.query(__db, _statement, false, null); try { final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid"); final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "name"); final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "age"); final List _result = new ArrayList (_cursor.getCount()); while(_cursor.moveToNext()) { final User _item; _item = new User(); final Integer _tmpUid; if (_cursor.isNull(_cursorIndexOfUid)) { _tmpUid = null; } else { _tmpUid = _cursor.getInt(_cursorIndexOfUid); } _item.setUid(_tmpUid); final String _tmpName; if (_cursor.isNull(_cursorIndexOfName)) { _tmpName = null; } else { _tmpName = _cursor.getString(_cursorIndexOfName); } _item.setName(_tmpName); final Integer _tmpAge; if (_cursor.isNull(_cursorIndexOfAge)) { _tmpAge = null; } else { _tmpAge = _cursor.getInt(_cursorIndexOfAge); } _item.setAge(_tmpAge); _result.add(_item); } return _result; } finally { _cursor.close(); _statement.release(); } }
到这里,Room就已经可以作为一个标准的ORM框架了,增删改查全部通过注解标记,由APT动态生成实现类,每个添加注解的方法都生成对应的SQL并执行。
3、Room与LiveData配合前面提到过,Room框架只有结合LiveData等Jetpack全家桶才能发挥出它的优势,当我们定义了一个LiveData接收数据库查询结果时,当数据库的数据发生变化,我们可以感知到数据的变化,这个又是如何做到的呢,我们来跟进一下之前定义的getAllLiveData()方法,在UserDao中定义,在UserDao_Impl中找到对应的实现
@Override public LiveData> getAllLiveData() { final String _sql = "select * from User"; final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0); return __db.getInvalidationTracker().createLiveData(new String[]{"User"}, false, new Callable
>() { @Override public List
call() throws Exception { ... } ... }); }
可以看到,调用了createLiveData()方法,并监听了它的回调,在call()回调方法中可以看到,就是去执行数据库操作,并返回结果。
createLiveData()方法最终会调用到RoomTrackingLiveData中,在构造方法中初始化了一个观察者mObserver
RoomTrackingLiveData(
...
Callable computeFunction,
String[] tableNames) {
...
mComputeFunction = computeFunction;
mContainer = container;
mObserver = new InvalidationTracker.Observer(tableNames) {
@Override
public void onInvalidated(@NonNull Set tables) {
ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
}
};
}
在onActive()方法中执行了mRefreshRunnable,这里的Active状态又是用到了Lifecycle中的mActive状态
protected void onActive() {
super.onActive();
mContainer.onActive(this);
getQueryExecutor().execute(mRefreshRunnable);
}
mRefreshRunnable中可以看到这些关键代码
while (mInvalid.compareAndSet(true, false)) {
computed = true;
try {
value = mComputeFunction.call();
} catch (Exception e) {
throw new RuntimeException("Exception while computing database"+
" live data.", e);
}
}
if (computed) {
postValue(value);
}
value就是通过前面提到的,回调里面数据库查询到的值,最终调用postValue()来更新LiveData,至此,Room框架与LiveData之间的关联就建立起来了。



