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