一、定义
具有宿主生命周期感知能力的数据存储组件,只能感知宿主被销毁的事件,也就是onDestory,此时可以复写onClear方法来做一些清理和释放的工作
ViewModel保存的数据,在页面因配置变更导致页面销毁重建之后依然也是存在的
配置变更:
横竖屏切换;分辨率调整;语言切换;权限变更;系统字体样式变更等
原理:
ViewModel的实例被保存了下来,页面被重建之后,还是获取的同一个ViewModel的实例,就能实现里面数据的复用
正常关闭页面,ViewModel的数据还是会被清理的
如果是电量不足、内存不足等系统原因导致的页面被回收,页面重建之后存储的数据也能再次被复用。
Activity的生命周期不断变化,被销毁重新创建,而ViewModel的生命周期不会发生变化
二、用法
1.常规用法
存储的数据,仅仅只能当页面因为配置变更导致的销毁在重建时才可复用,复用的是ViewModel的实例化对象的整体
public class MyViewModel extends ViewModel {
private MutableLiveData<String> name;
public LiveData<String> getName() {
if (name == null) {
name = new MutableLiveData<String>();
addName();
}
return name;
}
private void addName() {
name.setValue("Android进阶解密");
}
}
getName方法中创建一个MutableLiveData,并通过MutableLiveData的setValue方法来更新数据
在Activity中使用MyViewModel
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getName().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
Log.d(TAG, "畅销书:"+s);
}
});
}
}
通过ViewModelProviders获得MyViewModel的实例,配合LiveData观察Name的变化
class TestViewModel : ViewModel() {
val liveData = MutableLiveData<List<String>>()
fun loadData(): LiveData<List<String>> {
//为了适配因配置变更而导致的页面重建,重复利用之前的数据,加快页面渲染,不再请求接口
if (liveData.value == null){
val remoteData = queryData()
liveData.postValue(remoteData)
}
return liveData
}
fun queryData():List<String>{
//接口请求
……
return data
}
}
//通过ViewModelProvider来获取TestViewModel的实例
val viewModel = ViewModelProvider(this).get(TestViewModel::class.java)
viewModel.loadData().observe(this,Observer{
//数据回调
})
2.进阶用法
存储的数据,无论是配置变更,还是因内存、电量不足等系统原因导致的页面被回收再重建都可以复用,即便ViewModel不是同一个实例,它存储的数据也能做到复用,需要用到savedState组件
api 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0'
//SavedStateHandle 包装了一个hashMap
class TestViewModel(val savedState: SavedStateHandle) : ViewModel() {
private val KEY_TEST_DATA = "key_test_data"
val liveData = MutableLiveData<List<String>>()
fun loadData(): LiveData<List<String>> {
//内存复用
if (liveData.value == null) {
val remoteData = savedState.get<List<String>>(KEY_TEST_DATA)
liveData.postValue(remoteData)
return liveData
}
//请求接口
val remoteData = queryData()
savedState.set(KEY_TEST_DATA, remoteData)
liveData.postValue(remoteData)
return liveData
}
fun queryData():List<String>{
//接口请求
……
return data
}
}
3.跨页面的数据共享
不仅能实现单Activity多Fragment的数据共享,还可以实现跨Activity的数据共享
三、配置变更ViewModel复用实现原理
ViewModel是如何做到在宿主销毁了,还能继续存在,以至于页面恢复重建后还能继续复用
因为获取的是同一个ViewModel实例对象
val viewModel = ViewModelProvider(this).get(TestViewModel::class.java)
ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory){
this(owner.getViewModelStore(), factory);
}
ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
在ViewModelProvider需要的是ViewModelStoreOwner,而Activity、Fragment都是实现了ViewModelStoreOwner这个接口的,所以可以直接传递进去
ViewModelStore就是用来存储ViewModel实例的,本质上是一个HashMap
Factory就是用来创建ViewModel实例的
调用get方法,将传递进来的className在拼接一个DEFAULT_KEY字符串,这个key就是ViewModel在ViewModelStore存储的key
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
首先根据key来获取ViewModel实例,紧接着看这个实例是不是Class类型,如果是直接返回了,如果不是就根据Factory的类型来创建ViewModel实例,创建完成之后就把它存储到mViewModelStore这个缓存对象中
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
ViewModel的实例就是通过mViewModelStore来获取的,既然想做到ViewModel实例的复用,那就需要mViewModelStore做到复用,mViewModelStore的实例实在ComponentActivity的getViewModelStore()获取的
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
ensureViewModelStore();
return mViewModelStore;
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
NonConfigurationInstances 本质是个包装类,用来包装一些在配置变更后还想继续留存的数据,比如fragment,页面被旋转之后,Activity会被重建,而Fragment还是原来的那个,这个实现原理就是通过NonConfigurationInstances来实现的,ViewModel的复用也是这样
mViewModelStore的存储是在onRetainNonConfigurationInstance中,如果ViewModelStore为空就直接retrun,不为空就构建了NonConfigurationInstances对象,并将ViewModelStore给提取了出来,从而完成实例对象的复用
@Override
@Nullable
@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance() {
// Maintain backward compatibility.
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
custom对象是在onRetainCustomNonConfigurationInstance()中得到的
作用:
如果想当配置变更导致被销毁了在Activity中保存一些数据,在Activity在重建时继续复用,此时可以复写onRetainCustomNonConfigurationInstance方法
通过调用getLastNonConfigurationInstance()方法来获取保存的方法
但是这种方式已经被废弃了,推荐使用ViewModel来完成
四、ViewModel的SavedState数据复用原理
SavedState存储的数据即便ViewModel这个实例被销毁了,当Activity被重建的时候也能被复用
涉及到的类:
- SavedStateHandle的数据存储与恢复,即便ViewModel不是同一个实例,它存储的数据也能做到复用
- SavedStateRegistryOwner:接口,申明宿主的意思,Activity、Fragment都实现了这个接口
- SavedStateRegistryController:用于创建SavedStateRegistry,与Activity、Fragment简历联系,剥离SavedStateRegistry与Activity的耦合关系
- SavedStateRegistry:在实现SavedStateRegistryOwner的同时,要返回SavedStateRegistry对象,作用是:数据存储、恢复中心,每一个ViewModel中的数据都会被收集到这里。一个总Bundle(RestoreState),key-value存储着每个ViewModel对应子Bundle
- SavedStateHandle:每一个ViewModel对应一个SavedStateHandle,用于存储和恢复数据
SavedState数据存储流程:
逐一调用每个SavedStateHandle保存自己的数据。汇总成一个总的Bundle,在存储到Activity的SavedState对象中
- 在内存不足、电量不足等系统元原因导致的Activity页面即将被销毁时,会调用Activity的onSaveInstanceState(Bundle savedState)
- 紧接着利用SavedStateRegistryController调用performnSave(Bundle savedState)直接转发