Android Jetpack 庫架構組件 Room+Paging 基礎使用

上篇文章 Android Jetpack 庫架構組件 ViewModel+LiveData 基礎使用示例2中,使用 ViewModel+ LiveData 的方式實現了數據庫數據查詢並分頁顯示的效果,而這裏的數據庫用的就是Room,分頁使用的 Paging

Room 是什麼

Room 持久性庫在 SQLite 的基礎上提供了一個抽象層,讓用戶能夠在充分利用 SQLite 的強大功能的同時,獲享更強健的數據庫訪問機制。

也就是 Room 是在SQlite的基礎上封裝了接口,使得SQlite更加易用。

使用Room需要包含 3 個主要組件:

  • 數據庫:包含數據庫持有者。可以通過調用 Room.databaseBuilder()Room.inMemoryDatabaseBuilder()獲取 Database 的實例。
  • Entity:表示數據庫中的表。
  • DAO:包含用於訪問數據庫的方法。

Room 不同組件之間的關係如下圖所示:

步驟1:應用使用 Room 數據庫來獲取與該數據庫關聯的數據訪問對象 (DAO)。:
步驟2:應用使用每個 DAO 從數據庫中獲取實體,然後再將對這些實體的所有更改保存回數據庫中。
步驟3:應用使用實體來獲取和設置與數據庫中的表列相對應的值。

在這裏插入圖片描述

Room 使用步驟

示例:
(1)獲取數據庫中學生的姓名列表

  1. Module -> build.gradle的引入

版本依賴查看:https://developer.android.google.cn/jetpack/androidx/releases/room#declaring_dependencies

	def room_version = "2.2.5"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor

	// 還有一些可選項,可查看上面的版本依賴鏈接
  1. 定義實體類,用@Entity註解
@Entity(tableName = "Student")
data class Student(
	// 字段1,主鍵自增
	@PrimaryKey(autoGenerate = true) 
	val id: Int, 
	// 字段2
	val name: String)
    
  1. 創建 DAO接口,用@Dao註解實現數據庫的增刪改查
@Dao
interface StudentDao {

	// DataSource.Factory<Int, Student> 獲取 Room 數據庫中所有學生名稱按升序返回
    @Query("SELECT * FROM Student ORDER BY name COLLATE NOCASE ASC")
    fun getAllStudent(): DataSource.Factory<Int, Student>

	// 插入學生集合
    @Insert
    fun insert(students: List<Student>)

	// 插入一個學生
    @Insert
    fun insert(student: Student)
}
  1. 創建AppDatabase擴展 RoomDatabase 的抽象類,並創建數據庫實例。
// 把實體類添加到數組中,定義數據庫版本號
@Database(entities = [Student::class], version = 1)
abstract class StudentDb:RoomDatabase() {
    // 定義 DAO
    abstract fun studentDao(): StudentDao
	
	// 靜態方法創建實例和往數據庫插入學生姓名信息
    companion object {
        private var instance: StudentDb? = null
        @Synchronized
        fun get(): StudentDb {
            if (instance == null) {
                instance = Room.databaseBuilder(applicationContext,
                    StudentDb::class.java, "StudentDatabase").build()
            }
            return instance!!
        }
        // 默認數據
        fun initData(){
            ioThread {
                // 單線程池
                get().studentDao().insert(
                    CHEESE_DATA.map {
                        Student(
                            id = 0,
                            name = it
                        )
                    })
            }
        }
    }
}

// 學生姓名
private val CHEESE_DATA = arrayListOf(
    "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
    "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag",
    "Airedale", "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert",  // 15
    "American Cheese", "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro",
    "Appenzell", "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String",
    "Aromes au Gene de Marc", "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", // 30
    "Avaxtskyr", "Baby Swiss", "Babybel", "Baguette Laonnaise", "Bakers",
    "Baladi", "Balaton", "Bandal", "Banon", "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
    "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
    "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
    "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
    "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
    "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)"
)
  1. 獲取數據,展示
class HomeViewModel(context: Context): BaseViewModel() {

    companion object {
        private const val PAGE_SIZE = 15
        private const val ENABLE_PLACEHOLDERS = false
    }

//    val mContext = context
//    val dao = StudentDb.get(mContext).studentDao()

	// 獲取 DAO,通過 DAO 去獲取數據庫數據
    val dao = StudentDb.get().studentDao()
    // dao.getAllStudent() 這裏即返回了所有的學生姓名
	
	// 這裏使用 Paging 實現分頁,文章下面再說。
    val allStudents = LivePagedListBuilder(dao.getAllStudent(), PagedList.Config.Builder()
        .setPageSize(PAGE_SIZE)                         //配置分頁加載的數量
        .setEnablePlaceholders(ENABLE_PLACEHOLDERS)     //配置是否啓動PlaceHolders
        .setInitialLoadSizeHint(PAGE_SIZE)              //初始化加載的數量
        .build()).build()
}

Tip:
別忘了在Application調用數據的初始化,不然查詢數據庫時查不到學生數據。(或者你編寫學生姓名一條條插入數據庫)

