看完本文将收获:
一.ViewModel介绍以及优势
二. ViewModel的使用
三. ViewModel的实现原理
四. 关于ViewModel相关的问题Q-And-A
一.ViewModel的介绍以及优势
- 简介:
ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。 - 优势:
ViewModel 的替代方案是保存要在界面中显示的数据的普通类。在 activity 或 Navigation 目的地之间导航时,这可能会造成问题。此时,如果您不利用保存实例状态机制存储相应数据,系统便会销毁相应数据。ViewModel 提供了一个便捷的数据持久性 API,可以解决此问题。
ViewModel 类的主要优势实际上有两个方面:
-
它允许您持久保留界面状态。(ViewModel的生命周期)
-
它可以提供对业务逻辑的访问权限。
二.ViewModel的使用
详细可以看Google官方发表的使用方式(结合其他jetpack组件)
简单使用:
实现一个点击按钮让数字 + 1的功能
- 继承ViewModel
public class MyViewModel extends ViewModel {
public int number;
}
- 获取实例ViewModelProvider.get(xxx.class)和使用
public class MainActivity extends AppCompatActivity {
private TextView textView;
private MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
textView.setText(String.valueOf(viewModel.number));
Log.d("jian", "onCreate: " + this + " " + viewModel);
}
public void plusNumber(View view) {
textView.setText(String.valueOf(++viewModel.number));
}
}
当旋转屏幕时,数据依旧不会丢失,也就是“新创建的activity实例状态跟销毁前状态一样”
从打印的信息也可知,虽然Activity对象被重建了(跟屏幕旋转前实例不一样),但ViewModel依然没有被销毁重建(跟屏幕旋转前实例一样)
三. ViewModel的实现原理
学习ViewModel的实现原理前需要先掌握 Activity的保存实例状态机制,不懂的可以先阅读一下详解Activity和Fragment生命周期以及保存实例状态机制
思考和探究为什么ViewModel旋转时数据不会丢失
- 刚开始使用的时候看到屏幕旋转数据没丢失,viewmodel实例还是一样,直呼简直不要太方便,然后就突然在想,为什么ViewModel这么神奇?
- 当看到官方的解说就联想到保存实例状态机制
ViewModel 的替代方案是保存要在界面中显示的数据的普通类。在 activity 或 Navigation 目的地之间导航时,这可能会造成问题。此时,如果您不利用保存实例状态机制存储相应数据,系统便会销毁相应数据。
- 终于在看了相关保存实例状态机制源码的时候,找到了关键代码
onRetainNonConfigurationInstance():
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;
}
- 当系统配置状态改变的时候,系统会调用onRetainNonConfigurationInstance()把存储viewmodel的仓库保存下来,即:
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
//custom是我们重写onRetainCustomNonConfigurationInstance()后的返回值,
//也就是我们想要保存的“上个状态” (数据)
nci.viewModelStore = viewModelStore;
//这里将viewModelStore对象保存
return nci;
- 看一下NonConfigurationInstances这个类:
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
接着看viewmodel对象是如何创建的:
viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MyViewModel.class);
点进get看逻辑:
@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);
}
在此处先获取class对象的CanonicalName,然后以 DEFAULT_KEY + “:” + canonicalName为键值key调用下面这个方法
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
//此处调用get(key)获取到viewmodel对象
if (modelClass.isInstance(viewModel)) {
//判断viewModel是不是这个class对象的实例,因为key值可以人为定义,可能造成同一键值获取到不同class的viewmodel实例,(因为我们使用的都是自定义Viewmodel(继承))
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
//onRequery默认空实现,不用去看
//OnRequeryFactory是要我们去继承和重写onRequery方法的,无需看
}
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);
//利用传进来的viewmodelfactory去创建viewmodel对象
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
ViewModelFactory都是利用反射去创建viewmodel对象的(因为传进来的参数是class对象)
- Tips: ViewModelProvider.AndroidViewModelFactory可以获取全局的Application
看构造方法
public AndroidViewModelFactory(@NonNull Application application) {
mApplication = application;
}
根据上面的代码可以知道,viewmodel是在mViewModelStore中利用key去获取的(猜测内部实现是hashmap)
再看一下ViewModelStore这个类(确实是hashmap)
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();
}
}
此时又在想,ViewModelStore对象是怎么来的? 接着继续跟踪mViewModelStore对象
private final ViewModelStore mViewModelStore;
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
- 跟踪发现mViewModelStore在构造viewmodelprovider对象的时候被赋值,且是final修饰,值对象为owner.getViewModelStore(), ower是ViewModelStoreOwner接口实例。
- 继续跟踪,发现mViewModelStore是在ComponentActivity.java的ensureViewModelStore()方法内被赋值的, 而owner.getViewModelStore()内部是调用 ensureViewModelStore()方法,确保mViewModelStore不为null
@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;
}
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();
}
}
}
- owner关ComponentActivity什么事?
ComponentActivity实现了ViewModelStoreOwner接口,而AppCompatActivity间接继承了CompoentActivity,因此此处的ower其实就是我们的传进来的activity实例对象
- 接着再来看一下ensureViewModelStore()方法的具体实现:
void ensureViewModelStore() {
//当mViewModelStore不为null时,啥事不做
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
//获取上个activity配置状态改变时调用onRetainNonConfigurationInstance()的返回值,也就是保存下来的custom和mviewstore对象
if (nc != null) {
//把activity销毁前状态的viewModelStore赋值给重建后的activity的mviewModelStore
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
经过以上分析在总结梳理一下:
- 屏幕旋转时(系统配置状态改变),系统会调用Activity的onRetainNonConfigurationInstance(),此时会保存activity销毁前的mViewModelStore
- viewmodel是通过mViewModelStore.get(key)获取的,所以最好不用自定义key,而是用系统生成的默认key值(否则可能会get到不同class对象的viewmodel而抛出异常)
- mViewModelStore的赋值是在ensureViewModelStore()方法中实现的
- 如果当前mViewModelStore != null,不做处理(也就是当前的Acitvity没被销毁)
- 通过getLastNonConfigurationInstance()的返回值获取到activity销毁重建前的状态里面的ViewModelStore,并赋值给mViewModelStore (只有配置状态改变时getLastNonConfigurationInstance()的返回值才不为null, 因为配置状态改变时才会调用onRetainNonConfigurationInstance())
- 如果1,2都为null,就重新new 一个ViewModelStore (即activity没有被非正常销毁时(旋转屏幕)的创建)
以上就是屏幕旋转时,viewmodel数据不会丢失的分析的原因。
四. 关于ViewModel相关的问题Q-And-A
从当前的activity跳转到另外的activity,再按返回键返回时ViewModel还在吗? 要是使用跳转方式跳转过来呢?
- 在返回来viewModel还在 (上面mViewmodelstore赋值的第1点)
,因为此时activity不会被销毁重建(onPause->onStop->onStart…) - 使用跳转方式需要分activity启动模式了:
- 如果跳转回来的activity实例是新创建(没有被非正常销毁),那么获取viewmodelstore也是新创建的(上面mViewmodelstore赋值的第3点),使用viewmodel实例也是新创建的
- 如果跳转回来的activity实例还是跟原来的一样,那viewmodelstore,viewmodel都跟跳转前的一样,不会重新创建新实例 (上面mViewmodelstore赋值的第1点)
根据上述分析原理如果利用viewmodel实现fragment之间共享数据?
- 实现fragment之间贡献数据前提是owner对象一定要使用共有的activity
在Fragment中:
//owner对象必须传入getActivity()
MyViewModel viewModel = new ViewModelProvider(getActivity(), new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(MyViewModel.class);
写个小案例,利用livedata + viewmodel 实现fragment间数据的共享联动效果
拖动上面进度条时下面的进度条会跟着动,拖动下面的进度条时上面的进度条会跟着动
- 核心代码:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_first, container, false);
SeekBar seekBar = root.findViewById(R.id.seekBar);
//只要上下俩个fragment的此处owner传入getActivity()就能实现了
MyViewModel viewModel = new ViewModelProvider(getActivity(), new ViewModelProvider.AndroidViewModelFactory(getActivity().getApplication())).get(MyViewModel.class);
Log.d("ning", "onCreateView: " + viewModel);
viewModel.getProgress().observe(getActivity(), new Observer<Integer>() {
@Override
public void onChanged(Integer i) {
seekBar.setProgress(i);
}
});
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
viewModel.getProgress().setValue(progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
// Inflate the layout for this fragment
return root;
}
在Fragment中屏幕旋转时数据为什么不会丢失?
ViewModel 存储在 FragmentManagerViewModel 中的,而 FragmentManagerViewModel 是存储在宿主 Activity 中的 ViewModelStore 中,又因 Activity 中 ViewModelStore不会因配置改变而销毁,故 Fragment 中 ViewModel 也不会因配置改变而销毁。
若要看源码请自行看,这个涉及到Activity的生命周期方法的详细逻辑
ViewModel什么时候被清除?
在ComponentActivity.java中可以看到
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
当 event == Lifecycle.Event.ON_DESTROY 和 isChangingConfigurations()为false 时,(即Activity会销毁且不是因为状态改变被销毁时)会调用 getViewModelStore().clear()方法把viewmodelstore给清空掉
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
因此在调用finish()方法(人为或者系统) 的时候 viewmodelstore数据是会被清空的,即Activity再创建的时候数据不会被保存。