ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。
现在,常用的项目架构,在MVP向MVVM转变。相对于MVP中的P(presenter),MVVM中的ViewModel有哪些优势呢。
ViewModel的优势
- 可以持久的保留界面状态(比如,屏幕旋转)
- 提供了对业务逻辑的访问权限()
我们先通过代码来看下ViewModel的使用。然后,看下ViewModel的原理
本文内容:
- 一、ViewModel的使用
- 二、ViewModel的源码分析
- 三、其它的知识点
一、viewModel的使用
之前,学习了LiveData。这里,我们用LiveData+ViewModel组合的方式使用。
首先创建一个ViewModel,用来更新UI的数据
public class MyViewModel extends ViewModel {
//liveData
public MutableLiveData<String> name = new MutableLiveData<>();
/**
* 获取用户名字
*/
public void obtainUserName() {
name.setValue("张三");
}
}
下面创建一个Activity,用2个TextView,1个用ViewModel更新数据;一个用普通方式更新数据
public class ViewModelActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_livedata_test);
//textView用viewModel更新数据
final TextView tvContent = findViewById(R.id.tv_content);
//textView用普通方式更新数据
final TextView tvUnUnUsedViewModelContent = findViewById(R.id.tv_content_unused_viewmodel);
//获取viewModel实例
final MyViewModel myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
//添加LiveData观察者
myViewModel.name.observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
tvContent.setText(s);
}
});
//点击按钮,更新2个TextView的数据
findViewById(R.id.btn_livedata_change).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myViewModel.obtainUserName();
tvUnUnUsedViewModelContent.setText("xxx");
}
});
}
}
最后,就是清单文件,什么也不配置
到这里,我们运行项目后,点击按钮,数据都是正常显示的。
但是,如果,我们旋转屏幕的话,就会发现第一个TextView的数据还存在,但是,第二个TextView的数据,就回到了默认状态
到这里,ViewModel+LiveData的基本使用就介绍完啦
- 创建ViewModel
- Activity获取ViewModel对象
- 添加ViewModel里面的LiveData观察者
当数据改变的时候,UI的状态就可以自动改变了。
但是,ViewModel相对于普通的数据更新来说,它的优势,就是在旋转屏幕的时候,数据也不会丢失。
并且,ViewModel 的作用域将限定为 ViewModelStoreOwner 的 Lifecycle。它会一直保留在内存中,直到其 ViewModelStoreOwner 永久消失。ViewModelStoreOwner的实现类有(Activity、Fragment等)
ViewModel使用起来很简单,那一块看看是如何实现的吧
二、ViewModel源码分析
ViewModel的使用,只是通过实例化出来一个ViewModel,然后,调用ViewModel内部的数据来进行更新数据。
我们就来看看ViewModel是怎么实例化的。实例化代码
final MyViewModel myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
这里,我们分为2步
- 1,new ViewModelProvider获取provider
- 2,通过get(XXViewModel.class)获取ViewModel的实例
先看下new ViewModelProvider()做了什么操作
2.1, new ViewModelProvider(this)
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
...
//实现了ViewModelStoreOwner,HasDefaultViewModelProviderFactory接口
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner
我们在new ViewModelProvider()传入了this(Activity)。
这里看下Activity,它实现了ViewModelStoreOwner和HasDefaultViewModelProviderFactory接口
实例化ViewModelProvider,调用this的构造器,相当于this(activity.getViewModelStore(),activity.getDefaultViewModelProviderFactory())
相当于2个参数都从activity内获取。
先往下看this()2个参数的构造器,最后,看下获取的内容是什么
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
我们发现,ViewModelProvider就是保存数据
- 保存ViewModelStore
- 保存factory
下面,看看从activity获取的factory跟ViewModelStore
先看下获取ViewModelStore的代码
- ComponentActivity#getViewModelStore()
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.");
}
//如果是null的话
if (mViewModelStore == null) {
//查看,最后一次配置(横竖屏)时候,里面是否已经保存了ViewModelStore
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
//如果已经保存了,这里直接恢复
mViewModelStore = nc.viewModelStore;
}
//没有保存,就new一个新的出来
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
通过上面的代码,我们可以发现,如果是横竖屏切换的话。
getLastNonConfigurationInstance()里面会保存之前的viewmodelstore的话,就直接恢复了之前的viewModelStore,这也就是为什么,我们横竖屏切换的话,数据不会丢失的原因了。
- 获取factory的代码
public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
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 (mDefaultFactory == null) {
mDefaultFactory = new SavedStateViewModelFactory(
getApplication(),
this,
getIntent() != null ? getIntent().getExtras() : null);
}
return mDefaultFactory;
}
工厂如果是null的话,直接实例化一个SavedStateViewModelFactory工厂
到这里new ViewModelProvider() 就看完啦
整体的流程:
2.2,ViewModelProvider#get(MyViewModel.class)分析
从传入的class,我们基本上也能猜测,应该是通过反射重建的ViewModel
看下具体代码
@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");
}
//这里传入的是类的全路径 (xx.xx.Test),class
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
看下get重载方法
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//查看store里面是否保存了该key的ViewModel
//如果是横竖屏切换的话,store中是保存有ViewModel的
ViewModel viewModel = mViewModelStore.get(key);
//如果viewModel是该class的。直接返回
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.
}
}
//通过上面,我们知道activity里面默认的Factory是SavedStateViewModelFactory
//SavedStateViewModelFactory实现了KeyedFactory类
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
//创建的viewmodel保存到store中
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
这里面,先查看了viewModeStore是否保存了该ViewModel,如果保存了直接返回,没有的话,调用create()方法创建,并且保存到viewModelStore中。
主要作用:
- 从ViewModelStore中获取ViewModel
- 如果上面没有,通过Factory#create()方法获取ViewModel
- 把ViewModel放到ViewModelStore中
我们看看Factory#create()方法
上面说到了,默认的Factory是SavedStateViewModelFactory,它继承了KeyedFactory.直接看它的create()方法
SavedStateViewModelFactory.java
@Override
public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
Constructor<T> constructor;
//获取ViewModel构造器类型(AndroidViewModel还是ViewModel)
if (isAndroidViewModel) {
constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
} else {
constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
}
// doesn't need SavedStateHandle
if (constructor == null) {
return mFactory.create(modelClass);
}
SavedStateHandleController controller = SavedStateHandleController.create(
mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
try {
//通过反射获取ViewModel
T viewmodel;
if (isAndroidViewModel) {
viewmodel = constructor.newInstance(mApplication, controller.getHandle());
} else {
viewmodel = constructor.newInstance(controller.getHandle());
}
viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
return viewmodel;
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to access " + modelClass, e);
} catch (InstantiationException e) {
throw new RuntimeException("A " + modelClass + " cannot be instantiated.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("An exception happened in constructor of "
+ modelClass, e.getCause());
}
}
Facory获取ViewModel,主要是获取合适的构造器通过反射创建ViewModel对象。
到这里,我们查看完了ViewModel获取的全过程。
获取ViewModel的整体流程图
三、其它的相关知识点
3.1 ViewModelStore类
有木有发现,里面出现最多的就是ViewModelStore类,我们看下这个类
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
通过上面,我们知道,这个类就是封装了一下Map对象。缓存的ViewModel也是缓存到了Map里面。
3.2 viewModel怎么做到在Activity重建的时候,依然能保存数据
在Activity发生屏幕旋转的时候,会执行Activity的onRetainCustomNonConfigurationInstance()方法
我们看下ComponentActivity#onRetainCustomNonConfigurationInstance()方法
@Nullable
public final Object onRetainNonConfigurationInstance() {
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
//如果viewModelStore不是空的话,会获取他最后的配置
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
//把viewModelStore的内容重新恢复
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
这里,可以明显看到,viewModelStore里面的内容在这里被恢复了。
那么,Activity重建的时候,数据又是怎么保存的呢?
在Activity屏幕旋转的时候,整体的流程应该是
- 在Activity销毁之前,执行了ActivityThread#performDestroyActivity()方法
如果是Activity重建的话,会把activity的配置NonConfigurationInstances信息保存到ActivityClientRecord 里面 - 在Activity重建的时候,activity#performLaunchActivity(ActivityClientRecord,Intent)方法
- 执行activity.attach()方法把lastNonConfigurationInstances对象传过去。
这样,就保证了ViewModelStore内容的恢复。(中间省略了很多过程,有兴趣的,自己看下)
ViewModel声明周期。Activity 经历屏幕旋转而后结束时所处的各种生命周期状态。该图还在关联的 activity 生命周期的旁边显示了 ViewModel 的生命周期。此图表说明了 activity 的各种状态。这些基本状态同样适用于 fragment 的生命周期。
官方配图
3.3,ViewModel的数据,会在什么时候被清除呢
在Activity初始化的时候,就通过Lifecycle进行了判断,代码
public ComponentActivity() {
...
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
//当页面销毁时
if (event == Lifecycle.Event.ON_DESTROY) {
//并且不是横竖屏切换等reLaunchActivity的情况
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
}
在Activity初始化的时候,已经为ViewModelStore设置了数据会在页面onDestroy时清理。
官方文档:https://developer.android.google.cn/topic/libraries/architecture/viewmodel
ViewModel类型:SharedViewModel,AndroidViewModel,ViewModel,SavedStateViewModel