上篇文章 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)獲取數據庫中學生的姓名列表
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
// 還有一些可選項,可查看上面的版本依賴鏈接
- 定義實體類,用
@Entity
注解
@Entity(tableName = "Student")
data class Student(
// 字段1,主鍵自增
@PrimaryKey(autoGenerate = true)
val id: Int,
// 字段2
val name: String)
- 創建
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)
}
- 創建
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)"
)
- 獲取數據,展示
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
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
}
- 構建對象。
Room
數據庫提供了DataSource.Factory
對象或者 自定義對象
@Dao
interface StudentDao {
@Query("SELECT * FROM Student ORDER BY name COLLATE NOCASE ASC")
fun getAllStudent(): DataSource.Factory<Int, Student>
}
- 生成
PagedList
。將DataSource.Factory
的實例傳遞到LivePagedListBuilder
或RxPagedListBuilder
對象。
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()
}
- 創建
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
}
}
}
- 在
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
數據庫中讀取需要創建數據庫實例
、DAO
、Entity
,而將數據展示在列表需要用到PagedListAdapter
。PagedListAdapter
繼承自RecyclerView.Adapter
,在該Adapter
中需要傳入diffCallback
,用於判斷數據是否是最新的。最后就是在UI
中通過LiveData
監聽數據的變化及時更新到 UI
。
參考
上車
佛系原創號主