Room Kotlin API的使用入門教程
Room 是 SQLite 的封裝,它使 Android 對數(shù)據(jù)庫的操作變得非常簡單,也是迄今為止我最喜歡的 Jetpack 庫。在本文中我會告訴大家如何使用并且測試 Room Kotlin API,同時在介紹過程中,我也會為大家分享其工作原理。
我們將基于 Room with a view codelab 為大家講解。這里我們會創(chuàng)建一個存儲在數(shù)據(jù)庫的詞匯表,然后將它們顯示到屏幕上,同時用戶還可以向列表中添加單詞。
定義數(shù)據(jù)庫表
在我們的數(shù)據(jù)庫中僅有一個表,就是保存詞匯的表。Word 類代表表中的一條記錄,并且它需要使用注解 @Entity。我們使用 @PrimaryKey 注解為表定義主鍵。然后,Room 會生成一個 SQLite 表,表名和類名相同。每個類的成員對應(yīng)表中的列。列名和類型與類中每個字段的名稱和類型一致。如果您希望改變列名而不使用類中的變量名稱作為列名,可以通過 @ColumnInfo 注解來修改。
/* 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 注解,因?yàn)樗梢允鼓`活地對成員進(jìn)行重命名而無需同時修改數(shù)據(jù)庫的列名。因?yàn)樾薷牧忻麜婕暗叫薷臄?shù)據(jù)庫模式,因而您需要實(shí)現(xiàn)數(shù)據(jù)遷移。
訪問表中的數(shù)據(jù)
如需訪問表中的數(shù)據(jù),需要創(chuàng)建一個數(shù)據(jù)訪問對象 (DAO)。也就是一個叫做 WorkDao 的接口,它會帶有 @Dao 注解。我們希望通過它實(shí)現(xiàn)表級別的數(shù)據(jù)插入、刪除和獲取,所以數(shù)據(jù)訪問對象中會定義相應(yīng)的抽象方法。操作數(shù)據(jù)庫屬于比較耗時的 I/O 操作,所以需要在后臺線程中完成。我們將把 Room 與 Kotlin 協(xié)程和 Flow 相結(jié)合來實(shí)現(xiàn)上述功能。
/* 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) }
我們在視頻 Kotlin Vocabulary 中介紹了 協(xié)程的相關(guān)基本概念, 在 Kotlin Vocabulary 另一個視頻中則介紹了 Flow 相關(guān)的內(nèi)容。
插入數(shù)據(jù)
要實(shí)現(xiàn)插入數(shù)據(jù)的操作,首先創(chuàng)建一個抽象的掛起函數(shù),需要插入的單詞作為它的參數(shù),并且添加 @Insert 注解。Room 會生成將數(shù)據(jù)插入數(shù)據(jù)庫的全部操作,并且由于我們將函數(shù)定義為可掛起,所以 Room 會將整個操作過程放在后臺線程中完成。因此,該掛起函數(shù)是主線程安全的,也就是在主線程可以放心調(diào)用而不必?fù)?dān)心阻塞主線程。
@Insert suspend fun insert(word: Word)
在底層 Room 生成了 Dao 抽象函數(shù)的實(shí)現(xiàn)代碼。下面代碼片段就是我們的數(shù)據(jù)插入方法的具體實(shí)現(xiàn):
/* 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); }
CoroutinesRoom.execute() 函數(shù)被調(diào)用,里面包含三個參數(shù): 數(shù)據(jù)庫、一個用于表示是否正處于事務(wù)中的標(biāo)識、一個 Callable 對象。Callable.call() 包含處理數(shù)據(jù)庫插入數(shù)據(jù)操作的代碼。
如果我們看一下 CoroutinesRoom.execute() 的 實(shí)現(xiàn),我們會看到 Room 將 callable.call() 移動到另外一個 CoroutineContext。該對象來自構(gòu)建數(shù)據(jù)庫時您所提供的執(zhí)行器,或者默認(rèn)使用 Architecture Components IO Executor。
查詢數(shù)據(jù)
為了能夠查詢表數(shù)據(jù),我們這里創(chuàng)建一個抽象函數(shù),并且為其添加 @Query 注解,注解后緊跟 SQL 請求語句: 該語句從單詞數(shù)據(jù)表中請求全部單詞,并且以字母順序排序。
我們希望當(dāng)數(shù)據(jù)庫中的數(shù)據(jù)發(fā)生改變的時候,能夠得到相應(yīng)的通知,所以我們返回一個 Flow<List<Word>>。由于返回類型是 Flow,Room 會在后臺線程中執(zhí)行數(shù)據(jù)請求。
@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(); } }); }
我們可以看到代碼里調(diào)用了 CoroutinesRoom.createFlow(),它包含四個參數(shù): 數(shù)據(jù)庫、一個用于標(biāo)識我們是否正處于事務(wù)中的變量、一個需要監(jiān)聽的數(shù)據(jù)庫表的列表 (在本例中列表里只有 word_table) 以及一個 Callable 對象。Callable.call() 包含需要被觸發(fā)的查詢的實(shí)現(xiàn)代碼。
如果我們看一下 CoroutinesRoom.createFlow() 的 實(shí)現(xiàn)代碼,會發(fā)現(xiàn)這里同數(shù)據(jù)請求調(diào)用一樣使用了不同的 CoroutineContext。同數(shù)據(jù)插入調(diào)用一樣,這里的分發(fā)器來自構(gòu)建數(shù)據(jù)庫時您所提供的執(zhí)行器,或者來自默認(rèn)使用的 Architecture Components IO 執(zhí)行器。
創(chuàng)建數(shù)據(jù)庫
我們已經(jīng)定義了存儲在數(shù)據(jù)庫中的數(shù)據(jù)以及如何訪問他們,現(xiàn)在我們來定義數(shù)據(jù)庫。要創(chuàng)建數(shù)據(jù)庫,我們需要創(chuàng)建一個抽象類,它繼承自 RoomDatabase,并且添加 @Database 注解。將 Word 作為需要存儲的實(shí)體元素傳入,數(shù)值 1 作為數(shù)據(jù)庫版本。
我們還會定義一個抽象方法,該方法返回一個 WordDao 對象。所有這些都是抽象類型的,因?yàn)?Room 會幫我們生成所有的實(shí)現(xiàn)代碼。就像這里,有很多邏輯代碼無需我們親自實(shí)現(xiàn)。
最后一步就是構(gòu)建數(shù)據(jù)庫。我們希望能夠確保不會有多個同時打開的數(shù)據(jù)庫實(shí)例,而且還需要應(yīng)用的上下文來初始化數(shù)據(jù)庫。一種實(shí)現(xiàn)方法是在類中添加伴生對象,并且在其中定義一個 RoomDatabase 實(shí)例,然后在類中添加 getDatabase 函數(shù)來構(gòu)建數(shù)據(jù)庫。如果我們希望 Room 查詢不是在 Room 自身創(chuàng)建的 IO Executor 中執(zhí)行,而是在另外的 Executor 中執(zhí)行,我們需要通過調(diào)用 setQueryExecutor() 將新的 Executor 傳入 builder。
/* 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 // 返回實(shí)例 instance } } }
測試 Dao
為了測試 Dao,我們需要實(shí)現(xiàn) AndroidJUnit 測試來讓 Room 在設(shè)備上創(chuàng)建 SQLite 數(shù)據(jù)庫。
當(dāng)實(shí)現(xiàn) Dao 測試的時候,在每個測試運(yùn)行之前,我們創(chuàng)建數(shù)據(jù)庫。當(dāng)每個測試運(yùn)行后,我們關(guān)閉數(shù)據(jù)庫。由于我們并不需要在設(shè)備上存儲數(shù)據(jù),當(dāng)創(chuàng)建數(shù)據(jù)庫的時候,我們可以使用內(nèi)存數(shù)據(jù)庫。也因?yàn)檫@僅僅是個測試,我們可以在主線程中運(yùn)行請求。
/* 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() // 由于當(dāng)進(jìn)程結(jié)束的時候會清除這里的數(shù)據(jù),所以使用內(nèi)存數(shù)據(jù)庫 db = Room.inMemoryDatabaseBuilder(context, WordRoomDatabase::class.java) // 可以在主線程中發(fā)起請求,僅用于測試。 .allowMainThreadQueries() .build() wordDao = db.wordDao() } @After @Throws(IOException::class) fun closeDb() { db.close() } ... }
要測試單詞是否能夠被正確添加到數(shù)據(jù)庫,我們會創(chuàng)建一個 Word 實(shí)例,然后插入數(shù)據(jù)庫,然后按照字母順序找到單詞列表中的第一個,然后確保它和我們創(chuàng)建的單詞是一致的。由于我們調(diào)用的是掛起函數(shù),所以我們會在 runBlocking 代碼塊中運(yùn)行測試。因?yàn)檫@里僅僅是測試,所以我們無需關(guān)心測試過程是否會阻塞測試線程。
/* 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) }
除了本文所介紹的功能,Room 提供了非常多的功能性和靈活性,遠(yuǎn)遠(yuǎn)超出本文所涵蓋的范圍。比如您可以指定 Room 如何處理數(shù)據(jù)庫沖突、可以通過創(chuàng)建 TypeConverters 存儲原生 SQLite 無法存儲的數(shù)據(jù)類型 (比如 Date 類型)、可以使用 JOIN 以及其它 SQL 功能實(shí)現(xiàn)復(fù)雜的查詢、創(chuàng)建數(shù)據(jù)庫視圖、預(yù)填充數(shù)據(jù)庫以及當(dāng)數(shù)據(jù)庫被創(chuàng)建或打開的時候觸發(fā)特定動作。
更多相關(guān)信息請查閱我們的 Room 官方文檔,如果想通過實(shí)踐學(xué)習(xí),可以訪問 Room with a view codelab。
以上就是Room Kotlin API使用入門教程的詳細(xì)內(nèi)容,更多關(guān)于Room Kotlin API使用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android 自定義TextView實(shí)現(xiàn)文本內(nèi)容自動調(diào)整字體大小
本文主要介紹了Android 自定義TextView實(shí)現(xiàn)文本內(nèi)容自動調(diào)整字體大小以適應(yīng)TextView的大小的方法。具有很好的參考價值。下面跟著小編一起來看下吧2017-03-03Android RecyclerView四級緩存源碼層詳細(xì)分析
RecyclerView是Android一個更強(qiáng)大的控件,其不僅可以實(shí)現(xiàn)和ListView同樣的效果,還有優(yōu)化了ListView中的各種不足。其可以實(shí)現(xiàn)數(shù)據(jù)縱向滾動,也可以實(shí)現(xiàn)橫向滾動(ListView做不到橫向滾動)。接下來講解RecyclerView的用法2022-11-11Android DrawerLayout帶有側(cè)滑功能的布局類(1)
這篇文章主要為大家詳細(xì)介紹了Android DrawerLayout帶有側(cè)滑功能的布局類,感興趣的小伙伴們可以參考一下2016-07-07Android自定義View實(shí)現(xiàn)QQ音樂中圓形旋轉(zhuǎn)碟子
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)QQ音樂中圓形旋轉(zhuǎn)碟子,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-09-09詳解Flutter?響應(yīng)式狀態(tài)管理框架GetX
這篇文章主要為大家介紹了Flutter?響應(yīng)式狀態(tài)管理框架GetX詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Android形狀圖形與狀態(tài)列表圖形及九宮格圖片超詳細(xì)講解
這篇文章主要介紹了Android形狀圖形與狀態(tài)列表圖形及九宮格圖片的應(yīng)用方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-09-09Java語言讀取配置文件config.properties的方法講解
今天小編就為大家分享一篇關(guān)于Java語言讀取配置文件config.properties的方法講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03android 自定義TabActivity的實(shí)例方法
系統(tǒng)自帶的TabActivity的效果不甚理想。開發(fā)中對TabActivity自定義可能有兩種:第一種:改變TAB行的位置,如放到頁面下方。第二種:對TabHost圖片的自定義2013-11-11Android中實(shí)現(xiàn)記事本動態(tài)添加行效果
記事本對我們每個人來說再熟悉不過,下面這篇文章主要給大家介紹了在Android中實(shí)現(xiàn)記事本動態(tài)添加行效果的相關(guān)資料,這是最近在開發(fā)中遇到的一個小需求,想著分享出來供大家參考學(xué)習(xí),需要的朋友們下面來一起看看吧。2017-06-06