Android Architecture Components Part1:Room

怪談時間發表於2018-06-13

Android Architecture Components Part1:Room

前言

Android Architecture Components(AAC)首次釋出與2017 GoogleI/O大會,經過近一年的維護,現在Google團隊已經發布了穩定版(v1.1.1)。能夠更好的幫助我們來構建自己的App應用,如果你還沒有了解ACC現在時間剛剛好,來不及解釋,趕緊上車吧。

ACC是一個架構元件,它能幫忙我們更好的來管理我們的App,方便我們的開發。它能幫助我們的App更好的儲存資料、管理生命週期、進行模組化、避免常見的錯誤、減少樣板檔案的編寫。

ACC主要由4個單一元件組成,分別為:Room、LiveData、Lifecycle與ViewModel。它們每一個都是獨立存在的元件,我們可以單獨使用其中幾個,又或者可以將它們全部整合到一起。所以對於ACC它提供了更好的使用靈活性,方便我們整合到我們的App中。

今天主要是對ACC其中的Room元件進行分析。Room是一個穩健的SQL物件對映庫,用來幫助我們快速的實現資料本地儲存。至於為何要使用本地資料庫,自然是當使用者無網路或者網路差的時候,能夠更好的提高使用者對我們App的體驗。

新增依賴

在使用Room之前,我們還是要在專案中對其進行依賴新增。 首先在你的專案的根目錄下的build.gradle中新增google()庫,程式碼如下:

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正好可以幫助我們減少程式碼、簡化複雜度。

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

  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型別資料。它是ACC的另一強大元件,這也是Room的另一強大之處,它可以直接返回LiveData資料型別,完美與LiveData結合。至於LiveData的作用,敬請關注下一篇文章Android Architecture Components Part2:LiveData。

總結

如果你的App使用了Room,那麼你的App本地資料獲取架構將會是這樣的

Android Architecture Components Part1:Room

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

相關文章

Android Architecture Components Part2:LiveData

關注

私人部落格

Android Architecture Components Part1:Room

相關文章