一、简介
1、Room是什么
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基本用法
Room的使用,主要涉及以下3个组件 1、Database:访问底层数据库的入口 2、Entity:代表数据库中的表(table),一般用注解 3、DAO(Data Access Object):数据库访问者 这三个组件的概念也出现在其他ORM框架中,通过Database获取DAO,然后通过DAO查询并获取entities,最终通过entities对数据库table中数据进行读写,用户只需要面向DAO即可。 通过一个栗子来使用一把Room框架
1、引入依赖
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")
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 = true)
var uid: Int? = null
@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<User>
@Query("select * from User where name like :name")
fun findByName(name: String): MutableList<User>
}
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<MutableList<User>>
}
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())
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 List<User> getAll() {
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<User> _result = new ArrayList<User>(_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<List<User>> getAllLiveData() {
final String _sql = "select * from User";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
return __db.getInvalidationTracker().createLiveData(new String[]{"User"},
false, new Callable<List<User>>() {
@Override
public List<User> call() throws Exception {
...
}
...
});
}
可以看到,调用了createLiveData() 方法,并监听了它的回调,在call() 回调方法中可以看到,就是去执行数据库操作,并返回结果。 createLiveData() 方法最终会调用到RoomTrackingLiveData 中,在构造方法中初始化了一个观察者mObserver
RoomTrackingLiveData(
...
Callable<T> computeFunction,
String[] tableNames) {
...
mComputeFunction = computeFunction;
mContainer = container;
mObserver = new InvalidationTracker.Observer(tableNames) {
@Override
public void onInvalidated(@NonNull Set<String> 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之间的关联就建立起来了。
|