這個程式碼研究室是 Android Kotlin 基礎知識課程的一部分。如果您按部就班完成程式碼研究室,就能充分體驗到本課程的價值。所有課程程式碼研究室都列在 Android Kotlin 基礎知識程式碼研究室到達網頁。
簡介
大多數應用程式都有需要保留的資料,即使使用者關閉應用程式也不例外。舉例來說,應用程式可能會儲存播放清單、遊戲物品清單、支出和收入記錄、星座目錄或一段時間內的睡眠資料。一般來說,您會使用資料庫儲存持續性資料。
Room 是 Android Jetpack 中的資料庫程式庫。Room 可處理許多設定和設定資料庫的雜務,讓您的應用程式能使用一般的函式呼叫與資料庫互動。在幕後,Room 是 SQLite 資料庫頂端的抽象層。Room 的術語,以及更複雜查詢的查詢語法,都遵循 SQLite 模型。
下圖說明瞭 Room 資料庫如何配合本課程推薦的整體架構。

必備知識
您必須已經熟悉下列項目:
- 為 Android 應用程式建構基本的使用者介面 (UI)
- 使用活動、片段和檢視畫面。
- 在片段之間導覽,並使用 Safe Args (Gradle 外掛程式) 在片段之間傳遞資料。
- 查看模型、ViewModel 工廠,以及
LiveData和其觀察器。本課程先前的程式碼研究室已涵蓋這些架構元件主題。 - 對 SQL 資料庫和 SQLite 語言有基本瞭解。如要快速瞭解或複習相關概念,請參閱 SQLite 基礎知識。
課程內容
- 如何建立及操作
Room資料庫,以便保存資料。 - 如何建立定義資料庫中資料表的資料類別。
- 如何使用資料存取物件 (DAO) 將 Kotlin 函式對應至 SQL 查詢。
- 如何測試資料庫是否正常運作。
學習內容
- 建立
Room資料庫,並提供夜間睡眠資料的介面。 - 使用提供的測試項目測試資料庫。
在本程式碼研究室中,您將建構應用程式的資料庫部分,該部分會追蹤睡眠品質。應用程式會使用資料庫儲存一段時間內的睡眠資料。
如圖所示,這個應用程式有兩個畫面,分別以片段表示。
如左側所示,第一個畫面會顯示開始和停止追蹤的按鈕。畫面上會顯示使用者的所有睡眠資料。「清除」按鈕會永久刪除應用程式為使用者收集的所有資料。
右側的第二個畫面用於選取睡眠品質評分。應用程式會以數字表示評分。為了方便開發,應用程式會同時顯示臉部圖示和對應的數字。
使用者流程如下:
- 使用者開啟應用程式,並看到睡眠追蹤畫面。
- 使用者輕觸「開始」按鈕。系統會記錄開始時間並顯示。「開始」按鈕已停用,「停止」按鈕已啟用。
- 使用者輕觸「停止」按鈕。系統會記錄結束時間,並開啟睡眠品質畫面。
- 使用者選取睡眠品質圖示。螢幕會關閉,追蹤畫面則會顯示睡眠結束時間和睡眠品質。「停止」按鈕已停用,「開始」按鈕已啟用。應用程式已準備好迎接下一個夜晚。
- 只要資料庫中有資料,「清除」按鈕就會啟用。使用者輕觸「清除」按鈕後,所有資料都會遭到清除,且無法復原,系統也不會顯示「確定要清除嗎?」訊息。
這個應用程式採用簡化架構,如下圖所示 (完整架構的背景資訊)。應用程式只會使用下列元件:
- UI 控制器
- 查看模型和
LiveData - Room 資料庫
步驟 1:下載並執行入門應用程式
- 從 GitHub 下載 TrackMySleepQuality-Starter 應用程式。
- 建構並執行應用程式。應用程式會顯示
SleepTrackerFragment片段的 UI,但不會顯示任何資料。按鈕不會對輕觸動作做出反應。
步驟 2:檢查入門應用程式
- 查看 Gradle 檔案:
- 專案 Gradle 檔案
在專案層級的build.gradle檔案中,請注意指定程式庫版本的變數。範例應用程式中使用的版本可順暢搭配運作,且與這個應用程式相容。完成本程式碼研究室時,Android Studio 可能會提示您更新部分版本。您可以選擇更新或繼續使用應用程式中的版本。如果遇到「奇怪」的編譯錯誤,請嘗試使用最終解決方案應用程式所用的程式庫版本組合。 - 模組 Gradle 檔案。請注意,所有 Android Jetpack 程式庫 (包括
Room) 和協同程式的依附元件都已提供。
- 請查看套件和 UI。應用程式是依功能架構而成。這個套件含有預留位置檔案,您會在這一系列的程式碼研究室中新增程式碼。
database套件,適用於所有與Room資料庫相關的程式碼。sleepquality和sleeptracker套件包含每個畫面的片段、檢視模型和檢視模型工廠。
- 請查看
Util.kt檔案,其中包含有助於顯示睡眠品質資料的函式。部分程式碼已加上註解,因為這些程式碼會參照您稍後建立的檢視區塊模型。 - 查看 androidTest 資料夾 (
SleepDatabaseTest.kt)。您將使用這項測試驗證資料庫是否正常運作。
在 Android 中,資料會以資料類別表示,並透過函式呼叫存取及修改。但在資料庫中,您需要實體和查詢。
- 實體代表要儲存在資料庫中的物件/概念及其屬性。實體類別定義了資料表,這個類別的每個例項都代表資料表中的一列;每個屬性都會定義一個資料欄。在應用程式中,這個實體會存放有關一晚睡眠的資訊,
- 查詢是指從資料庫表格或表格組合要求資料或資訊,或是要求對資料執行動作。常見查詢是為了取得、插入及更新實體。舉例來說,你可以查詢所有記錄的睡眠時間,並依開始時間排序。
Room 會為您完成所有繁瑣的工作,將 Kotlin 資料類別轉換為可儲存在 SQLite 資料表中的實體,並將函式宣告轉換為 SQL 查詢。
您必須將每個實體定義為加上註解的資料類別,並將互動定義為加上註解的介面,也就是資料存取物件 (DAO)。Room 會使用這些註解類別,在資料庫中建立資料表,以及對資料庫執行的查詢。

