原文連結:https://medium.com/androiddevelopers/room-coroutines-422b786dc4c5
作者:Florina Muntenescu
Room
從2.1版本(目前已更新到2.2.0-alpha03版本)開始添加了對
kotlin
協程的支援。現在我們可以使用
suspend
關鍵字将
DAO
中的方法聲明為挂起函數,進而保證這些方法不在主線程中執行。請繼續閱讀以了解如何在
Room
中使用協程,它的工作原理,以及如何測試這個新功能。
給你的資料庫添加suspend特性
如果想在你的APP中使用協程來操作
Room資料庫
,那麼必須将項目中的
Room
版本更新為2.1版本,同時在
build.gradle
檔案添加如下依賴:
implementation "androidx.room:room-coroutines:${versions.room}"
另外你的
kotlin
版本至少為
1.3.0
,
Coroutines
版本至少為
1.0.0
現在你可以将
DAO
中的方法使用
suspend
關鍵字将其定義為挂起函數了
//具有suspend方法的DAO代碼示例
@Dao
interface UsersDao {
@Query("SELECT * FROM users")
suspend fun getUsers(): List<User>
@Query("UPDATE users SET age = age + 1 WHERE userId = :userId")
suspend fun incrementUserAge(userId: String)
@Insert
suspend fun insertUser(user: User)
@Update
suspend fun updateUser(user: User)
@Delete
suspend fun deleteUser(user: User)
}
@Transaction
聲明的方法也可以定義為挂起函數,同時它也可以調用
DAO
中其他的挂起函數
//具有事務挂起功能的DAO代碼示例
@Dao
abstract class UsersDao {
@Transaction
open suspend fun setLoggedInUser(loggedInUser: User) {
deleteUser(loggedInUser)
insertUser(loggedInUser)
}
@Query("DELETE FROM users")
abstract fun deleteUser(user: User)
@Insert
abstract suspend fun insertUser(user: User)
}
你也可以在事務中調用不同
DAO
的挂起函數
//在一個事務中調用兩個不同DAO的挂起函數
class Repository(val database: MyDatabase) {
suspend fun clearData(){
database.withTransaction {
database.userDao().deleteLoggedInUser() // suspend function
database.commentsDao().deleteComments() // suspend function
}
}
}
預設情況
Room
會使用架構元件的
IO Executor
來執行SQL語句,但我們也可以在建構
Room
資料庫的時候,通過調用
setTransactionExecutor
和
setQueryExecutor
這兩個方法來自定義執行SQL語句的
Executor
測試DAO中的挂起函數
測試
DAO
中的挂起函數和測試其他普通的挂起函數沒有什麼不同。
例如我們要測試使用者資訊被插入後能夠被檢索到,我們可以将測試代碼塊放在
runBlock
塊中:
@Test
fun insertAndGetUser() = runBlocking {
// Given a User that has been inserted into the DB
userDao.insertUser(user)
// When getting the Users via the DAO
val usersFromDb = userDao.getUsers()
// Then the retrieved Users matches the original user object
assertEquals(listOf(user), userFromDb)
}
工作原理
為了了解
Room
支援協程的實作原理,我們來看看
Room
為同步方法和挂起函數生成的
DAO
方法實作
//DAO中同步和挂起函數的定義插入操作
@Insert
fun insertUserSync(user: User)
@Insert
suspend fun insertUser(user: User)
對于同步插入,生成的代碼首先開啟了一個事務,接着執行資料插入操作,然後标記事務成功,最後結束事務。生成的代碼如下:
@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<? 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);
}
上面的生成代碼確定了插入操作不會在
UI線程
中執行。
在生成的
suspend
函數的中,傳入了一個
Continuation
和待插入的資料,同時它的插入邏輯和同步插入邏輯相同,隻是它的插入邏輯被封裝在
Callable
中。
另外我們可以看到,生成的函數一開始會調用
CoroutinesRoom.execute
函數,實際上該函數會根據資料庫是否打開,是否處于事務來決定如何切換上下文。
CoroutinesRoom.execute
方法實作如下:
@JvmStatic
suspend fun <R> execute(
db: RoomDatabase,
inTransaction: Boolean,
callable: Callable<R>
): R {
if (db.isOpen && db.inTransaction()) {
return callable.call()
}
// Use the transaction dispatcher if we are on a transaction coroutine, otherwise
// use the database dispatchers.
val context = coroutineContext[TransactionElement]?.transactionDispatcher
?: if (inTransaction) db.transactionDispatcher else db.queryDispatcher
return withContext(context) {
callable.call()
}
}
情況1:資料庫打開且處于事務中
這種情況會直接執行
Callable.call()
方法,也就是資料庫的實際插入操作。
這種情況下
Room
不會對操作資料庫的協程上下文做任何處理,是以調用者需要自己確定調用
Room
操作的協程上下文環境不是
Dispatcher.Main
情況2:非事務
這種情況下
Room
會確定
Callable.call()
操作是在背景線程中完成的。
對于事務和查詢
Room
會采用不同的
Dispatchers
,這些
Dispatchers
來源于建構
Room資料庫
時我們自己自定義的
Dispatchers
,或者是系統預設提供的架構元件
IO Executor
,這和使
LiveData
處于背景運作的
Dispatchers
是一樣的。
如果有興趣研究具體的實作原理,可以檢視CoroutinesRoom.kt和RoomDatabase.kt的源碼
在你的APP中使用
Room
和協程吧,保證資料庫的操作在
non-UI Dispatcher
中執行。使用
suspend
将
DAO
中的方法定義為挂起函數,并從其他挂起函數或協程中調用他們。
下面是我的個人公衆号,歡迎關注交流
