一、基本概念
1、LiveData
1.1 LiveData 簡介
LiveData 是一種可觀察的資料存儲器類。與正常的可觀察類不同,LiveData 具有生命周期感覺能力,意指它遵循其他應用元件(如 Activity、Fragment 或 Service)的生命周期。
2、LiveData 優勢
- 確定界面符合資料狀态LiveData 遵循觀察者模式。
當底層資料發生變化時,LiveData 會通知 Observer 對象。可以整合代碼以在這些 Observer 對象來更新界面。這樣一來,無需在每次應用資料發生變化時更新界面,因為觀察者會替您完成更新。
- 不會發生記憶體洩漏
觀察者會綁定到 Lifecycle 對象,并在其關聯的生命周期遭到銷毀後進行自我清理。
- 不會因 Activity 停止而導緻崩潰
如果觀察者的生命周期處于非活躍狀态(如傳回棧中的 Activity),它不會接收任何 LiveData 事件。
- 不再需要手動處理生命周期
界面元件隻是觀察相關資料,不會停止或恢複觀察。LiveData 将自動管理所有這些操作,因為它在觀察時可以感覺相關的生命周期狀态變化。
- 資料始終保持最新狀态
如果生命周期變為非活躍狀态,它會在再次變為活躍狀态時接收最新的資料。例如,曾經在背景的 Activity 會在傳回前台後立即接收最新的資料。
- 适當的配置更改
如果由于配置更改(如裝置旋轉)而重新建立了 Activity 或 Fragment,它會立即接收最新的可用資料。
- 共享資源
可以使用單例模式擴充 LiveData 對象以封裝系統服務,以便在應用中共享它們。LiveData 對象連接配接到系統服務一次,然後需要相應資源的任何觀察者隻需觀察 LiveData 對象。
2、ViewModel
2.1 ViewModel簡介
Android Jetpack的一部分,用于為界面準備資料
2.1 ViewModel 優勢
- 讓應用以注重生命周期的方式存儲和管理界面相關的資料
- 讓資料可在發生螢幕旋轉等配置更改後繼續留存
- 從界面控制器邏輯中分離出視圖資料所有權,使資料和試圖操作更容易且更高效
二、基礎使用
1、添加依賴
- 項目build.gradle中
allprojects {
repositories {
google()
jcenter()
}
}
- app子產品下build.gradle中
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
2、代碼實戰
2.1 建立ViewModel類
class NameTestViewModel:ViewModel() {
val userName:MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
}
2.2 Activity中使用
- 擷取ViewModel執行個體
private val mViewModel: NameTestViewModel by viewModels()
- 建立觀察者并為viewModel添加觀察者
// 建立observer
val contentObserver = Observer<String> { content ->
// 資料變更更新ui
tvLiveDataContent.text = content
}
// 添加觀察者
mViewModel.userName.observe(this, contentObserver)
- LiveData 設定、更新、轉換資料
// 設定資料
btnSetData.setOnClickListener {
// mViewModel.userName.value = "張三"
mViewModel.userName.postValue("張三")
}
// 更新資料
btnChangeData.setOnClickListener {
mViewModel.userName.value = "李四"
}
// 轉換資料
mapDataFunction()
}
- LiveData 轉換資料Map、SwitchMap
- Map 無需傳回值
private fun mapDataFunction() {
val mapString=MutableLiveData<String>()
mapString.postValue("資料")
// 轉換資料
btnMapData.setOnClickListener {
val mapObserver = Transformations.map(mapString) { userName ->
"我是轉換後的 $userName"
}
mapObserver.observe(this){
tvLiveDataContent.text = it // text:我是轉換後的資料
}
}
}
- switchMap 經過switchMap轉換後必須有傳回值,傳回值為liveData類型資料
private fun switchMapData() {
val switchMapString=MutableLiveData<String>()
switchMapString.value="switchMapData"
val switchMapStringTwo=MutableLiveData<String>()
switchMapStringTwo.postValue("switchMapDataTwo")
btnSwitchMapData.setOnClickListener {
// 經過switchMap轉換後必須有傳回值,傳回值為liveData類型資料
val switchMapObserver=Transformations.switchMap(switchMapString){
// 修改資料
switchMapStringTwo.postValue(it)
// 通過switchMap将switchMapStringTwo作為傳回值
switchMapStringTwo
}
switchMapObserver.observe(this){
tvLiveDataContent.text = it // text:switchMapData
}
}
}
- LiveData mediatorLiveData 合并多個資料源
private fun mediatorLiveData() {
val sourceString = MutableLiveData<String>()
sourceString.value = "sourceLiveDataOne"
val sourceStringTwo = MutableLiveData<String>()
sourceStringTwo.postValue("sourceLiveDataTwo")
val mediatorLiveData = MediatorLiveData<String>()
// 添加資料源
mediatorLiveData.addSource(sourceString) {
mediatorLiveData.value = it
}
mediatorLiveData.addSource(sourceStringTwo) {
mediatorLiveData.value = it
}
// 更新資料
btnMediatorLiveData.setOnClickListener {
mediatorLiveData.observe(this) {
tvLiveDataContent.text = it // text: sourceLiveDataTwo
}
}
}
使用mediatorLiveData的addSource添加多個資料源,按照執行先後順序執行,後執行的會覆寫先執行的資料
3、ViewModel 補充
3.1 viewModel生命周期
- ViewModel 對象存在的時間範圍是擷取 ViewModel 時傳遞給 ViewModelProvider 的 Lifecycle。
- ViewModel 将一直留在記憶體中,直到限定其存在時間範圍的 Lifecycle 永久消失
- 對于 activity,是在 activity 完成時;而對于 fragment,是在 fragment 分離時
- activity中ViewModel生命周期
LiveData與ViewModel基礎使用篇
3.2 Fragment間使用ViewModel共享資料
- 建立兩個Fragment
- 擷取viewModel執行個體
- 設定監聽
- 更新ui
class FirstFragment : Fragment() {
// 擷取viewModel
private val model:DataTestViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return layoutInflater.inflate(R.layout.layout_fragment_first, container, false) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 設定觀察者
model.userName.observe(viewLifecycleOwner, Observer {
// 更新ui
tvContent.text=it
})
}
}
class SecondFragment:Fragment() {
private val model: DataTestViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return layoutInflater.inflate(R.layout.layout_fragment_first, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.userName.observe(viewLifecycleOwner, Observer {
tvContent.text=it
})
}
}
- 将fragment添加到activity中
private fun addFragment() {
val firstFragment=FirstFragment()
val secondFragment=SecondFragment()
supportFragmentManager.beginTransaction()
.add(R.id.flOne,firstFragment)
.add(R.id.flTwo,secondFragment)
.commitAllowingStateLoss()
}
因為兩個fragment共享ViewModel,同時實作了監聽,所有當viewmodel中資料發生變化,兩個fragment都能收到響應,實作ui更新
3.3 協程與ViewModel一起使用
- 添加KTX依賴
// ktx
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
- 結合ViewModelScope使用
// ViewModelScope
init {
viewModelScope.launch {
userName.value="defaultValue"
}
}
- Fragment中結合LifecycleScope使用
// lifecycleScope 執行耗時操作
btnLifecycleScope.setOnClickListener {
viewLifecycleOwner.lifecycleScope.launch {
delay(100)
// 修改資料
model.userName.postValue("lifecycleScope")
}
}
-
可重新開機生命周期感覺型協程
在Fragment的onViewCreated()中監聽生命周期變化并列印日志
// 可重新開機生命周期感覺型攜程
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.whenCreated {
Log.d(TAG,"lifeCycle onCreated")
}
viewLifecycleOwner.whenStarted {
Log.d(TAG,"lifeCycle onStarted")
}
viewLifecycleOwner.whenResumed {
Log.d(TAG,"lifeCycle onResume")
}
}
- 日志列印
D/FirstFragment: lifeCycle onCreated
D/FirstFragment: lifeCycle onStarted
D/FirstFragment: lifeCycle onResume
可見,我們通過viewLifecycleOwner觀察到了fragment生命周期的變化。
-
挂起生命周期感覺型協程
即使 CoroutineScope 提供了适當的方法來自動取消長時間運作的操作,在某些情況下,可能需要暫停執行代碼塊(除非 Lifecycle 處于特定狀态),手動在finally釋放資源
private fun hangUpScope() {
viewLifecycleOwner.lifecycleScope.launch {
try {
whenStarted {
var success = false
// 模拟攜程執行1s耗時操作
withContext(Dispatchers.IO) {
delay(1000)
success = true
}
if (success) {
model.userName.postValue("success")
}
}
}finally {
// finally 中釋放資源
if (lifecycle.currentState>=Lifecycle.State.STARTED){
releaseSource()
}
}
}
}
// 釋放資源
private fun releaseSource() {
Log.d(TAG, "release source")
}
- 日志列印
D/FirstFragment: release source
- 結合liveData與協程并通過emit将資料發送出去
- ViewModel中
// 延遲1s 協程中模拟網絡請求并通過emit将資料發送出去
val liveDataItem= liveData {
delay(1000)
val data="John"
emit(data)
}
- Fragment中監聽資料變化
model.liveDataItem.observe(viewLifecycleOwner, Observer {
Log.d(TAG,it)
})
- 日志列印
D/FirstFragment: John
- 使用 liveData 建構器函數調用 suspend 函數,并将結果作為 LiveData 對象傳送
- 使用liveData在協程中處理資料,并通過emit将結果發出
- viewModel中代碼
// 延遲1s 協程中模拟網絡請求并通過emit将資料發送出去
val liveDataItem= liveData {
delay(1000)
val data="John"
emit(data)
}
val resultBean= liveData {
emit(ResultBean(ResultBean.STATUS_LOADING))
try {
delay(2000)
emit(ResultBean(ResultBean.STATUS_SUCCESS))
}catch (e:Exception){
emit(ResultBean(ResultBean.STATUS_FAIL))
}
}
- Fragment中代碼
private fun liveDataWithRoutineCombination() {
model.liveDataItem.observe(viewLifecycleOwner, Observer {
Log.d(TAG,it)
})
model.resultBean.observe(viewLifecycleOwner, Observer {
when(it.status){
ResultBean.STATUS_LOADING->{
Log.d(TAG, "resultBean loading")
}
ResultBean.STATUS_SUCCESS->{
Log.d(TAG, "resultBean success")
}
ResultBean.STATUS_FAIL->{
Log.d(TAG, "resultBean fail")
}
}
})
}
- 日志列印
D/FirstFragment: resultBean loading
D/FirstFragment: John
D/FirstFragment: resultBean success
-
ViewModel 中使用SavedStateHandle儲存狀态
在onSaveInstance中儲存資料,并在下次重新開機時,通過savedStateHandle讀取資料,類似我們在onSaveInstance()中儲存資料,在onCreate()中通過讀取bundle恢複資料
-
支援的資料類型
保留在 SavedStateHandle 中的資料将作為 Bundle 與 Activity 或 Fragment 的 savedInstanceState 的其餘部分一起儲存和恢複
LiveData與ViewModel基礎使用篇
如果該類沒有擴充上述清單中的任何一項,則應考慮通過添加 @Parcelize Kotlin 注解或直接實作 Parcelable 來使該類變為 Parcelable 類型或者實作Serializable實作序列化
-
代碼執行個體
ViewModel注意構造方法中添加SavedStateHandle
class DataViewModelTwo(private val savedStateHandle: SavedStateHandle):ViewModel(){
private val TAG="tag"
// 設定query資料
fun setQuery(query: String) {
if (savedStateHandle.contains("query")) {
Log.d(TAG, "contains query==== ${savedStateHandle.get<UserBean>("query")}")
}else{
Log.d(TAG,"儲存資料")
savedStateHandle["query"] = UserBean(userName = query,userAge = 10)
}
}
// 擷取資料
fun getSavedValue(): UserBean? {
return savedStateHandle.get<UserBean>("query")
}
}
- 擷取ViewModel對象
private val model2: DataViewModelTwo by activityViewModels()
- Fragment的onSaveInstance中儲存資料
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
model2.setQuery("啦啦啦啦")
}
- 在Fragment中onResume()方法中模拟擷取資料
override fun onResume() {
super.onResume()
// saveData
saveDataFunction()
}
private fun saveDataFunction() {
Log.d(TAG, "query==== ${model2.getSavedValue()}")
}
- 日志列印
D/tag: query==== null
D/tag: 儲存資料
D/tag: query==== UserBean(userName=啦啦啦啦, userAge=10)
通過上面的例子我們就實作了viewModel儲存資料的效果
-
參考:
https://developer.android.google.cn/topic/libraries/architecture/viewmodel?hl=zh_cn
https://developer.android.google.cn/topic/libraries/architecture/livedata?hl=zh_cn
https://developer.android.google.cn/topic/libraries/architecture/viewmodel-savedstate?hl=zh_cn