步驟 1:建立 SleepNight 實體
在這項工作中,您要將一晚的睡眠定義為註解資料類別。
如要記錄一晚的睡眠,請輸入開始時間、結束時間和睡眠品質評分。
此外,您還需要專屬 ID 來識別當晚。
- 在
database套件中,找出並開啟SleepNight.kt檔案。 - 建立
SleepNight資料類別,並加入 ID、開始時間 (毫秒)、結束時間 (毫秒) 和睡眠品質評分 (數值) 的參數。
- 您必須初始化
sleepQuality,因此請將其設為-1,表示系統尚未收集任何品質資料。 - 您也必須初始化結束時間。將其設為開始時間,表示尚未記錄結束時間。
data class SleepNight(
var nightId: Long = 0L,
val startTimeMilli: Long = System.currentTimeMillis(),
var endTimeMilli: Long = startTimeMilli,
var sleepQuality: Int = -1
)- 在類別宣告前,為資料類別加上
@Entity註解。將資料表命名為daily_sleep_quality_table。雖然tableName的引數是選用引數,但我們建議使用。您可以在說明文件中查詢其他引數。
如果系統提示,請從androidx程式庫匯入Entity和所有其他註解。
@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(...)- 要將
nightId標識為主鍵,請為nightId屬性加上註解@PrimaryKey。將autoGenerate參數設為true,以便Room為每個實體產生 ID。這能保證每晚的 ID 都不重複。
@PrimaryKey(autoGenerate = true)
var nightId: Long = 0L,...- 為其餘屬性加上註解
@ColumnInfo。使用參數自訂屬性名稱,如下所示。
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "daily_sleep_quality_table")
data class SleepNight(
@PrimaryKey(autoGenerate = true)
var nightId: Long = 0L,
@ColumnInfo(name = "start_time_milli")
val startTimeMilli: Long = System.currentTimeMillis(),
@ColumnInfo(name = "end_time_milli")
var endTimeMilli: Long = startTimeMilli,
@ColumnInfo(name = "quality_rating")
var sleepQuality: Int = -1
)- 建構並執行程式碼,確保沒有錯誤。
在這項工作中,您要定義資料存取物件 (DAO)。在 Android 上,DAO 提供插入、刪除及更新資料庫的簡便方法。
使用 Room 資料庫時,您可以在程式碼中定義及呼叫 Kotlin 函式,藉此查詢資料庫。這些 Kotlin 函式會對應至 SQL 查詢。您可以使用註解在 DAO 中定義這些對應項,而 Room 會建立必要的程式碼。
您可以將 DAO 視為定義存取資料庫的自訂介面。
針對常見的資料庫作業,Room 程式庫可提供便利的註解,例如 @Insert、@Delete 和 @Update。除此之外,您還可以使用 @Query 註解。您可以編寫受 SQLite 支援的任何查詢。
另一個好處是,當您在 Android Studio 中建立查詢時,編譯器會檢查 SQL 查詢是否有語法錯誤。
對於睡眠追蹤器資料庫的睡眠夜數,您需要能夠執行以下操作:
- 插入新的夜晚。
- 更新現有夜間睡眠的結束時間和品質評分。
- 根據索引鍵取得特定夜晚。
- 取得所有晚數,以便顯示。
- 取得最近一晚的睡眠資料。
- 刪除資料庫中的所有項目。
步驟 1:建立 SleepDatabase DAO
- 在
database套件中開啟SleepDatabaseDao.kt。 - 請注意,
interfaceSleepDatabaseDao已加上@Dao註解。所有 DAO 都必須以@Dao關鍵字註解。
@Dao
interface SleepDatabaseDao {}- 在介面內文中,新增
@Insert註解。在@Insert下方,新增insert()函式,以將Entity類別SleepNight的例項做為引數。
就是這麼簡單!Room會產生將SleepNight插入資料庫所需的所有程式碼。當您從 Kotlin 程式碼呼叫insert()時,Room會執行 SQL 查詢,將實體插入資料庫中。(注意:您可以將函式命名為任何名稱)。
@Insert
fun insert(night: SleepNight)- 為一個
SleepNight新增帶有update()函式的@Update註解。更新的實體與傳入的實體金鑰相同。您可以更新實體的部分或所有其他屬性。
@Update
fun update(night: SleepNight)剩餘功能沒有便利的註解,因此您必須使用 @Query 註解並提供 SQLite 查詢。
- 新增
@Query註解,並加入get()函式,這個函式會採用Longkey引數,並傳回可為空值的SleepNight。系統會顯示缺少參數的錯誤。
@Query
fun get(key: Long): SleepNight?- 查詢以字串參數的形式提供給註解。在
@Query中新增參數。將其設為 SQLite 查詢的String。
- 從
daily_sleep_quality_table中選取所有欄 WHEREnightId與 :key引數相符。
請注意:key。您可以在查詢中使用冒號標記法來參照函式中的引數。
("SELECT * from daily_sleep_quality_table WHERE nightId = :key")- 新增另一個
@Query,其中包含clear()函式和 SQLite 查詢,用於從daily_sleep_quality_tableDELETE所有項目。這項查詢不會刪除資料表本身,@Delete註解會刪除一個項目,您可以使用@Delete並提供要刪除的晚數清單。缺點是您必須擷取或瞭解表格內容。@Delete註解很適合刪除特定項目,但如果想清除資料表中的所有項目,效率就不高。
@Query("DELETE FROM daily_sleep_quality_table")
fun clear()- 新增具有
getTonight()函式的@Query。將getTonight()傳回的SleepNight設為可為空值,以便函式處理資料表為空的情況。(資料表一開始是空的,資料清除後也是空的)。
如要從資料庫取得「今晚」,請編寫 SQLite 查詢,傳回依nightId遞減排序的結果清單中第一個元素。使用LIMIT 1只傳回一個元素。
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
fun getTonight(): SleepNight?- 新增具有
getAllNights()函式的@Query:
- 讓 SQLite 查詢傳回
daily_sleep_quality_table中的所有資料欄,以遞減順序排序。 - 讓
getAllNights()將SleepNight實體清單做為LiveData傳回。Room將隨時為您更新這個LiveData,因此您只需明確取得資料一次。 - 您可能需要從
androidx.lifecycle.LiveData匯入LiveData。
@Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
fun getAllNights(): LiveData<List<SleepNight>>- 雖然您看不到任何明顯的變更,但請執行應用程式,確定沒有任何錯誤。
在這項工作中,您要建立 Room 資料庫,並使用您在先前工作中建立的 Entity 和 DAO。
您需要建立抽象的資料庫容器類別,並加上 @Database 註解。這個類別包含一個方法,可在沒有資料庫時建立資料庫例項,或者傳回現有資料庫的參照。
取得 Room 資料庫的程序較為複雜,因此在開始編寫程式碼之前,請先瞭解一般程序:
- 建立
public abstract類別,即extends RoomDatabase。這個類別會成為資料庫容器。這個類別是抽象的,因為Room會為您建立實作。 - 為該類別加上
@Database註解。在引數中,宣告資料庫的實體並設定版本號碼。 - 在
companion物件內,定義傳回SleepDatabaseDao的抽象方法或屬性。Room會為你生成內文。 - 整個應用程式只需要一個
Room資料庫執行個體,因此請將RoomDatabase設為單例模式。 - 僅在資料庫不存在的情況下,使用
Room的資料庫建構工具建立資料庫。否則,請傳回現有資料庫。
步驟 1:建立資料庫
- 在
database套件中開啟SleepDatabase.kt。 - 在檔案中,建立名為
SleepDatabase的abstract類別,並擴充RoomDatabase。
使用@Database為類別加上註解。
@Database()
abstract class SleepDatabase : RoomDatabase() {}- 系統會顯示缺少實體和版本參數的錯誤。
@Database註解需要多個引數,以便Room能夠建構資料庫。
- 將
SleepNight指定為包含entities清單的唯一項目。 - 將
version設為1。每次變更結構定義時,都必須增加版本號碼。 - 只要將
exportSchema設為false,即可不保留結構定義版本記錄的備份。
entities = [SleepNight::class], version = 1, exportSchema = false- 資料庫需要知道該 DAO。在類別內文中,宣告一個傳回
SleepDatabaseDao的抽象值。您可以擁有多個 DAO。
abstract val sleepDatabaseDao: SleepDatabaseDao- 在下方定義
companion物件。夥伴物件可讓用戶端存取建立或取得資料庫的方法,而不必例項化類別。由於這個類別的唯一用途是提供資料庫,因此沒有理由要例項化。
companion object {}- 在
companion物件中,宣告資料庫的私人空值變數INSTANCE,並將其初始化為null。INSTANCE變數會在建立資料庫時保留對該資料庫的參照。這有助於避免重複開啟資料庫連線,因為這會耗費大量資源。
使用 @Volatile 為 INSTANCE 加上註解。系統一律不會快取易失變數的值,所有讀取與寫入作業都會在主記憶體中完成。這有助於確保所有執行緒的 INSTANCE 值保持在最新狀態且相同。也就是說,任一執行緒對 INSTANCE 所做的變更會立即向所有其他執行緒顯示,您不會遇到兩個執行緒各自更新快取中的相同實體,進而造成問題的情況。
@Volatile
private var INSTANCE: SleepDatabase? = null- 在
INSTANCE下方的companion物件內,使用資料庫建構工具所需的Context參數定義getInstance()方法。傳回類型SleepDatabase。由於getInstance()尚未傳回任何內容,因此系統會顯示錯誤訊息。
fun getInstance(context: Context): SleepDatabase {}- 在
getInstance()內新增synchronized{}區塊。傳入this,即可存取內容。
多個執行緒可能會同時要求一個資料庫例項,因而產生兩個資料庫,而非單一資料庫。這個問題不太可能發生在這個範例應用程式中,但較複雜的應用程式可能會遇到。如果將用來取得資料庫的程式碼納入synchronized,就表示一次只有一個執行緒能夠進入此程式碼區塊,這可確保資料庫僅初始化一次。
synchronized(this) {}var instance = INSTANCE- 在
synchronized區塊內,return instance位於synchronized區塊的結尾。忽略傳回型別不符的錯誤;完成後,您絕不會傳回空值。
return instance- 在
return陳述式上方,新增if陳述式,檢查instance是否為空值 (也就是目前沒有資料庫)。
if (instance == null) {}- 如果
instance是null,請使用資料庫建構工具取得資料庫。在if陳述式的主體中,叫用Room.databaseBuilder並提供您傳入的內容、資料庫類別和資料庫名稱sleep_history_database。如要移除這項錯誤,請按照下列步驟新增遷移策略和build()。
instance = Room.databaseBuilder(
context.applicationContext,
SleepDatabase::class.java,
"sleep_history_database")- 將必要的遷移策略新增至建構工具。使用
.fallbackToDestructiveMigration().
一般來說,您必須為遷移物件提供有關何時變更結構定義的遷移策略。「遷移物件」定義了如何擷取所有採用舊結構定義的資料列,並轉換為以新結構定義呈現的資料列,以免資料遺失。遷移不在本程式碼研究室的範圍內。其中一個簡單的解決方法是刪除並重新建構資料庫,但代表資料會遺失。
.fallbackToDestructiveMigration()- 最後,呼叫
.build()。
.build()- 在
if陳述式中,將INSTANCE = instance指派為最後一個步驟。
INSTANCE = instance- 最終程式碼應如下所示:
@Database(entities = [SleepNight::class], version = 1, exportSchema = false)
abstract class SleepDatabase : RoomDatabase() {
abstract val sleepDatabaseDao: SleepDatabaseDao
companion object {
@Volatile
private var INSTANCE: SleepDatabase? = null
fun getInstance(context: Context): SleepDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
SleepDatabase::class.java,
"sleep_history_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
}
return instance
}
}
}
}- 建構並執行程式碼。
現在您擁有了使用 Room 資料庫所需的所有建構區塊。這個程式碼可以編譯並執行,但無法判斷它是否正常運作。因此,這正是新增一些基本測試的好時機。
步驟 2:測試 SleepDatabase
在這個步驟中,您將執行提供的測試,確認資料庫運作正常。這有助於確保資料庫正常運作,再進行建構。提供的測試是基本測試。如果是正式版應用程式,您會執行所有 DAO 中的所有函式和查詢。
入門應用程式包含 androidTest 資料夾。這個 androidTest 資料夾包含涉及 Android 檢測設備的單元測試,也就是說,測試需要 Android 架構,因此您必須在實體或虛擬裝置上執行測試。當然,您也可以建立及執行不涉及 Android 架構的純單元測試。
- 在 Android Studio 的 androidTest 資料夾中,開啟 SleepDatabaseTest 檔案。
- 如要取消程式碼註解,請選取所有註解程式碼,然後按下
Cmd+/或Control+/鍵盤快速鍵。 - 查看檔案。
以下快速瀏覽測試程式碼,因為這是另一段可重複使用的程式碼:
SleepDabaseTest是測試類別。@RunWith註解會識別測試執行器,也就是設定及執行測試的程式。- 設定期間,系統會執行加上
@Before註解的函式,並使用SleepDatabaseDao建立記憶體內SleepDatabase。「記憶體內」表示這個資料庫不會儲存在檔案系統中,測試執行完畢後就會刪除。 - 此外,建構記憶體內資料庫時,程式碼會呼叫另一個測試專用方法
allowMainThreadQueries。根據預設,如果您嘗試在主執行緒上執行查詢,系統會顯示錯誤訊息。這個方法可讓您在主執行緒上執行測試,但您只應在測試期間執行這項操作。 - 在以
@Test註解的測試方法中,您會建立、插入及擷取SleepNight,並確認兩者相同。如有任何問題,請擲回例外狀況。在實際測試中,您會有許多@Test方法。 - 測試完成後,標註
@After的函式會執行,以關閉資料庫。
- 在「Project」窗格中的測試檔案上按一下滑鼠右鍵,然後選取「Run 'SleepDatabaseTest'」。
- 測試執行完畢後,請在「SleepDatabaseTest」SleepDatabaseTest窗格中確認所有測試都已通過。

