Jetpack學習之ViewModel、Lifecycles、LiveData
宣告:本學習筆記基于郭霖大大的《第一行代碼 第3版》并結合官方檔案、網路資源以及個人理解整理而成,歡迎大家討論指正
Jetpack簡介
主要組成
? Jetpack是一個開發工具集,能夠協助開發者撰寫出更簡潔的代碼,簡化開發程序,并且這些組件有一個很好的特點,他們大部分不依賴與任何Android系統版本,這意味著這些組件通常是定義在AndroidX庫當中,并且擁有非常好的向下兼容性,
? Jetpack全家桶包含內容非常多,主要可分為 基礎、架構、行為、界面 4個部分,本次學習主要聚焦于對架構的學習,其中很多組件更是專門為MVVM架構量身打造的,
使用前準備
? Jetpack中組件通常都是以AndroidX庫的形式發布的,所以常用的Jetpack組件會在創建Android專案時被自動包含進去,使用者個可以根據官方檔案的說明,在app/build.gradle根據需要添加依賴
dependencies {
def lifecycle_version = "2.4.0-rc01"
def arch_version = "2.1.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
// Saved state module for ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
// Annotation processor
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
// alternately - if using Java8, use the following instead of lifecycle-compiler
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// optional - helpers for implementing LifecycleOwner in a Service
implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version"
// optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
// optional - ReactiveStreams support for LiveData
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
// optional - Test helpers for LiveData
testImplementation "androidx.arch.core:core-testing:$arch_version"
}
示例準備
? 還有一個簡單的,包含了一堆按鈕的布局檔案activity_main.xml,可以提前寫進進去了
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/infoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="32sp"/>
<Button
android:id="@+id/plusOneBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Plus One"/>
<Button
android:id="@+id/clearBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Clear"/>
<Button
android:id="@+id/getUserBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Get User"/>
<Button
android:id="@+id/addDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Add Data"/>
<Button
android:id="@+id/updateDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Update Data"/>
<Button
android:id="@+id/deleteDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Delete Data"/>
<Button
android:id="@+id/queryDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Query Data"/>
<Button
android:id="@+id/doWorkBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Do Work"/>
</LinearLayout>
ViewModel
簡介
? Jetpack最重要的組件之一,可以幫任務太重的Activity分擔一部分作業,用于專門存放界面相關資料,即界面上能看到的資料,其相關變數都應該存放在ViewModel中,而不是Activity,從而減輕Activity作業,
重要特性
? 手機橫豎屏旋轉時,Activity會被重新創建,存放在Activity中的資料也會丟失,而ViewModel不會因為螢屏旋轉而重新創建,只有當Activity退出時才會跟著Activity一起銷毀,所以非常適合存放界面上的變數,
生命周期示意圖
基本用法
? 通常來講,比較好的編程規范是要給每個Activity和Fragment都創建一個對應的ViewModel,這里以創建一個簡單的計數器為例
-
為
MainActivity創建一個對應的MainViewModel類繼承自ViewModelimport androidx.lifecycle.ViewModel class MainViewModel: ViewModel() { //計數器變數 var counter = 0 } -
在
MainActivity中獲取MainViewModel并呼叫import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.LayoutInflater import androidx.lifecycle.ViewModelProvider import com.example.jetpacktest.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { val binding: ActivityMainBinding by lazy{ ActivityMainBinding.inflate(LayoutInflater.from(this)) } lateinit var viewModel: MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) //注意:ViewModelProviders.of已棄用 //注意:絕對不可以直接去創建ViewModel實體 viewModel = ViewModelProvider(this)[MainViewModel::class.java] //呼叫 binding.plusOneBtn.setOnClickListener { viewModel.counter++ refreshCounter() } } //重繪函式 private fun refreshCounter() { binding.infoText.text = viewModel.counter.toString() } }? 記住絕對不可以直接去創建
ViewModel實體,一定要通過ViewModelProvider(ViewModelStoreOwner)建構式來獲取,具體語法規則為為viewModel = ViewModelProvider(<你的Activity或Fragment實體>)[<你的ViewModel>::class.java]? 之所以要這樣寫是因為每次旋轉螢屏都會重新呼叫
onCreate()方法,若每次都創建新的實體就無法保存資料了,用上述方法,onCreate方法被再次呼叫,它會回傳一個與確切的與MainActivity相關聯的預先存在的ViewModel,這就是保存資料的原因,? 悲報:
? 在2019.08..07的版本更新中棄用了
ViewModelProviders.of(),您可以將Fragment或FragmentActivity傳遞給新的ViewModelProvider(ViewModelStoreOwner)建構式,以實作相同的功能,
向ViewModel傳遞引數
? 來看看需要通過建構式傳遞引數的情況,主要借助ViewModelProvider.Factory實作,并且還能實作退出程式后重新打開仍然保留資料的功能
-
修改
MainViewModel代碼,添加建構式class MainViewModel(countReserved: Int): ViewModel() { var counter = countReserved } -
新建一個
MainViewModelFactory類實作ViewModelProvider.Factory介面import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider class MainViewModelFactory(private val countReserved: Int): ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { //創建MainViewModel實體 return MainViewModel(countReserved) as T } }? 構造器中接受了
countReserved引數,并要求實作create()方法,并在里面創建MainViewModel實體,因為該方法的執行時機與Activity的生命周期無關,所以不會產生此前的問題, -
修改
MainActivityclass MainActivity : AppCompatActivity() { ... lateinit var viewModel: MainViewModel lateinit var sp: SharedPreferences override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) sp = getPreferences(Context.MODE_PRIVATE) val countReserved = sp.getInt("count_reservde",0) viewModel = ViewModelProvider( this, MainViewModelFactory(countReserved))[MainViewModel::class.java] ... binding.clearBtn.setOnClickListener { viewModel.counter = 0 refreshCounter() } refreshCounter() } override fun onPause() { super.onPause() //對計數進行保存 sp.edit { putInt("count_reservde",viewModel.counter) } } ... }? 在
onCreate()方法中,我們先獲取SharedPreferences的實體,用于關閉程式后保存資料,然后讀取之前保存的計數值,如果沒有讀到就以0為默認值,要注意在ViewModelProvider()方法中多傳入了一個引數,是將讀取到的計數值傳給了MainViewModelFactory,這有這樣才能將計數值傳遞給MainViewModel的建構式,
Lifecycles
簡介
Lifecycles用于感知Activity的生命周期(在非Activity類也可),下面是手寫Lifecycle監聽器的兩步方法
基本用法
-
新建一個
MyObserver類實作LifecycleObserver介面import android.util.Log import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent class MyObserver : LifecycleObserver{ //其實這是一個空方法介面,只需要實作宣告即可 //利用注解功能,會在Activity的OnStart()觸發時執行 @OnLifecycleEvent(Lifecycle.Event.ON_START) fun activityStart() { Log.d("MyObserver","activityStart") } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun activityStop() { Log.d("MyObserver","activityStop") } }? 利用注解功能傳入相應得生命周期時間,需要注意的是除了熟悉的六種外還多了一種ON_ANY型別,可以表示以匹配Activity的任何生命周期回呼
-
借助
LifecycleOwner獲取通知,在MainActivity中增加一行代碼,MyObserver就能自動感應Activity的生命周期了override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) ... //增加一行 lifecycle.addObserver(MyObserver()) ... }? 首先我們要了解
LifecycleOwner的語法結構是:LifecycleOwner.lifecycle.addObserver(MyObserver()),而只要你的Activity是繼承自AppCompatActivity,或者你的Fragment繼承自androidx.fragment.app.Fragment的,那么它們本身就是一個LifecycleOwner實體,在AndroidX庫中已經自動幫我們實作了,所以上示代碼才可以這樣寫, -
若是想要主動獲取當前生命周期狀態,只需在
MyObserver的建構式中傳入Lifecycle物件,相應修改MainActivity即可class MyObserver(val lifecycle: Lifecycle) : LifecycleObserver{ //其實這是一個空方法介面,只需要實作宣告即可 ... }lifecycle.addObserver(MyObserver(lifecycle))
生命周期對應狀態示意圖
LiveData
簡介
LiveData是一種可觀察的資料存盤器類,與常規的可觀察類不同,LiveData 具有生命周期感知能力,意指它遵循其他應用組件(如 Activity、Fragment 或 Service)的生命周期,這種感知能力可確保 LiveData 僅更新處于活躍生命周期狀態的應用組件觀察者,
之前示例代碼的缺陷:
? 一直都是在Activity中操作,每次加一先給ViewModel 中的計數加一,然后再呼叫其獲取,,而并不是ViewModel 主動通知回傳給Activity,在多執行緒任務中容易或渠道錯誤的資料
解決方法:
? 把Activity傳給ViewModel ?那絕對不行,根據上示ViewModel 生命周期示意圖,ViewModel 的生命周期是長于Activity的,若把Activity實體傳給ViewModel ,很可能導致Activity無法釋放從而造成記憶體泄露,這是一種非常錯誤的做法!正解當然就是用LiveData啦
基本用法
? MutableLiveData是一種可變的LiveData,主要是3種讀寫資料的方法
-
getValue()用于獲取LiveData中包含的資料 -
setValue()用于在主執行緒中給LiveData設定資料 -
postValue()用于在非主執行緒中給LiveData設定資料一般在Kotlin中會采用語法糖寫法,如下例
-
修改上面寫過的
MainViewModel代碼class MainViewModel(countReserved: Int): ViewModel() { var counter = MutableLiveData<Int>() init {//初始化之前保存的資料 counter.value = https://www.cnblogs.com/Gnocihz/archive/2021/10/17/countReserved } fun plusOne() { val count = counter.value ?:0 counter.value = count + 1 } fun clear() { counter.value = 0 } }? 這里
counter變數修改為一個MutableLiveData物件,泛型指定為Int表示整形資料,init結構體給counter設定資料,這樣之前保存的資料可以在初始化時得以恢復,新增兩個函式分別用于“加1”和“清零”操作,由于LiveData的getvalue方法可能為空,所以這里用一個?:運算子,當獲取到的資料為空時就用0為默認計數, -
修改
MainActivityoverride fun onCreate(savedInstanceState: Bundle?) { ... binding.plusOneBtn.setOnClickListener { // viewModel.counter++ // refreshCounter() viewModel.plusOne() } binding.clearBtn.setOnClickListener { // viewModel.counter = 0 // refreshCounter() viewModel.clear() } viewModel.counter.observe(this, Observer { count -> binding.infoText.text = count.toString() }) } override fun onPause() { super.onPause() sp.edit { putInt("count_reservde", viewModel.counter.value ?: 0) } }? 在兩個按鈕對應的事件中都換成了對應的方法
plusOne()和clear(),在onPause()方法中也相應修改了寫法,中間是最關鍵的修改地方:這里呼叫了viewModel.counter的observe()方法,需要傳入兩個引數,一是LifecycleOwner,在上文提到Activity本身也是LifecycleOwner,所以直接傳入this,第二個引數是Observer介面,當counter中包含的資料發生變化時,就會回呼到這里,我們也能借此更新到界面上,? 修改后的好處:不用擔心
ViewModel開啟執行緒執行耗時邏輯,同時也應注意此時是運行在主執行緒中,若要在子執行緒修改LiveData值則要注意一定要呼叫postValue()方法 -
以上就是
LiveData的基礎用法了,但還不算最規范的,主要問題在于我們把counter這個可變的LiveData暴露給了外部,這樣即使是在ViewModel外面也能為counter設定資料,從而破壞ViewModel的封裝性并存在一定風險,所以比較推薦的做法是,永遠只暴露不可變的LiveData給外部,這樣在非ViewModel中就只能觀察LiveData的資料變化而不能設定LiveData資料:class MainViewModel(countReserved: Int): ViewModel() { // var counter = MutableLiveData<Int>() // init { counter.value = https://www.cnblogs.com/Gnocihz/archive/2021/10/17/countReserved } // fun plusOne() { // val count = counter.value ?:0 // counter.value = count + 1 // } // fun clear() { counter.value = 0} val counter : LiveDataget() = _counter private val _counter = MutableLiveData () init { _counter.value = countReserved } fun plusOne() { val count = _counter.value ?:0 _counter.value = count + 1 } fun clear() { _counter.value = 0 } } ? 相比原來,
counter變數全部變為了_counter變數,并添加了private修飾符對外部不可見,然后重新定義一個counter變數宣告為不可變的LiveData,并在它的get()屬性方法中回傳_counter變數,這樣做當外部呼叫counter變數時,實際上獲得的是_counter的實體,并無法給counter設定資料,從而保證了封裝性,這也是官方的推薦寫法,非常規范
map和switchMap
LiveData的兩種轉換方法:map()和switchMap()
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/320958.html
標籤:其他
