上手使用 Room Kotlin API
Room 是 SQLite 的封装,它使 Android 对数据库的操作变得非常简单,也是迄今为止我最喜欢的 Jetpack 库。在本文中我会告诉大家如何使用并且测试 Room Kotlin API,同时在介绍过程中,我也会为大家分享其工作原理。
我们将基于 Room with a view codelab 为大家讲解。这里我们会创建一个存储在数据库的词汇表,然后将它们显示到屏幕上,同时用户还可以向列表中添加单词。
Room
https://developer.android.google.cn/training/data-storage/roomRoom with a view codelab
https://developer.android.google.cn/codelabs/android-room-with-a-view-kotlin#0
定义数据库表
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@Entity(tableName = "word_table")
data class Word(@PrimaryKey @ColumnInfo(name = "word") val word: String)
我们推荐大家使用 @ColumnInfo 注解,因为它可以使您更灵活地对成员进行重命名而无需同时修改数据库的列名。因为修改列名会涉及到修改数据库模式,因而您需要实现数据迁移。
访问表中的数据
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@Dao
interface WordDao {
@Query("SELECT * FROM word_table ORDER BY word ASC")
fun getAlphabetizedWords(): Flow<List<Word>>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(word: Word)
}
协程的相关基本概念 https://youtu.be/bM7PVVL_5GM
Flow 相关的内容 https://youtu.be/emk9_tVVLcc
插入数据
@Insert
suspend fun insert(word: Word)
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@Override
public Object insert(final Word word, final Continuation<? super Unit> p1) {
return CoroutinesRoom.execute(__db, true, new Callable<Unit>() {
@Override
public Unit call() throws Exception {
__db.beginTransaction();
try {
__insertionAdapterOfWord.insert(word);
__db.setTransactionSuccessful();
return Unit.INSTANCE;
} finally {
__db.endTransaction();
}
}
}, p1);
}
实现
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt;l=47?q=CoroutinesRoom
查询数据
我们希望当数据库中的数据发生改变的时候,能够得到相应的通知,所以我们返回一个 Flow<List<Word>>。由于返回类型是 Flow,Room 会在后台线程中执行数据请求。
@Query(“SELECT * FROM word_table ORDER BY word ASC”)
fun getAlphabetizedWords(): Flow<List<Word>>
在底层,Room 生成了 getAlphabetizedWords():
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@Override
public Flow<List<Word>> getAlphabetizedWords() {
final String _sql = "SELECT * FROM word_table ORDER BY word ASC";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
return CoroutinesRoom.createFlow(__db, false, new String[]{"word_table"}, new Callable<List<Word>>() {
@Override
public List<Word> call() throws Exception {
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfWord = CursorUtil.getColumnIndexOrThrow(_cursor, "word");
final List<Word> _result = new ArrayList<Word>(_cursor.getCount());
while(_cursor.moveToNext()) {
final Word _item;
final String _tmpWord;
_tmpWord = _cursor.getString(_cursorIndexOfWord);
_item = new Word(_tmpWord);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
}
}
@Override
protected void finalize() {
_statement.release();
}
});
}
我们可以看到代码里调用了 CoroutinesRoom.createFlow(),它包含四个参数: 数据库、一个用于标识我们是否正处于事务中的变量、一个需要监听的数据库表的列表 (在本例中列表里只有 word_table) 以及一个 Callable 对象。Callable.call() 包含需要被触发的查询的实现代码。
如果我们看一下 CoroutinesRoom.createFlow() 的实现代码,会发现这里同数据请求调用一样使用了不同的 CoroutineContext。同数据插入调用一样,这里的分发器来自构建数据库时您所提供的执行器,或者来自默认使用的 Architecture Components IO 执行器。
实现代码
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt;l=99?q=CoroutinesRoom
创建数据库
我们已经定义了存储在数据库中的数据以及如何访问他们,现在我们来定义数据库。要创建数据库,我们需要创建一个抽象类,它继承自 RoomDatabase,并且添加 @Database 注解。将 Word 作为需要存储的实体元素传入,数值 1 作为数据库版本。
我们还会定义一个抽象方法,该方法返回一个 WordDao 对象。所有这些都是抽象类型的,因为 Room 会帮我们生成所有的实现代码。就像这里,有很多逻辑代码无需我们亲自实现。
最后一步就是构建数据库。我们希望能够确保不会有多个同时打开的数据库实例,而且还需要应用的上下文来初始化数据库。一种实现方法是在类中添加伴生对象,并且在其中定义一个 RoomDatabase 实例,然后在类中添加 getDatabase 函数来构建数据库。如果我们希望 Room 查询不是在 Room 自身创建的 IO Executor 中执行,而是在另外的 Executor 中执行,我们需要通过调用 setQueryExecutor() 将新的 Executor 传入 builder。
setQueryExecutor()
https://developer.android.google.cn/reference/androidx/room/RoomDatabase.Builder#setQueryExecutor(java.util.concurrent.Executor)
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
companion object {
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context): WordRoomDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordRoomDatabase::class.java,
"word_database"
).build()
INSTANCE = instance
// 返回实例
instance
}
}
}
测试 Dao
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@RunWith(AndroidJUnit4::class)
class WordDaoTest {
private lateinit var wordDao: WordDao
private lateinit var db: WordRoomDatabase
@Before
fun createDb() {
val context: Context = ApplicationProvider.getApplicationContext()
// 由于当进程结束的时候会清除这里的数据,所以使用内存数据库
db = Room.inMemoryDatabaseBuilder(context, WordRoomDatabase::class.java)
// 可以在主线程中发起请求,仅用于测试。
.allowMainThreadQueries()
.build()
wordDao = db.wordDao()
}
@After
@Throws(IOException::class)
fun closeDb() {
db.close()
}
...
}
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@Test
@Throws(Exception::class)
fun insertAndGetWord() = runBlocking {
val word = Word("word")
wordDao.insert(word)
val allWords = wordDao.getAlphabetizedWords().first()
assertEquals(allWords[0].word, word.word)
}
Date 类型
https://medium.com/androiddevelopers/room-time-2b4cf9672b98创建数据库视图
https://developer.android.google.cn/training/data-storage/room/creating-views
Room 官方文档
https://developer.android.google.cn/training/data-storage/roomRoom with a view codelab
https://developer.android.google.cn/codelabs/android-room-with-a-view-kotlin#0
推荐阅读