由於所有測試都通過,您現在瞭解以下幾件事:
- 資料庫建立正確。
- 您可以將
SleepNight插入資料庫。 - 你可以取回
SleepNight。 SleepNight具有正確的品質值。
Android Studio 專案:TrackMySleepQualityRoomAndTesting
測試資料庫時,您需要執行 DAO 中定義的所有方法。如要完成測試, 請新增並執行測試,以練習其他 DAO 方法。
- 將資料表定義為具有
@Entity註解的資料類別。定義具有@ColumnInfo註解的屬性做為資料表中的資料欄。 - 定義資料存取物件 (DAO) 做為標有
@Dao註解的介面。DAO 會將 Kotlin 函式對應至資料庫查詢。 - 使用註解來定義
@Insert、@Delete和@Update函式。 - 針對其他查詢,使用
@Query註解搭配 SQLite 查詢字串做為參數。 - 建立具有
getInstance()函式的抽象類別,該函式會傳回資料庫。 - 使用檢測設備測試,確認資料庫和 DAO 運作正常。您可以將提供的測試做為範本。
Udacity 課程:
Android 開發人員說明文件:
RoomDatabaseDatabase(註解)- 您可以使用原始查詢搭配
Room Roomdatabase.Builder- 測試訓練
SQLiteDatabase類別DaoRoom持續性程式庫
其他說明文件和文章:
- 單例模式
- Google 開發人員專家:正確使用揮發性和同步化
- 伴生物件
- 瞭解 Room 遷移作業
- 測試 Room 遷移作業
- 資料庫的歷史
- SQLite 網站
- SQLite 所理解的 SQL 完整說明
本節列出的作業可由課程講師指派給學習本程式碼研究室的學員。講師可自由採取以下行動:
- 視需要指派作業。
- 告知學員如何繳交作業。
- 為作業評分。
講師可以視需求使用全部或部分建議內容,也可以自由指派任何其他合適的作業。
如果您是自行學習本程式碼研究室,不妨利用這些作業驗收學習成果。
回答問題
第 1 題
如何表示某個類別是儲存在 Room 資料庫中的實體?
- 將類別擴充
DatabaseEntity。 - 使用
@Entity為類別加上註解。 - 使用
@Database為類別加上註解。 - 讓類別擴充
RoomEntity,並使用@Room為類別加上註解。
第 2 題
DAO (資料存取物件) 是 Room 用來將 Kotlin 函式對應至資料庫查詢的介面。
如何指出介面就是 Room 資料庫的 DAO?
- 讓介面擴充
RoomDAO。 - 讓介面擴充
EntityDao,然後實作DaoConnection()方法。 - 使用
@Dao為介面加上註解。 - 使用
@RoomConnection為介面加上註解。
第 3 題
下列有關 Room 資料庫的敘述何者正確?(可複選)?
- 您可以將
Room資料庫的資料表定義為註解資料類別。 - 如果您從查詢傳回
LiveData,Room會在LiveData變更時為您更新LiveData。 - 每個
Room資料庫都必須有一個 DAO,而且只能有一個。 - 如要將類別識別為
Room資料庫,請將其設為RoomDatabase的子類別,並使用@Database加上註解。
第 4 題
您可以在 @Dao 介面中使用下列哪些註解?(可複選)?
@Get@Update@Insert@Query
第 5 題
如何驗證資料庫是否正常運作?請選取所有適用選項。
- 編寫檢測設備測試。
- 繼續編寫及執行應用程式,直到顯示資料為止。
- 呼叫
Entity類別中的類似方法,而不要呼叫 DAO 介面中的方法。 - 執行
Room程式庫提供的verifyDatabase()函式。
開始下一個課程:
如要查看本課程其他程式碼研究室的連結,請參閱 Android Kotlin 基礎知識程式碼研究室登陸頁面。