ViewModel简介
ViewModel是以感知生命周期的形式来存储和管理视图相关数据
主要特点: -当Activity被销毁时我们通常使用onSaveInstanceState()来恢复数据,这种方法仅限于恢复少量支持序列化和反序列化的数据,不使用与列表,大视图等场景,而ViewModel支持这种大量数据的恢复并且不需要数据序列化
- Android中Activity/Fragment用于显示UI,如果再让其负责显示数据库和网络请求等操作,会导致视图层逻辑臃肿,不方便维护和排查问题,而ViewModel可以胜任数据层和Ui进行分离解耦
- 数据层由于和UI层进行关联,一旦有些异步任务在视图被销毁时未及时清除则会引起内存泄露和空指针,ViewModel具有感知宿主生命周期的能力所以不会发生内存泄露等问题
ViewModel 可以存放任意 Object 数据,因配置变更导致的页面被回收才有效。此时存在ActivityThread#ActivityClientRecord 中,如果是因内存不足或者因为电量不足导致页面被回收,这种情况不是配置变更,所以 ViewModel 就无法实现复用了
ViewModel使用
之前的LifeCircle和LiveData组件在引入appcompat的时候会自动关联,所以不需要单独引入 ViewModel需要引入
implementation "android.arch.lifecycle:extensions:1.1.1"
复制代码
自定义ViewModel
class SimpleViewModel() : ViewModel() {
private val liveData: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
fun getMyLiveData(): MutableLiveData<String> {
return liveData
}
fun getData(){
liveData.postValue("我TM心态崩了!!!")
}
fun getData2(){
liveData.postValue("加油吧,奥利给!!!")
}
}
复制代码
在Activity的onCreate()中调用
private fun testViewModel() {
//获取ViewModel对象
val mySimpleViewModel = ViewModelProviders.of(this).get(SimpleViewModel::class.java)
//获取LiveData对象并观察其数据变化
mySimpleViewModel.getMyLiveData().observe(this) {
tv_text.text = it
Log.i("ViewModelTag",it)
}
//模拟获取数据
mySimpleViewModel.getData()
}
复制代码
结果
此时旋转屏幕使Activity重新被创建,结果
纳尼?为啥有两次的呢? Activity发生旋转后当前Activity被销毁重建,重新执行onCreate()上述代码被重新执行一遍
- 第一次打印出的结果是ViewModel恢复的数据
- 第二次是重新调用了getData()方法执行的结果
咱们做一个简单的验证
private fun testViewModel() {
val mySimpleViewModel = ViewModelProviders.of(this).get(SimpleViewModel::class.java)
mySimpleViewModel.getMyLiveData().observe(this) {
tv_text.text = it
Log.i("ViewModelTag",it)
}
//模拟获取数据
mySimpleViewModel.getData()
//延迟一秒再发送一个数据
Handler().postDelayed(Runnable {
mySimpleViewModel.getData2()
},1000)
}
复制代码
结果
此时旋转屏幕 按照我们的推论应该是先恢复"加油吧奥利给",在执行重新发送的数据
ViewModel生命周期
在旋转设备屏幕时,Activity会被销毁重新创建,而ViewModel却不会这样,它的生命周期如下所示。
ViewModel原理
ViewModelProviders.of()->获取ViewModelProvider对象
# ViewModelProviders类
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return of(activity, null);
}
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
复制代码
ViewModelProvider.get()->获取ViewModel对象并存放在ViewModelStore中维护的map中
# ViewModelProvider
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//通过ViewModelStore维护的map先获取
ViewModel viewModel = mViewModelStore.get(key);
//Factory初始化ViewModel
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
//添加到维护的map中
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
复制代码
Activity配置更改存储并恢复ViewModel
# ComponentActivity 类
public class ComponentActivity implement ViewModelStoreOwner{
//实现获取ViewModelStore方法
public ViewModelStore getViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
//从因配置更改存储的ViewModelStore
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
}
//页面配置变化时存储已经创建的ViewModelStore,其中有维护ViewModel的Map
public final Object onRetainNonConfigurationInstance() {
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
复制代码
SavedState解决因内存和电量等问题导致的被回收的问题
以下是专栏里说的
SavedState定义
在页面即将被销毁的时候,每个使用 SavedState 的 ViewModel 都会创建一个 Bundle 来存储自己的这份数据,最后这些 Bundle 会被汇总到一个 Bundle 中,然后再保存到 onSaveInstanceState(Bundle outState) 的outState 中。
当页面恢复的时候,会从 onCreate(Bundle savedInstanceState) 中的 savedInstanceState 中取出原来存放的总的那个 Bundle,然后再取出一个个的属于 ViewModel 的子 Bundle,于是我们就能愉快的在 ViewModel 中复用之前存储的数据了 。
其实就是利用 Bundle 可以保存另一个 Bundle 这么一个特点,分层分区保存数据,让数据之间相互分离,进而方便整存整取。
使用
添加依赖
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
复制代码
//我们只需要在构造函数上添加SavedStateHandle参数即可。其他不变
class HiViewModel(val savedState: SavedStateHandle) : ViewModel() {
private val KEY_HOME_PAGE_DATA="key_home_page_data"
private val initData = MutableLiveData<List<GoodsModel>>()
fun loadInitData():LiveData<List<GoodsModel>> {
if(initData.value==null){
//1.from memory .
//加载数据的时候,先从savedState中尝试读取,如果有直接内存复用
val memoryData =savedState.get<List<GoodsModel>>(KEY_HOME_PAGE_DATA)
if(memoryData!=null){
initData.postValue(memoryData)
}else{
//2.from remote
val remoteData = fetchDataFromRemote()
//然后存储在savedState以备不时之需,数据模型需要使用parceable接口
//这种写法,即便页面因配置变更,内存不足被回收
//页面重建后,我们都能第一时间复用之前的数据,从而快速渲染页面
//这种能力,对于一级页面尤其首页是至为重要的
savedState.set(KEY_HOME_PAGE_DATA,remoteData)
initData.postValue(remoteData)
}
}
return initData
}
}
复制代码
原理不再介绍
总结
- ViewModel 搭配 SavedState 组件,可以实现非正常关闭情况的数据存储与复用,这对于一级页面,尤其是首页模块及其重要。
- SavedState 本质是利用了 onSaveIntanceState 的时机。每个 ViewModel 的数据单独存储在一个 Bundle中,再合并成一个整体。再存放到 outBundle 中。所以它也不能存超过 1M 的数据。