Android协程——Room&Coroutines

在Room2.1版本中提供了对协程的支持。Dao层的方法可以被suspend标记来确保他们在主线程中被执行。接下来,我们就来看看如何使用并为它写一个简单的单元测试。

为你的数据库加点suspending

首先我们要为项目加上Room的依赖,并确保版本在2.1及以上。

implementation "androidx.room:room-coroutines:${versions.room}"

同时,我们的还需要Kotlin版本在1.3.0以上,以及Coroutines 1.0.0以上。

接下来我们可以编写如下的DAO层,使用suspend标记方法。

@Dao
interface UsersDao{
    @Query
    suspend fun getUsers(): List
    
    @Query
    suspend fun incrementUserAge(userId: String)
    
    @Insert
    suspend fun insertUser(user: User)
    
    @Update
    suspend fun updateUser(user: User)
    
    @Delete
    suspend fun deleteUser(user: User)
}

被@Transacition注解的方法也可以使用suspend关键字标记,并且可以调用其他被suspend标记的DAO层方法。

@Dao
abstract class UsersDao{
    @Transaction
    open suspend fun setLoggedInUser(loggerInUser: User){
        deleteUser(loggedInUser)
        insertUser(loggedInUser)
    }
    
    @Query("DELETE FROM users")
    abstract fun deleteUser(user: User)

    @Insert
    abstract suspend fun insertUser(user: User)
}

同样,你也可以从不同的DAO中调用suspend方法。

class Repository(val database: MyDatabase) {
    
    suspend fun clearData(){
        database.withTransaction {
            database.userDao().deleteLoggerInUser()
            database.commentsDao().deleteComments()
        }
    }
}

你可以在创建数据库时通过调用setTransactionExecutor()方法或setQueryExecutor()方法来提供executors去控制它们运行的线程。默认情况下,这将是用于在后台线程上运行查询的相同执行程序。

编写单元测试

测试DAO suspend方法和测试其它的suspend方法是一样的。例如,要检查插入用户后我们能够检索它,我们将测试包装在runBlocking块中:

@Test
fun insertAndGetUser() = runBlocking {
    //提供一个插入到数据库中的User
    userDao.insertUser(user)
    
    // 通过DAO获取Users
    val usersFromDb = userDao.getUsers()
    
    //验证
    assertEquals(listOf(user), userFromDb)
}

再深入一点去看

进一步,让我们来看看为同步和暂停插入生成的DAO类实现:

@Insert
fun insertUserSync(user: User)

@Insert
suspend fun insertUser(user: User)

在同步插入的方式中,生成的代码启动事务,执行插入,将事务标记为成功并结束它。同步方法只在任何调用它的线程上的执行insert。

@Override 
public void insertUserSync(final User user) {
    _db.beginTransaction();
    try{
        _insertionAdapterOfUser.insert(user);
        _db.setTransactionSuccessful();
    } finally {
        _db.endTransaction();
    }
}

我们再来看看使用suspend关键字的方法是如何处理的。

@Override
public Object insertUserSuspend(final User user,
                               final Continuation p1){
    
    return CoroutinesRoom.execute(_db,new Callable(){
        @Override
        public Unit call() throws Exception {
            _db.beginTransaction();
            try {
                _insertionAdapterOfUser.insert(user);
                _db.setTransactionSuccessful();
                return kotlin.Unit.INSTANCE;
            } finally {
                _db.endTransaction();
            }
        }
    }, p1);
}

生成的代码中确保了插入不发生在UI线程上。在我们的suspend函数实现中,同步insert方法中的相同逻辑包含在Callable中。Room调用CoroutinesRoom.execute挂起函数,该函数切换到后台调度程序,具体取决于数据库是否已打开且我们是否处于事务中。这是函数的实现:

@JvmStatic
suspend fun  execute(
    db: RoomDatabase,
    inTransaction: Boolean,
    callable: Callable
): R {
    if (db.isOpen && db.inTransaction) {
        return callable.call()
    }
    
    val context = coroutineContext[TransactionElement]?.transactionDispatcher
        ?: if (inTransaction) db.transactionDispatcher else db.queryDispatcher
    return withContext(context) {
        callable.call()
    }
}

情形一:数据库打开,且我们在事务中

此时我们直接执行callable即可

情形二:其他

Room确保Callable中的工作已完成(call方法在后台执行)。

Room会使用不同的dispatcher来处理事务和查询。这些是从构建数据库时提供的执行程序派生的,或者默认情况下将使用系统组件IO执行程序,这和LiveData执行后台任务的executor是一样的。

在应用程序中开始使用Room和coroutines,保证数据库工作在非UI Dispatcher上运行。使用suspend修饰符标记您的DAO方法,并从其他挂起函数或协程中调用它们!

你可能感兴趣的:(Android协程——Room&Coroutines)