在Room2.1版本中提供了對(duì)協(xié)程的支持。Dao層的方法可以被suspend標(biāo)記來確保他們?cè)谥骶€程中被執(zhí)行。接下來,我們就來看看如何使用并為它寫一個(gè)簡單的單元測試。
Demo地址:https://github.com/jotyy/coroutines-retrofit-example 歡迎交流和star,謝謝
為你的數(shù)據(jù)庫加點(diǎn)suspending
首先我們要為項(xiàng)目加上Room的依賴,并確保版本在2.1及以上。
implementation "androidx.room:room-coroutines:${versions.room}"
同時(shí),我們的還需要Kotlin版本在1.3.0以上,以及Coroutines 1.0.0以上。
接下來我們可以編寫如下的DAO層,使用suspend標(biāo)記方法。
@Dao
interface UsersDao{
@Query
suspend fun getUsers(): List<User>
@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關(guān)鍵字標(biāo)記,并且可以調(diào)用其他被suspend標(biāo)記的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中調(diào)用suspend方法。
class Repository(val database: MyDatabase) {
suspend fun clearData(){
database.withTransaction {
database.userDao().deleteLoggerInUser()
database.commentsDao().deleteComments()
}
}
}
你可以在創(chuàng)建數(shù)據(jù)庫時(shí)通過調(diào)用setTransactionExecutor()方法或setQueryExecutor()方法來提供executors去控制它們運(yùn)行的線程。默認(rèn)情況下,這將是用于在后臺(tái)線程上運(yùn)行查詢的相同執(zhí)行程序。
編寫單元測試
測試DAO suspend方法和測試其它的suspend方法是一樣的。例如,要檢查插入用戶后我們能夠檢索它,我們將測試包裝在runBlocking塊中:
@Test
fun insertAndGetUser() = runBlocking {
//提供一個(gè)插入到數(shù)據(jù)庫中的User
userDao.insertUser(user)
// 通過DAO獲取Users
val usersFromDb = userDao.getUsers()
//驗(yàn)證
assertEquals(listOf(user), userFromDb)
}
再深入一點(diǎn)去看
進(jìn)一步,讓我們來看看為同步和暫停插入生成的DAO類實(shí)現(xiàn):
@Insert
fun insertUserSync(user: User)
@Insert
suspend fun insertUser(user: User)
在同步插入的方式中,生成的代碼啟動(dòng)事務(wù),執(zhí)行插入,將事務(wù)標(biāo)記為成功并結(jié)束它。同步方法只在任何調(diào)用它的線程上的執(zhí)行insert。
@Override
public void insertUserSync(final User user) {
_db.beginTransaction();
try{
_insertionAdapterOfUser.insert(user);
_db.setTransactionSuccessful();
} finally {
_db.endTransaction();
}
}
我們?cè)賮砜纯词褂胹uspend關(guān)鍵字的方法是如何處理的。
@Override
public Object insertUserSuspend(final User user,
final Continuation<? super Unit> p1){
return CoroutinesRoom.execute(_db,new Callable<Unit>(){
@Override
public Unit call() throws Exception {
_db.beginTransaction();
try {
_insertionAdapterOfUser.insert(user);
_db.setTransactionSuccessful();
return kotlin.Unit.INSTANCE;
} finally {
_db.endTransaction();
}
}
}, p1);
}
生成的代碼中確保了插入不發(fā)生在UI線程上。在我們的suspend函數(shù)實(shí)現(xiàn)中,同步insert方法中的相同邏輯包含在Callable中。Room調(diào)用CoroutinesRoom.execute掛起函數(shù),該函數(shù)切換到后臺(tái)調(diào)度程序,具體取決于數(shù)據(jù)庫是否已打開且我們是否處于事務(wù)中。這是函數(shù)的實(shí)現(xiàn):
@JvmStatic
suspend fun <R> execute(
db: RoomDatabase,
inTransaction: Boolean,
callable: Callable<R>
): 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()
}
}
情形一:數(shù)據(jù)庫打開,且我們?cè)谑聞?wù)中
此時(shí)我們直接執(zhí)行callable即可
情形二:其他
Room確保Callable中的工作已完成(call方法在后臺(tái)執(zhí)行)。
Room會(huì)使用不同的dispatcher來處理事務(wù)和查詢。這些是從構(gòu)建數(shù)據(jù)庫時(shí)提供的執(zhí)行程序派生的,或者默認(rèn)情況下將使用系統(tǒng)組件IO執(zhí)行程序,這和LiveData執(zhí)行后臺(tái)任務(wù)的executor是一樣的。
在應(yīng)用程序中開始使用Room和coroutines,保證數(shù)據(jù)庫工作在非UI Dispatcher上運(yùn)行。使用suspend修飾符標(biāo)記您的DAO方法,并從其他掛起函數(shù)或協(xié)程中調(diào)用它們!