本篇文章主要介紹以下幾個(gè)知識(shí)點(diǎn):
- ViewModel
- Lifecycles
- LiveData
- Room
- WorkManager
內(nèi)容參考自:第一行代碼第3版, Android Jetpack

Jetpack 是一個(gè)開發(fā)組件的工具集,其作用主要是編寫出更簡潔的代碼,簡化開發(fā)過程。這些組件通常是定義在 AndroidX 庫當(dāng)中的。
官方 Jetpack 目前的全家福如下:

目前 Android 官方最為推薦的項(xiàng)目架構(gòu)是 MVVM,而 Jetpack 中的許多架構(gòu)組件是專門為 MVVM 架構(gòu)量身打造的。
1. ViewModel
在傳統(tǒng)的開發(fā)模式下,Activity 扮演著多重角色,既要負(fù)責(zé)邏輯處理,又要控制 UI 展示,甚至還要處理網(wǎng)絡(luò)回調(diào)等,在大型項(xiàng)目中顯得過于臃腫且難以維護(hù)。
ViewModel 是專門用于存放與界面相關(guān)的數(shù)據(jù)的,因而可以幫助 Activity 分擔(dān)一部分工作。
另外,ViewModel 的生命周期和 Activity 的不同,它可以保證在手機(jī)屏幕旋轉(zhuǎn)時(shí)不會(huì)被重建,只有當(dāng) Activity 退出時(shí)才會(huì)跟著 Activity 一起銷毀:

1.1 ViewModel 的基本用法
首先在 build.gradle 中添加依賴:
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
一般來說,會(huì)給每一個(gè) Activity 和 Fragment 都創(chuàng)建一個(gè)對應(yīng)的 ViewModel,如給 MainActivity 創(chuàng)建一個(gè)對應(yīng)的 MainViewModel 如下:
class MainViewModel : ViewModel() { }
舉個(gè)栗子,要實(shí)現(xiàn)一個(gè)計(jì)數(shù)器的功能,就可以在 ViewModel 中加入一個(gè)變量:
class MainViewModel : ViewModel() {
var counter = 0 // 用于計(jì)數(shù)
}
界面上一個(gè)按鈕和一個(gè)顯示計(jì)數(shù)的 TextView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/counterText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="32sp" />
<Button
android:id="@+id/plusButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="plus one" />
</LinearLayout>
創(chuàng)建 ViewModel 的實(shí)例需要通過 ViewModelProvider 來獲取,其語法規(guī)則如下:
ViewModelProvider(<你的 Activity 或 Fragment 實(shí)例>).get(<你的 ViewModel>::class.java)
接下來在 MainActivity 中創(chuàng)建 ViewModel 的實(shí)例和計(jì)數(shù)器邏輯:
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 獲取 ViewModel 實(shí)例
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
plusButton.setOnClickListener {
// 點(diǎn)擊按鈕計(jì)數(shù)器加 1 并刷新計(jì)數(shù)
viewModel.counter++
refreshCounter()
}
refreshCounter()
}
private fun refreshCounter() {
// 顯示當(dāng)前計(jì)數(shù)
counterText.text = viewModel.counter.toString()
}
}
注: 通過 ViewModelProvider 來獲取實(shí)例是因?yàn)?ViewModel 有其獨(dú)有的生命周期,其生命周期長于 Activity,若在 onCreate() 直接創(chuàng)建 ViewModel 的實(shí)例,那么每次 onCreate() 時(shí)都會(huì)創(chuàng)建一個(gè)新的實(shí)例,那手機(jī)屏幕旋轉(zhuǎn)時(shí)就無法保留其中的數(shù)據(jù)了。
以上便是 ViewModel 的基本用法了。
1.2 向 ViewModel 傳遞參數(shù)
現(xiàn)有個(gè)需求,在程序退出時(shí)保存當(dāng)前的計(jì)數(shù),重新打開程序時(shí)讀取保存的計(jì)數(shù),并傳遞給 MainViewModel,那么可以修改 MainViewModel 如下:
// 在構(gòu)造函數(shù)中添加 countReserved 參數(shù)用于記錄之前保存的計(jì)數(shù)
class MainViewModel(countReserved: Int) : ViewModel() {
var counter = countReserved // 用于計(jì)數(shù)
}
向 ViewModel 的構(gòu)造函數(shù)中傳遞參數(shù)需要借助 ViewModelProvider.Factory,其實(shí)現(xiàn)如下:
class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MainViewModel(countReserved) as T
}
}
注:ViewModelProvider.Factory 接口要求必須實(shí)現(xiàn) create() 方法,這邊可以創(chuàng)建 MainViewModel 的實(shí)例是因?yàn)?create() 方法的執(zhí)行時(shí)機(jī)和 Activity 的生命周期無關(guān),不會(huì)產(chǎn)生之前提到的問題。
接著修改 MainActivity 的代碼如下:
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
lateinit var sp: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 獲取 SharedPreferences 實(shí)例,讀取之前保存的計(jì)數(shù)
sp = getPreferences(Context.MODE_PRIVATE)
val countReserved = sp.getInt(SP_COUNT_RESERVED, 0)
// 傳入保存的計(jì)數(shù)并獲取 ViewModel 實(shí)例
viewModel = ViewModelProvider(this, MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
plusButton.setOnClickListener {
// 點(diǎn)擊按鈕計(jì)數(shù)器加 1 并刷新計(jì)數(shù)
viewModel.counter++
refreshCounter()
}
clearButton.setOnClickListener {
// 點(diǎn)擊按鈕計(jì)數(shù)器清零并刷新計(jì)數(shù)
viewModel.counter = 0
refreshCounter()
}
refreshCounter()
}
private fun refreshCounter() {
// 顯示當(dāng)前計(jì)數(shù)
counterText.text = viewModel.counter.toString()
}
override fun onPause() {
super.onPause()
// 退出保存計(jì)數(shù)
sp.edit {
putInt(SP_COUNT_RESERVED, viewModel.counter)
}
}
companion object {
const val SP_COUNT_RESERVED = "count_reserved"
}
}
這樣,退出程序并重新打開計(jì)數(shù)器的值就不會(huì)丟失了,只能點(diǎn)擊 Clear 按鈕才會(huì)清零。
2. Lifecycles
若有個(gè)需求,在一個(gè)非 Activity 的類中去感知 Activity 的生命周期,那么可以通過在 Activity 中嵌入一個(gè)隱藏的 Fragment 來進(jìn)行感知,或通過手寫監(jiān)聽器的方式來進(jìn)行感知等。
比如通過手寫監(jiān)聽器的方式來對 Activity 的生命周期進(jìn)行感知:
class MyObserver {
fun activityStart() { }
fun activityStop() { }
}
class MainActivity : AppCompatActivity() {
lateinit var observer: MyObserver
override fun onCreate(savedInstanceState: Bundle?) {
observer = MyObserver()
}
override fun onStart() {
super.onStart()
observer.activityStart()
}
override fun onStop() {
super.onStop()
observer.activityStop()
}
}
上面這種方式雖然可以工作,但不夠優(yōu)雅,需要在 Activity 中編寫大量的邏輯。
而 Lifecycles 組件就是為了解決這個(gè)問題出現(xiàn)的,它可以讓任何一個(gè)類輕松感知到 Activity 的生命周期,同時(shí)無需在 Activity 中編寫大量的邏輯處理。
下面舉個(gè)栗子來說明 Lifecycles 組件的用法,新建一個(gè) MyObserver 類實(shí)現(xiàn) LifecycleObserver 接口:
class MyObserver : LifecycleObserver {
// LifecycleObserver 是個(gè)空接口,只需接口實(shí)現(xiàn)聲明,無需重寫任何方法
}
接下來就可以在 MyObserver 中定義任何方法,若需要感知 Activity 的生命周期,還需借助額外的注解,如定義 activityStart() 和 activityStop() 方法如下:
class MyObserver : LifecycleObserver {
// 生命周期的類型有 7 種:
// ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY 對應(yīng) Activity 中相應(yīng)的生命周期回調(diào)
// ON_ANY 表示可匹配 Activity 的任何生命周期回調(diào)
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun activityStart() {
Log.d("MyObserver", "activityStart")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun activityStop() {
Log.d("MyObserver", "activityStop")
}
}
這樣,上面的 activityStart() 和 activityStop() 方法就會(huì)分別在 Activity 的 onStart() 和 onStop() 觸發(fā)時(shí)執(zhí)行。
此時(shí),當(dāng) Activity 的生命周期發(fā)生變化時(shí)并不會(huì)去通知 MyObserver,還需借助 LifecycleOwner 讓它得到通知,其語法結(jié)構(gòu)如下:
// 調(diào)用 LifecycleOwner 的 getLifecycle() 方法得到 Lifecycle 對象
// 然后調(diào)用它的 addObserver() 方法來觀察 LifecycleOwner 的生命周期
// 再把 MyObserver 的實(shí)例傳進(jìn)去
lifecycleOwner.lifecycle.addObserver(MyObserver())
對于 LifecycleOwner,只要 Activity 是繼承自 AppCompatActivity 或 Fragment 是繼承自 androidx.fragment.app.Fragment 的,那么它本身就是一個(gè) LifecycleOwner 實(shí)例,即在 Activity 中直接調(diào)用即可:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 傳入 MyObserver 的實(shí)例就可以自動(dòng)感知 Activity 的生命周期了
lifecycle.addObserver(MyObserver())
}
}
上面 MyObserver 雖然可以感知 Activity 是生命周期,卻無法主動(dòng)獲取當(dāng)前的生命周期狀態(tài),若需要主動(dòng)獲取則可在 MyObserver 的構(gòu)造方法中傳人 Lifecycle 對象:
class MyObserver(val lifecycle: Lifecycle) : LifecycleObserver {
...
}
有了 Lifecycle 對象后就可在任何地方調(diào)用 lifecycle.currentState 來主動(dòng)獲取當(dāng)前的生命周期狀態(tài),lifecycle.currentState 返回的是一個(gè)枚舉類型:
// 共 5 種狀態(tài):
INITIALIZED、DESTROYED、CREATED、STRARED、RESUMED
它們與 Activity 的生命周期回調(diào)所對應(yīng)關(guān)系如下:

3. LiveData
LiveData 是 Jetpack 提供的一種響應(yīng)式編程組件,它可包含任何類型的數(shù)據(jù),并在數(shù)據(jù)發(fā)生變化時(shí)通知給觀察者。
3.1 LiveData 的基本用法
前面計(jì)數(shù)器的栗子中,是在 Activity 中手動(dòng)獲取 ViewModel 中的數(shù)據(jù),但 ViewModel 卻無法將數(shù)據(jù)的變化主動(dòng)通知給 Activity。
注:由于 ViewModel 的生命周期長于 Activity,若把 Activity 的實(shí)例傳給 ViewModel 就可能會(huì)因?yàn)?Activity 無法釋放而造成內(nèi)存泄漏。
而用 LiveData 來包裝計(jì)數(shù)器的計(jì)數(shù),然后在 Activity 中去觀察它,就可以將數(shù)據(jù)變化通知給 Activity 了。
修改 MainViewModel 的代碼如下:
class MainViewModel(countReserved: Int) : ViewModel() {
// MutableLiveData 是一種可變的 LiveData,它主要有 3 種讀寫數(shù)據(jù)的方法:
// getValue() 用于獲取 LiveData 中包含的數(shù)據(jù)
// setValue() 用于給 LiveData 設(shè)置數(shù)據(jù),但只能在主線程中調(diào)用
// postValue() 用于在非主線程中給 LiveData 設(shè)置數(shù)據(jù)
var counter = MutableLiveData<Int>()
init {
counter.value = countReserved
}
// 計(jì)數(shù)器加 1
fun plusOne() {
val count = counter.value ?: 0
counter.value = count + 1
}
// 計(jì)數(shù)器清零
fun clear() {
counter.value = 0
}
}
接著修改 MainActivity 的代碼如下:
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
lateinit var sp: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycle.addObserver(MyObserver(lifecycle))
// 獲取 SharedPreferences 實(shí)例,讀取之前保存的計(jì)數(shù)
sp = getPreferences(Context.MODE_PRIVATE)
val countReserved = sp.getInt(SP_COUNT_RESERVED, 0)
// 獲取 ViewModel 實(shí)例
viewModel = ViewModelProvider(this, MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
plusButton.setOnClickListener {
// 點(diǎn)擊按鈕計(jì)數(shù)器加 1
viewModel.plusOne()
}
clearButton.setOnClickListener {
// 點(diǎn)擊按鈕計(jì)數(shù)器清零
viewModel.clear()
}
// 調(diào)用 observe 觀察數(shù)據(jù)變化
// observe() 方法接收兩個(gè)參數(shù):LifecycleOwner 對象 和 Observer 接口
viewModel.counter.observe(this, Observer { count ->
counterText.text = count.toString()
})
}
override fun onPause() {
super.onPause()
// 保存計(jì)數(shù)
sp.edit {
putInt(SP_COUNT_RESERVED, viewModel.counter.value ?: 0)
}
}
companion object {
const val SP_COUNT_RESERVED = "count_reserved"
}
}
注:上面 observe() 方法中沒有用到函數(shù)式 API 的寫法,當(dāng)一個(gè) Java 方法同時(shí)接收兩個(gè)單抽象方法接口參數(shù)時(shí),要么同時(shí)使用函數(shù)式 API 寫法,要么都不用。由于第一個(gè)參數(shù)傳 this,因此第二個(gè)參數(shù)就無法使用函數(shù)式 API 了。
當(dāng)然若添加如下依賴庫:
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
上面的 observe() 方法就可改為:
viewModel.counter.observe(this) { count ->
counterText.text = count.toString()
}
以上就是 LiveData 的基本用法,但不是很規(guī)范,因?yàn)榘?counter 這個(gè)可變的 LiveData 暴露給了外部,這樣在 ViewModel 外面也可給 counter 設(shè)置數(shù)據(jù),從而破壞了 ViewModel 的封裝性,有一定的風(fēng)險(xiǎn)。
比較推薦的做法是,永遠(yuǎn)只暴露不可變的 LiveData 給外部,修改 MainViewModel 如下:
class MainViewModel(countReserved: Int) : ViewModel() {
val counter: LiveData<Int> get() = _counter
// _counter 對外部不可見
private val _counter = MutableLiveData<Int>()
init {
_counter.value = countReserved
}
// 計(jì)數(shù)器加 1
fun plusOne() {
val count = _counter.value ?: 0
_counter.value = count + 1
}
// 計(jì)數(shù)器清零
fun clear() {
_counter.value = 0
}
}
這樣,當(dāng)外部調(diào)用 counter 變量時(shí),實(shí)際獲得的是 _counter 的實(shí)例,但無法給 counter 設(shè)置數(shù)據(jù),從而保證了 ViewModel 的數(shù)據(jù)封裝性。
3.2 map 和 switchMap
LiveData 為了應(yīng)付不同的場景需求,提供了兩種轉(zhuǎn)換方法:map() 和 switchMap() 方法。
- map() 方法: 將實(shí)際包含數(shù)據(jù)的 LiveData 和僅用于觀察數(shù)據(jù)的 LiveData 進(jìn)行轉(zhuǎn)換。
舉個(gè)栗子,定義一個(gè) User 類如下:
data class User(var firstName: String, var lastName: String, var age: Int)
將 User 對象轉(zhuǎn)換成一個(gè)只包含用戶姓名的字符串暴露給外部,就可以使用 map() 方法:
class MainViewModel : ViewModel() {
private val _userLiveData = MutableLiveData<User>()
// 當(dāng) _userLiveData 數(shù)據(jù)變化時(shí),map() 方法會(huì)監(jiān)聽到并執(zhí)行轉(zhuǎn)換函數(shù)中的邏輯,
// 然后把轉(zhuǎn)換后的數(shù)據(jù)通知給 userName 的觀察者
val userName: LiveData<String> = Transformations.map(_userLiveData) {
"${it.firstName} ${it.lastName}"
}
}
- switchMap() 方法:將轉(zhuǎn)換函數(shù)中返回的 LiveData 對象轉(zhuǎn)化成另外一個(gè)可觀察的 LiveData 對象。
舉個(gè)栗子,模擬 ViewModel 中的某個(gè) LiveData 對象是調(diào)用另外的方法獲取的,首先新建個(gè)單例類如下:
object Repository {
// 模擬根據(jù) userId 獲取 User 對象
// 每次調(diào)用 getUser() 方法都會(huì)返回一個(gè)新的 LiveData 實(shí)例
fun getUser(userId: String): LiveData<User>{
val liveData = MutableLiveData<User>()
liveData.value = User(userId, userId, 18)
return liveData
}
}
借助 switchMap()方法,修改 MainViewModel 如下:
class MainViewModel : ViewModel() {
// 用于觀察 userId 的數(shù)據(jù)變化
private val userIdLiveData = MutableLiveData<String>()
// 傳入 userId,switchMap() 方法會(huì)對它進(jìn)行觀察
val user: LiveData<User> = Transformations.switchMap(userIdLiveData){
Repository.getUser(it)
}
// 把傳入的 userId 設(shè)置給 userIdLiveData, userIdLiveData 變化后 switchMap() 方法就會(huì)執(zhí)行
// 將 Repository.getUser() 返回的 LiveData 對象轉(zhuǎn)換成一個(gè)可觀察的 LiveData 對象
fun getUser(userId: String){
userIdLiveData.value = userId
}
}
對于 Activity 來說,只有觀察 user 這個(gè) LiveData 對象即可,調(diào)用如下:
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
getUserButton.setOnClickListener {
// 點(diǎn)擊按鈕傳入隨機(jī)數(shù)作 userId 獲取用戶數(shù)據(jù)
// 數(shù)據(jù)獲取完成后,可觀察的 LiveData 對象的 observe() 方法將會(huì)得到通知,顯示名字
val userId = (0..10000).random().toString()
viewModel.getUser(userId)
}
viewModel.user.observe(this){
userText.text = it.firstName
}
}
}
小結(jié):LiveData 之所以能成為 Activity 與 ViewModel 之間通信的橋梁,并且不會(huì)有內(nèi)存泄漏的風(fēng)險(xiǎn),靠的是 Lifecycles 組件。LiveData 內(nèi)部使用了 Lifecycles 組件來自我感知生命周期的變化,從而在 Activity 銷毀時(shí)及時(shí)釋放引用。
另外,當(dāng) Activity 不可見狀態(tài)時(shí),若 LiveData 的數(shù)據(jù)發(fā)生了變化,是不會(huì)通知給觀察者的,只有當(dāng) Activity 恢復(fù)可見狀態(tài)時(shí),才會(huì)通知(只有最新的那份數(shù)據(jù)才會(huì)通知給觀察者),減少性能消耗。
4. Room
ORM(Object Relational Mapping) 叫對象關(guān)系映射。將面向?qū)ο蟮恼Z言和面向關(guān)系的數(shù)據(jù)庫之間建立一種映射關(guān)系,就是 ORM 了,Jetpack 中的 Room 就是一個(gè) ORM 框架。
4.1 使用 Room 進(jìn)行增刪改查
Room 主要由3部分組成:
Entity :定義封裝實(shí)際數(shù)據(jù)的實(shí)體類,每個(gè)實(shí)體類都會(huì)在數(shù)據(jù)庫中有一張對于的表,并且表中的列是根據(jù)實(shí)體類中的字段自動(dòng)生成的。
Dao:數(shù)據(jù)訪問對象,封裝對數(shù)據(jù)庫的各項(xiàng)操作,在編程時(shí)邏輯層直接和 Dao 層交互即可。
Database:定義數(shù)據(jù)庫中的關(guān)鍵信息,包括數(shù)據(jù)庫版本號(hào)、包含的實(shí)體類以及提供 Dao 層的訪問實(shí)例。
要使用 Room,需要在 build.gradle 中添加如下依賴:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
dependencies {
// Room
implementation "androidx.room:room-runtime:2.2.5"
kapt "androidx.room:room-compiler:2.2.5"
}
下面舉個(gè)栗子來實(shí)現(xiàn) Room 的3個(gè)組成部分,首先是用 @Entity 注解來定義 Entity 實(shí)體類如下:
@Entity
data class User(var firstName: String, var lastName: String, var age: Int) {
// 把 id 字段設(shè)為主鍵,并自動(dòng)生成
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
接著使用 @Dao 注解來定義 Dao,新建 UserDao 接口如下:
@Dao
interface UserDao {
// 插入對象
@Insert
fun insertUser(user: User): Long
// 更新對象
@Update
fun updateUser(newUser: User)
// 查詢?nèi)繉ο? @Query("select * from User")
fun loadAllUsers(): List<User>
// 查詢所有年齡大于指定年齡的對象
@Query("select * from User where age > :age")
fun loadUsersOlderThan(age: Int): List<User>
// 刪除對象
@Delete
fun deleteUser(user: User)
// 通過 lastName 來刪除對象
@Query("delete from User where lastName = :lastName")
fun deleteUserByLastName(lastName: String): Int
}
最后,使用 @Database 注解來定義 Database,只需定義好數(shù)據(jù)庫版本號(hào)、包含哪些實(shí)體類、提供 Dao 層的訪問實(shí)例,如下:
// 注解中聲明版本號(hào)及包含的實(shí)體類,多個(gè)實(shí)體類用逗號(hào)隔開即可
@Database(version = 1, entities = [User::class])
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var instance: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
// 不為空直接返回
instance?.let { return it }
// 否則構(gòu)建一個(gè)實(shí)例
return Room.databaseBuilder(
context.applicationContext,// 第一個(gè)參數(shù)一定要使用 applicationContext,否至容易出現(xiàn)內(nèi)存泄漏
AppDatabase::class.java, // 第二個(gè)參數(shù)是 AppDatabase 的 Class 類型
"app_database" // 第三個(gè)參數(shù)是數(shù)據(jù)庫名
).build().apply {
instance = this
}
}
}
}
在 Activity 中的使用如下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 獲取 UserDao 的實(shí)例
val userDao = AppDatabase.getDatabase(this).userDao()
val user1 = User("李", "小蘭", 18)
val user2 = User("王", "小紅", 10)
// 新增
addUserButton.setOnClickListener {
thread {
user1.id = userDao.insertUser(user1)
user2.id = userDao.insertUser(user2)
}
}
// 更新
updateUserButton.setOnClickListener {
thread {
user1.age = 20
userDao.updateUser(user1)
}
}
// 刪除
deleteUserButton.setOnClickListener {
thread {
userDao.deleteUserByLastName("小紅")
}
}
// 查詢
queryUserButton.setOnClickListener {
thread {
for (user in userDao.loadAllUsers()) {
Log.d("MainActivity", user.toString())
}
}
}
}
}
4.2 Room 的數(shù)據(jù)庫升級(jí)
在開發(fā)測試階段,在構(gòu)建 AppDatabase 實(shí)例時(shí),添加 fallbackToDestructiveMigration() 方法如下:
Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database")
.allbackToDestructiveMigration()
.build()
這樣只要數(shù)據(jù)庫進(jìn)行了升級(jí), Room 就會(huì)將當(dāng)前數(shù)據(jù)庫銷毀(數(shù)據(jù)全丟失了),再重建,實(shí)現(xiàn)自動(dòng)升級(jí)數(shù)據(jù)庫,但這只適合用在開發(fā)測試階段,下面介紹在 Room 中升級(jí)數(shù)據(jù)庫的正規(guī)寫法。
如在數(shù)據(jù)庫中添加一張 Book 表如下:
@Entity
data class Book(var name: String, var page: Int) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
@Dao
interface BookDao {
@Insert
fun insertBook(book: Book): Long
@Query("select * from Book")
fun loadAllBooks(): List<Book>
}
接下來要升級(jí)數(shù)據(jù)庫,需要修改 AppDatabase 如下:
// 將版本號(hào)升級(jí)成了2,并將 Book 類添加到實(shí)體類聲明中
@Database(version = 2, entities = [User::class, Book::class])
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun bookDao(): BookDao
companion object {
// 當(dāng)數(shù)據(jù)庫版本從 1 升級(jí)到 2 時(shí)就執(zhí)行這個(gè)匿名類 Migration 中的邏輯
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("create table Book(id integer primary key autoincrement not null, name text not null, pages integer not null)")
}
}
private var instance: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
instance?.let { return it }
return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database")
.addMigrations(MIGRATION_1_2) // 傳入 MIGRATION_1_2
.build().apply {
instance = this
}
}
}
}
這樣 Room 就會(huì)自動(dòng)根據(jù)當(dāng)前數(shù)據(jù)庫的版本號(hào)執(zhí)行升級(jí)邏輯,讓數(shù)據(jù)庫始終是最新版本。
當(dāng)向現(xiàn)有表中添加新的列而不是新增一張表時(shí),使用 alert 語句修改表結(jié)構(gòu)即可,如在 Book 中添加一個(gè) author 字段:
@Entity
data class Book(var name: String, var page: Int, var author: String) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
那么對應(yīng)的數(shù)據(jù)庫表升級(jí),修改 AppDatabase 如下:
// 將版本號(hào)升級(jí)成了3
@Database(version = 3, entities = [User::class, Book::class])
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun bookDao(): BookDao
companion object {
// 當(dāng)數(shù)據(jù)庫版本從 1 升級(jí)到 2 時(shí)就執(zhí)行這個(gè)匿名類 Migration 中的邏輯
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("create table Book(id integer primary key autoincrement not null, name text not null, pages integer not null)")
}
}
// 當(dāng)數(shù)據(jù)庫版本從 2 升級(jí)到 3 時(shí)執(zhí)行
private val MIGRATION_2_3 = object : Migration(2, 3){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table Book add column author text not null default 'unknown'")
}
}
private var instance: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
instance?.let { return it }
return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3) // 傳入 MIGRATION_1_2, MIGRATION_2_3
.build().apply {
instance = this
}
}
}
}
5. WorkManager
WorkManager 是一個(gè)處理定時(shí)任務(wù)的工具,它可以保證即使在應(yīng)用退出甚至手機(jī)重啟的情況下,之前注冊的任務(wù)仍然會(huì)得到執(zhí)行,因此它適合執(zhí)行一些定期和服務(wù)器進(jìn)行交互的任務(wù),如周期性同步數(shù)據(jù)等。
WorkManager 和 Service 并不相同,也沒直接的聯(lián)系,Service 是四大組件之一,在沒被銷毀時(shí)一直運(yùn)行在后臺(tái)。
另外,系統(tǒng)為了減少電量消耗,使用 WorkManager 注冊的周期性任務(wù)不能保證一定會(huì)準(zhǔn)時(shí)執(zhí)行,可能會(huì)將觸發(fā)時(shí)間臨近的幾個(gè)任務(wù)放在一起執(zhí)行。
5.1 WorkManager 的基本用法
要使用 WorkManager,需要在 build.gradle 中添加如下依賴:
implementation "androidx.work:work-runtime:2.3.4"
其基本用法主要有以下三步:
(1)定義一個(gè)后臺(tái)任務(wù),并實(shí)現(xiàn)具體的任務(wù)邏輯
(2)配置該后臺(tái)任務(wù)的運(yùn)行條件和約束信息,并構(gòu)建后臺(tái)任務(wù)請求
(3)將該后臺(tái)任務(wù)請求傳入 WorkManager 的
enqueue()方法中,系統(tǒng)會(huì)在合適的時(shí)間運(yùn)行
下面舉個(gè)栗子,首先定義一個(gè)后臺(tái)任務(wù)如下:
// 每一個(gè)后臺(tái)任務(wù)都必須繼承自 Worker 類,并調(diào)用它唯一的構(gòu)造函數(shù),然后重寫 doWork() 方法
class SimpleWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
// doWork 方法不會(huì)運(yùn)行在主線程中,可處理耗時(shí)操作
override fun doWork(): Result {
Log.d("SimpleWorker", "do work in SimpleWorker")
// 返回一個(gè) Result 對象,成功:Result.success() 失?。篟esult.failure() 或 Result.retry()
return Result.success()
}
}
接著配置運(yùn)行條件和約束信息:
// WorkRequest.Builder 有兩個(gè)子類:
// OneTimeWorkRequest.Builder:構(gòu)建單次運(yùn)行的后臺(tái)任務(wù)請求
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).build()
// PeriodicWorkRequest.Builder:構(gòu)建周期性運(yùn)行的后臺(tái)任務(wù)請求
// 如構(gòu)建運(yùn)行周期間隔不能短于15分鐘的后臺(tái)任務(wù)
val request = PeriodicWorkRequest.Builder(SimpleWorker::class.java, 15, TimeUnit.MINUTES).build()
最后把構(gòu)建出的任務(wù)傳入 WorkManager 中:
WorkManager.getInstance(context).enqueue(request)
如在 Activity 中調(diào)用:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
doWorkButton.setOnClickListener {
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).build()
WorkManager.getInstance(this).enqueue(request)
}
}
}
5.2 使用 WorkManager 處理復(fù)雜的任務(wù)
WorkManager 除了運(yùn)行時(shí)間外,還允許控制許多其他的東西,如下:
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
// 指定延遲時(shí)間后運(yùn)行
.setInitialDelay(5, TimeUnit.MINUTES)
// 添加標(biāo)簽
.addTag("simple")
// 若任務(wù)返回了 Result.retry(),可以結(jié)合 setBackoffCriteria() 方法來重新執(zhí)行任務(wù)
// 第一個(gè)參數(shù)有兩種:LINER 重試時(shí)間以線性方式延遲,EXPONENTIAL 重試時(shí)間以指數(shù)方式延遲
.setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS)
.build()
// 通過標(biāo)簽取消任務(wù)
WorkManager.getInstance(this).cancelAllWorkByTag("simple")
// 通過 id 取消任務(wù)
WorkManager.getInstance(this).cancelWorkById(request.id)
// 取消全部任務(wù)
WorkManager.getInstance(this).cancelAllWork()
另外,可以調(diào)用 getWorkInfoByIdLiveData() 或 getWorkInfoByTagLiveData() 方法來監(jiān)聽任務(wù)運(yùn)行結(jié)果,如:
WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id).observe(this){
if (it.state == WorkInfo.State.SUCCEEDED){
Log.d("MainActivity", "do work success")
} else if (it.state == WorkInfo.State.FAILED){
Log.d("MainActivity", "do work failed")
}
}
再介紹個(gè) WorkManager 中比較有特色的功能——鏈?zhǔn)饺蝿?wù)。
如定義了3個(gè)獨(dú)立后臺(tái)任務(wù):同步數(shù)據(jù)、壓縮數(shù)據(jù)、上傳數(shù)據(jù),要想先同步、再壓縮、最后上傳就可以借助鏈?zhǔn)饺蝿?wù)來實(shí)現(xiàn):
val sync = ...
val compress = ...
val upload = ...
WorkManger.getInstance(this)
.beginWith(sync)
.then(compress)
.then(upload)
.enqueue()
注:必須前一個(gè)任務(wù)成功后下一個(gè)任務(wù)才會(huì)運(yùn)行,若某個(gè)任務(wù)失敗或取消了,那么接下來的任務(wù)都不會(huì)運(yùn)行。
注:WorkManager 在國產(chǎn)手機(jī)上可能會(huì)非常不穩(wěn)定,不要依賴它去實(shí)現(xiàn)核心功能。
本篇文章就介紹到這。