ViewModel 是什麼
ViewModel 類旨在以注重生命週期的方式 存儲和管理 界面相關的數據。ViewModel 類讓數據可在發生屏幕旋轉等配置更改後繼續留存。
爲什麼ViewModel
類中的數據可在發生屏幕旋轉等配置更改後繼續留存?
因爲ViewModel
的生命週期長於組件(Activity/Fragment)
的生命週期
下圖是左側給出了Activity
經歷屏幕旋轉而後結束的過程,所處的各種生命週期狀態。而右側是ViewModel
的生命週期。
從中可以看出ViewModel
的生命週期是長於組件(Activity)
的生命週期。
屏幕旋轉後數據得以保存僅是ViewModel
的其中一個優勢,生命週期比組件長的優勢還可以:
- 減少資源浪費:避免因配置更改而重新創建對象
- 避免內存泄露:界面控制器經常需要做異步調用,而異步調用需要在一段時間後才返回結果。如果界面關閉之後數據在還沒返回,且其中的一些引用還存在,那麼內存泄露就不可避免了。通常我們會在頁面關閉的時候手動清除引用。在生命週期中我們可以看到
ViewModel
還有一個方法onCleared()
,我們可在該方法手動清除引用。
谷歌在生命感知組件的最佳做法中,推薦我們使用ViewModel+LiveData
的組合。
既然如此,那我們先來了解何爲LiveData
。
LiveData 是什麼
LiveData
是一種 可觀察的數據存儲器類。與常規的可觀察類不同,LiveData
具有生命週期感知能力,所以它能在不同生命週期處理不同的操作。
由於LiveData
是可觀察的數據存儲類且有生命週期感知能力,因此它有具備如下優勢:
-
確保界面符合數據狀態:LiveData 遵循觀察者模式。當生命週期改變數據也會刷新
-
不會發生內存泄露:觀察者會綁定到 Lifecycle 對象,並在其關聯的生命週期遭到銷燬後進行自我清理。
-
不會因 Activity 停止而導致崩潰:如果觀察者的生命週期處於非活躍狀態,則它不會接收任何 LiveData 事件。
-
不再需要手動處理生命週期:LiveData 將自動管理所有這些操作
-
數據始終保持最新狀態:如果生命週期變爲非活躍狀態,它會在再次變爲活躍狀態時接收最新的數據。
-
適當的配置更改:如果由於配置更改(如設備旋轉)而重新創建了 Activity 或 Fragment,它會立即接收最新的可用數據。
-
共享數據:數據存儲在
ViewModel
中,需要相應資源的任何(Activity/Fragment)等
觀察者只需觀察LiveData
對象
ViewModel+LiveData 使用步驟
示例:
(1)實現倒計時功能
(2)ViewModel傳參獲取數據庫的數據展示
Module -> build.gradle
的引入
版本依賴查看:https://developer.android.google.cn/jetpack/androidx/releases/lifecycle#declaring_dependencies
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// 還有一些可選項,可查看上面的版本依賴鏈接
- 創建
Viewmodel
(推薦創建BaseViewModel,之後再創建具體的ViewModel
繼承BaseViewModel
)
/**
* ViewModel 基類
*/
open class BaseViewModel : ViewModel() {
override fun onCleared() {
super.onCleared()
Log.e("CommonViewModel","onCleared")
}
}
(示例1:倒計時功能)
class MineViewModel : BaseViewModel() {
private val periodTime = 1000L
private val mElapsedRealTime = MutableLiveData<Long>()
private var mInitialTime: Long = 0
// Create a LiveData with a Long
val countDownTime: MutableLiveData<Long> by lazy {
mElapsedRealTime
}
/**
* 開啓倒計時
*/
fun getTime(){
mInitialTime = SystemClock.elapsedRealtime()
val timer = Timer()
val timeTask = object : TimerTask() {
override fun run() {
val newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000
if (newValue < 10){
mElapsedRealTime.postValue(newValue)
}else{
timer.cancel()
}
}
}
timer.scheduleAtFixedRate(timeTask,periodTime,periodTime)
}
}
(示例2:ViewModel傳參獲取數據庫的數據展示)
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()
val allStudents = LivePagedListBuilder(dao.getAllStudent(), PagedList.Config.Builder()
.setPageSize(PAGE_SIZE) //配置分頁加載的數量
.setEnablePlaceholders(ENABLE_PLACEHOLDERS) //配置是否啓動PlaceHolders
.setInitialLoadSizeHint(PAGE_SIZE) //初始化加載的數量
.build()).build()
}
- 創建
Activity
(推薦Fragment
)中使用
(示例1:倒計時功能)
class MineFragment : BaseFragment(),MineContract.View {
private lateinit var viewModel: MineViewModel
override fun initData() {
// 創建實例
viewModel = ViewModelProviders.of(this).get(MineViewModel::class.java)
// 觀察數據
viewModel.countDownTime.observe(this, Observer<Long> { aLong ->
//Update UI
tv_name.text = "time = " + aLong!!
})
}
override fun initView() {
// 訂閱事件
lifecycle.addObserver(minePresenter)
// 獲取數據
btn_get_data.setOnClickListener {
viewModel.getTime()
}
}
// 省略部分代碼,具體可看下方源碼鏈接
}
(示例2:ViewModel傳參獲取數據庫的數據展示)
class HomeJetpackFragment : BaseFragment() {
private val TAG = HomeJetpackFragment::class.java.simpleName
private val viewModel by lazy(LazyThreadSafetyMode.NONE) {
ViewModelProviders.of(this, object : ViewModelProvider.Factory {
// 傳遞 context
override fun <T : ViewModel?> create(modelClass: Class<T>): T = HomeViewModel(
BaseApplication.context as Application
) as T
}).get(HomeViewModel::class.java)
}
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)
})
}
// 省略部分代碼,具體可看下方源碼鏈接
}
Tip:
initData
和 initView
執行順序如下:
詳細使用代碼請參見:YGragon/FrameDemo
總結
使用ViewModel+LiveData
的模式,可以很好的在組件的不同生命週期處理數據。這是由於LiveData
是生命週期感知型類所帶來的特性。ViewModel
中專注於處理數據,和UI
組件徹底解耦。
而且在使用AndroidStudio
創建Fragment
的時候提供了創建ViewModel
選項,極大的方便了開發。
參考
上車
佛系原創號主