以简单来说,ViewModel是一个抽象类,但它可以以注重生命周期的方式存储和管理界面相关的数据,同时,它可以让数据可在发生屏幕旋转等配置更改后继续留存。下图是ViewModel对应Activity屏幕旋转及重开页面的生命周期图。
ViewModel与LiveData的组合可以将业务代码从Activity/fragment中挪走,是MVVM模式中重要的一环,此节只讨论ViewModel,详细分析ViewModel的实现逻辑。
ViewModel的依赖支持
//添加ViewModel依赖
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.2.0'
下面开始最简单的例子来分析ViewModel的逻辑。
1,实现一个最简单的ViewModel类
public class MyViewModel extends ViewModel {
private String TAG = "MyViewModel";
public int viewModel_Int = 0;
@Override
protected void onCleared() {
super.onCleared();
LogUtil.e(TAG, "oncleared");
}
}
2,页面做简单处理
public class MainActivity extends BaseActivity {
private String TAG = "ViewModel_Main";
private TextView tv_local;
private TextView tv_viewmodel;
MyViewModel viewModel;
int local_Int = 0;
@Override
protected int getLayoutId() {
return R.layout.activity_main2;
}
@Override
protected void initView() {
tv_local = this.findViewById(R.id.tv_local);
tv_viewmodel = this.findViewById(R.id.tv_viewmodel);
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
LogUtil.e(TAG, "activity=" + this);
LogUtil.e(TAG, "viewmodelstore=" + getViewModelStore());
LogUtil.e(TAG, "viewmodel=" + viewModel.toString());
setText();
}
@Override
protected void initData() {
}
public void changeClick(View view) {
viewModel.viewModel_Int++;
this.local_Int++;
setText();
}
public void setText() {
tv_local.setText(local_Int+"");
tv_viewmodel.setText(viewModel.viewModel_Int+"");
LogUtil.e(TAG, "local-Int=" + local_Int + " viewmodel-Int=" + viewModel.viewModel_Int);
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtil.e(TAG, "onDestroy");
}
}
3,最终有了如下日志
进入APP
ViewModel_Main: activity=com.sun.livedata.MainActivity@69b8bab
ViewModel_Main: viewmodelstore=androidx.lifecycle.ViewModelStore@60ac208
ViewModel_Main: viewmodel=com.sun.livedata.MyViewModel@3e33da1
ViewModel_Main: local-Int=0 viewmodel-Int=0
手动更改数据
ViewModel_Main: local-Int=1 viewmodel-Int=1
ViewModel_Main: local-Int=2 viewmodel-Int=2
ViewModel_Main: local-Int=3 viewmodel-Int=3
手机屏幕旋转
ViewModel_Main: onDestroy //activity被销毁
ViewModel_Main: activity=com.sun.livedata.MainActivity@8b1d7b //activity和上一个已经不是同一个了
ViewModel_Main: viewmodelstore=androidx.lifecycle.ViewModelStore@60ac208 //viewmodelstore是同一个
ViewModel_Main: viewmodel=com.sun.livedata.MyViewModel@3e33da1 //viewmodel是同一个
ViewModel_Main: local-Int=0 viewmodel-Int=3
ViewModel_Main: local-Int=1 viewmodel-Int=4
ViewModel_Main: local-Int=2 viewmodel-Int=5
退出APP
MyViewModel: oncleared
ViewModel_Main: onDestroy
小结:
屏幕旋转后,activity销毁并重新打开,页面上的变量重新开始计数,viewmodel上的变量还是继续计数,并且,销毁和新开的activity上的viewmodel和viewmodelstore是同一个实例,并没有改变。
下面做具体分析
1,viewmodel的获取及新建
1,分析如何获取ViewModle
MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
2,分析new ViewModelProvider(this)
androidx.lifecycle.ViewModelProvider
public ViewModelProvider(ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
ComponentActivity中实现了ViewModelStoreOwner,通过getViewModelStore()给我们返回了一个ViewModelStore的实例,具体怎么返回,后面分析
ComponentActivity没有实现Factory相关接口,所以第二个是NewInstanceFactory.getInstance(),返回的是NewInstanceFactory
3,分析ViewModelProvidel.get(MyViewModel.class)
public <T extends ViewModel> T get( String key, Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);//从ViewModelstore中的map集合中获取viewmodel,第一遍肯定是为null
//第二步知道 mFactory的类型为NewInstanceFactory
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);//走一步,factory中创建
}
mViewModelStore.put(key, viewModel);//新建好的viewmodel放到hashmap中
return (T) viewModel;
}
4,创建viewmodel
public static class NewInstanceFactory implements Factory {
......
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
try {
return modelClass.newInstance();//通过反射生成了viewmodel
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
}
下面分析在屏幕旋转页面重开ViewModelStore是如何保持不改变的
1,Android横竖屏切换时会触发onSaveInstanceState,可用来保持可序列化数据,而在页面重开时onRestoreInstanceState中还原数据。另外的,Android还有一组方法随着屏幕切换会调用,那就是onRetainNonConfigurationInstance()和getLastNonConfigurationInstance()。
为什么这2个方法能够实现在activity销毁重开的情况能够数据保持?
答案是在Activity基类中有一个私有静态太监类,有个Object的变量保存数据,,在ActivithThread实现了NonConfigurationInstances随着activity重启启动的传递。
android.app.activity
public class Activity{
static final class NonConfigurationInstances {
Object activity;
HashMap<String, Object> children;
FragmentManagerNonConfig fragments;
ArrayMap<String, LoaderManager> loaders;
VoiceInteractor voiceInteractor;
}
}
2,继续看ViewModelStore切换app时保存及获取的相关源码
androidx.activity.ComponentActivity
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {//屏幕切换时自动调用
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;//当前的viewmodel保存
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;//如果当前的viewmodel为null,则切换前的viewmodel
}
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;//返回要保存的对象到Activity.NonConfigurationInstances中
}
@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.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();//获取NonConfigurationInstances
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;//完美得到viewmodelstore
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();//如果没有就新建
}
}
return mViewModelStore;
}
通过onRetainNonConfigurationInstance()和getLastNonConfigurationInstance()这2个方法就实现了viewmodelstore的保存,为了防止开发者重写onRetainNonConfigurationInstance()造成代码干扰,google特意将此方法设为final了。
但还有个问题,viewmodel的生命周期伴随着activity的开启到销毁到重新开启再一直到app死的很彻底而viewmodel消亡,这一部分是怎么实现的?
androidx.activity.ComponentActivity
public ComponentActivity() {
......
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {//收到app销毁的感知通知了
if (!isChangingConfigurations()) {//不是屏幕配置切换的话那就是activity彻底死了,执行viewmodel的clear操作
getViewModelStore().clear();
}
}
}
});
.......
}
androidx.activity.ViewModelStore
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
public final void clear() {//遍历viewmodel,执行clear操作
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
自己写的viewmodel实现类,onCleared中进行相关资源释放/各类变量置空
public class MyViewModel extends ViewModel {
@Override
protected void onCleared() {
super.onCleared();
LogUtil.e(TAG, "oncleared");
}
}
至此,viewmodel的简单使用及源码分析已经全面完成,总体来说还是收货颇多。
总结一下:
ViewModelStore保存并管理着ViewModel,ViewModelStore通过屏幕切换会依次调用的onRetainNonConfigurationInstance()和getLastNonConfigurationInstance()将ViewModelStore进行了保存(依靠静态类,没有序列化和反序列化过程),这样在新开的Activity中ViewModelStore和ViewModel还是原来的,实现了屏幕旋转等配置更改后ViewModel继续留存,同时,ComponentActivity增加观察者感知ondestory方法,在非屏幕旋转情况下,收到销毁方法时进行ViewModel的销毁。
Android-Jetpack代码位置:github