Android Architecture Components Part1:Room

clipboard.png

前言

Android Architecture Components(AAC)首次發佈與2017 GoogleI/O大會,通過近一年的維護,如今Google團隊已經發布了穩定版(v1.1.1)。可以更好的幫助咱們來構建本身的App應用,若是你尚未了解ACC如今時間剛恰好,來不及解釋,趕忙上車吧。java

ACC是一個架構組件,它能幫忙咱們更好的來管理咱們的App,方便咱們的開發。它能幫助咱們的App更好的存儲數據、管理生命週期、進行模塊化、避免常見的錯誤、減小樣板文件的編寫。android

ACC主要由4個單一組件組成,分別爲:Room、LiveData、Lifecycle與ViewModel。它們每個都是獨立存在的組件,咱們能夠單獨使用其中幾個,又或者能夠將它們所有整合到一塊兒。因此對於ACC它提供了更好的使用靈活性,方便咱們集成到咱們的App中。git

今天主要是對ACC其中的Room組件進行分析。Room是一個穩健的SQL對象映射庫,用來幫助咱們快速的實現數據本地存儲。至於爲什麼要使用本地數據庫,天然是當用戶無網絡或者網絡差的時候,可以更好的提升用戶對咱們App的體驗。github

添加依賴

在使用Room以前,咱們仍是要在項目中對其進行依賴添加。
首先在你的項目的根目錄下的build.gradle中添加google()庫,代碼以下:sql

allprojects {
    repositories {
        jcenter()
        google()
    }
}

以後打開你的App或者module中的build.gradle文件,在dependencies中添加以下代碼:數據庫

dependencies {
    def room_version = "1.1.0" // or, for latest rc, use "1.1.1-rc1"
 
    implementation "android.arch.persistence.room:runtime:$room_version"
    annotationProcessor "android.arch.persistence.room:compiler:$room_version"
 
    // optional - RxJava support for Room
    implementation "android.arch.persistence.room:rxjava2:$room_version"
 
    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "android.arch.persistence.room:guava:$room_version"
 
    // Test helpers
    testImplementation "android.arch.persistence.room:testing:$room_version"
}

Room

上面的依賴添加完成後,接下來咱們能夠正式使用Room。在Android App中進行本地數據的存儲都是使用SQLite,當咱們使用原生的SQLite進行本地數據庫的編寫時,咱們不只要定義數據庫結構,還要建立SQLiteHelper,編寫一連串的SQL語句。這樣代碼量與複雜度不斷上升,這不是咱們想要的。而Room正好能夠幫助咱們減小代碼、簡化複雜度。segmentfault

對於Room的使用主要由三部分構成:api

  1. Entity:標識數據庫中的表結構
  2. DAO: 標識提供獲取數據庫表中的數據方法
  3. Database:標識所須要建立的數據庫

以上三部分在代碼中都是經過註釋來實現,從而達到代碼的精簡。數組

Entity

Entity做用在model上,即咱們與數據表中的字段所匹配的model類。如今咱們來創建一個聯繫人相關的model,對於正常的model創建以下:網絡

data class ContactsModel(val id: Int, val name: String, val phone: String)

如今咱們要把ContactsModel映射到數據庫中的一種表,只需進行以下操做:

@Entity(tableName = "contacts")
data class ContactsModel(
        @PrimaryKey
        @ColumnInfo(name = "contacts_id")
        val id: Int,
        @ColumnInfo(name = "name")
        val name: String,
        @ColumnInfo(name = "phone")
        val phone: String
)

首先咱們在ContactsModel中添加@Entity註釋,代表它將映射成一種表。在Entity中能夠經過使用tableName來爲該表命名,這裏將其命名未contacts。

除此以外,使用@ColumnInfo來標明表中的字段,@PrimaryKey來標明表的主鍵。其中@ColumnInfo也能夠經過(name = "name")來命名字段名。固然還有別的註釋例如外鍵的標明:@ForeignKey

DAO

數據庫表建好了,如今是提供操做數據表中的數據的方法。

@Dao
interface ContactsDao {
 
    @Query("SELECT * FROM contacts")
    fun getAllContacts(): List<ContactsModel>
 
    @Query("SELECT * FROM contacts WHERE contacts_id = :id")
    fun getContactsById(id: Int): LiveData<ContactsModel>
 
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertContacts(contactsModel: ContactsModel)
 
    @Query("UPDATE contacts SET name = :name AND phone = :phone WHERE contacts_id = :id")
    fun updateContacts(id: Int, name: String, phone: String)
 