// 初始化數據庫數據
StudentDb.initData()

Paging 是什麼

分頁庫可幫助您一次加載和顯示一小塊數據。按需載入部分數據會減少網絡帶寬和系統資源的使用量。

分頁庫的關鍵組件是 PagedList 類,用於加載應用數據塊或頁面。隨着所需數據的增多,系統會將其分頁到現有的 PagedList 對象中。

如果任何已加載的數據發生更改,會從LiveData 或基於 RxJava2 的對象向可觀察數據存儲器發出一個新的 PagedList 實例
隨着 PagedList 對象的生成,應用界面會呈現其內容,同時還會考慮界面控件的生命週期。

分頁庫支持以下數據架構:

  • 僅從後端服務器提供,推薦配合 Retrofit 使用。
  • 僅存儲在設備上的數據庫中,推薦配合 Room 使用。
  • 使用設備上的數據庫作爲緩存的其他來源組合,推薦配合 Retrofit + Room 使用。

在這裏插入圖片描述

下面以查詢數據庫顯示在RecyclerView爲例介紹使用步驟。

Paging 使用步驟

示例:
(1)將數據庫的數據查詢出來顯示在 RecyclerView

  1. Module -> build.gradle的引入

版本依賴查看:https://developer.android.google.cn/jetpack/androidx/releases/paging#declaring_dependencies

dependencies {
   def paging_version = "2.1.2"

   implementation "androidx.paging:paging-runtime:$paging_version" // For Kotlin use paging-runtime-ktx

   // alternatively - without Android dependencies for testing
   testImplementation "androidx.paging:paging-common:$paging_version" // For Kotlin use paging-common-ktx

   // optional - RxJava support
   implementation "androidx.paging:paging-rxjava2:$paging_version" // For Kotlin use paging-rxjava2-ktx
 }
    
  1. 構建對象。Room 數據庫提供了 DataSource.Factory 對象或者 自定義對象
@Dao
interface StudentDao {
	
    @Query("SELECT * FROM Student ORDER BY name COLLATE NOCASE ASC")
    fun getAllStudent(): DataSource.Factory<Int, Student>
}
  1. 生成PagedList。將 DataSource.Factory 的實例傳遞到 LivePagedListBuilderRxPagedListBuilder 對象。
class HomeViewModel(context: Context): BaseViewModel() {

    companion object {
        private const val PAGE_SIZE = 15
        private const val ENABLE_PLACEHOLDERS = false
    }

    val dao = StudentDb.get().studentDao()
	
	// 傳遞實例給 LivePagedListBuilder ,分頁配置
    val allStudents = LivePagedListBuilder(dao.getAllStudent(), PagedList.Config.Builder()
        .setPageSize(PAGE_SIZE)                         //配置分頁加載的數量
        .setEnablePlaceholders(ENABLE_PLACEHOLDERS)     //配置是否啓動PlaceHolders
        .setInitialLoadSizeHint(PAGE_SIZE)              //初始化加載的數量
        .build()).build()
}
  1. 創建 Adapter繼承PagedListAdapter
    PagedListAdapter繼承自RecyclerView.Adapter
    在這裏插入圖片描述

PagedListAdapter需要接收一個DiffUtil.ItemCallback參數進行對象的構建。

class StudentAdapter: PagedListAdapter<Student, StudentViewHolder>(diffCallback) {

    override fun onBindViewHolder(holder: StudentViewHolder, position: Int) {
        holder.bindTo(getItem(position))
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StudentViewHolder =
        StudentViewHolder(parent)

    companion object {
    
    	// 用於計算列表中兩個非空 item 之間的差異的回調。
        private val diffCallback = object : DiffUtil.ItemCallback<Student>() {
        
        	// 檢查兩個對象是否表示同一 item 數據。
            override fun areItemsTheSame(oldItem: Student, newItem: Student): Boolean =
                oldItem.id == newItem.id
			
			// 檢查兩個項目是否具有相同的數據。
            override fun areContentsTheSame(oldItem: Student, newItem: Student): Boolean =
                oldItem == newItem
        }
    }
}
  1. UI頁面使用數據
override fun initView() {

    val adapter = StudentAdapter()
    val layoutManager = LinearLayoutManager(activity)
    rv_list.layoutManager = layoutManager
    rv_list.adapter = adapter
    
    // 將數據的變化反映到UI上
    viewModel.allStudents.observe(this, Observer {
        adapter.submitList(it)
    })
}

詳細使用代碼請參見:YGragon/FrameDemo

總結

Paging是分頁庫,也就是將大量的數據通過一段一段的返回給頁面展示。而大量的數據可以從網絡請求返回,也可以是從Room數據庫中讀取。從Room數據庫中讀取需要創建數據庫實例DAOEntity,而將數據展示在列表需要用到PagedListAdapterPagedListAdapter繼承自RecyclerView.Adapter,在該Adapter中需要傳入diffCallback,用於判斷數據是否是最新的。最後就是在UI中通過LiveData監聽數據的變化及時更新到 UI

參考

上車

佛系原創號主
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章