Jetpack-Room框架使用与源码分析

一、简介

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):数据库访问者
dao
这三个组件的概念也出现在其他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") // 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<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())

    // 模拟数据被修改了,一旦数据库被修改,那么数据会驱动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_ImplUserDao_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中,创建了RoomOpenHelperRoomOpenHelper继承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之间的关联就建立起来了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值