    @Query("DELETE FROM contacts WHERE contacts_id = :id")
    fun deleteContacts(id: Int)

這裏咱們只需建立一個接口,經過@Dao來標明它是提供操做數據表的方法集。要注意它必須爲interface,在接口中咱們只需定義接口方法便可。與日常的接口方法定義不一樣的是,咱們必須在每個接口方法上經過註釋來標明該方法的做用。

例如getAllContacts()方法,咱們爲了讓它實現獲取contacts表中的全部數據,咱們須要在其方法中添加@Query註釋,因爲是查詢方法,天然是使用Query,若是是插入方法就是Insert(第三個方法)。其次()中的內容就是正常的查詢語句。這裏是獲取全部的Contacts,因此咱們使用

@Query("SELECT * FROM contacts")

對於有參數的sql語句編寫,能夠查看第二個方法,參數值只需在對應的方法參數名前加入:前綴,這就是傳參的格式。

@Query("SELECT * FROM contacts WHERE contacts_id = :id")
fun getContactsById(id: Int): LiveData<ContactsModel>

Room就是這麼簡單,經過定義接口與接口方法的形式,再結合註釋來簡化代碼量與複雜度。固然最終Room會根據註釋,編譯器會幫咱們實現這些接口方法。咱們能夠build項目,而後咱們就能夠搜索到ContactsDao_Impl類,這個讀者能夠自行嘗試。本質是ContactsDao_Impl實現了ContactsDao接口。

Room的強大之一是:它能夠在編譯時檢測你的SQL語句是否編寫正確,若是編寫錯誤將致使編譯失敗。這樣就能夠避免App在運行時致使崩潰。這個讀者能夠自行測試一下。

Database

如今數據表有了,對錶的操做方法也有了,最後就差數據庫來保存各個數據表了。Talk is cheap. Show me the code。

@Database(entities = arrayOf(ContactsModel::class), version = 1)
abstract class ContactsDataBase : RoomDatabase() {
 
    abstract fun contactsDao(): ContactsDao
 
    companion object {
        private var instance: ContactsDataBase? = null
        fun getInstance(context: Context): ContactsDataBase {
            if (instance == null) {
                instance = Room.databaseBuilder(context.applicationContext,
                        ContactsDataBase::class.java,
                        "contacts.db").build()
            }
            return instance as ContactsDataBase
        }
    }
}

沒錯,仍是使用註釋,這裏咱們定義ContactsDataBase抽象類,讓它繼承RoomDatabase抽象類。固然也是同@Database來標明它是一個數據庫。它接收兩個參數,分別爲entities與version,前者接收的類型是Class[]數組,內容爲對於表的Class;後者是int的數據庫版本號。

在ContactsDataBase中還需定義一個抽象方法,讓它返回由@Dao註釋的ContactsDao,即提供獲取數據表的方法。本質的爲數據庫暴露操做數據表的入口。至於它的具體方法實現也能夠經過build來查看對應的自動生成文件ContactsDataBase_Impl類。

由於contactsDao是數據庫的惟一入口,避免每次對數據庫進行操做時都建立ContactsDataBase實例,如上代碼咱們可使用單例模式來減小實例頻繁建立的開銷。

使用

通過上面的Entity、DAO與Database的建立,如今咱們已經有了完整的本地數據庫結構。接下來咱們來看史上最簡數據庫使用的調用代碼:

private val mContactsDao by lazy { ContactsDataBase.getInstance(application).contactsDao() }
 
fun getContactsById(id: Int): LiveData<ContactsModel> = mContactsDao.getContactsById(id)

你沒看錯只需兩行代碼,咱們就能獲取數據庫中Contacts表中的所用數據。

第一行代碼咱們獲取了ContactsDao實例,該實例包含操做數據表的因此方法。而第二行代碼就是調用ContactsDao中的操做方法。返回咱們所需的數據。

在第二行代碼,細心的大家可能會發現它返回了LiveData<ContactsModel>類型數據。它是ACC的另外一強大組件,這也是Room的另外一強大之處,它能夠直接返回LiveData數據類型,完美與LiveData結合。至於LiveData的做用,敬請關注下一篇文章Android Architecture Components Part2:LiveData

總結

若是你的App使用了Room,那麼你的App本地數據獲取架構將會是這樣的

clipboard.png

最後文章中的代碼均可以在Github中獲取到。使用時請將分支切換到feat_architecture_components

相關文章

Android Architecture Components Part2:LiveData
Android Architecture Components Part3:Lifecycle
Android Architecture Components Part4:ViewModel

關注

私人博客

clipboard.png

相關文章
相關標籤/搜索