ViewModel用于保存数据的值和状态。
1.一般情况下,屏幕发生选择后,Activity会销毁重新创建,这时定义在Activity中的变量的值就会丢失,重新变为初始状态。
1)可以在Activity标签下配置android:configChanges="orientation|screenSize",禁止屏幕旋转后Activity重新创建。

2)也可以在onSaveInstanceState和onRestoreInstanceState进行数据的缓存和恢复

3)另外一种就是将要介绍的ViewModel,可以用于保存数据,在发生屏幕旋转,Activity重新创建后,保存的数据不会丢失。
2.简单使用
1)创建ViewModel子类,用于保存数据
- public class MyViewModel extends ViewModel {
- public int age ;
-
- //数据回收时调用
- @Override
- protected void onCleared() {
- super.onCleared();
- }
-
- }
2)Activity中使用,通过反射方式拿到MyViewModel对象,里面的定义的age就不会随着屏幕旋转导致的Activity销毁创建而发生数据的变化。
- public class ViewModelActivity extends AppCompatActivity {
- private MyViewModel viewModel;
- private TextView ageTxt;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_view_model);
- ageTxt = findViewById(R.id.age);
- //通过反射方式获取MyViewModel实例
- viewModel = new ViewModelProvider(this).get(MyViewModel.class);
- ageTxt.setText(String.valueOf(viewModel.age));
-
- findViewById(R.id.set_value).setOnClickListener(v -> {
- ageTxt.setText(String.valueOf(++viewModel.age));
- });
- }
- }
3.ViewModel能够进行数据恢复的原理,源码分析。
- //通过反射方式获取MyViewModel实例
- viewModel = new ViewModelProvider(this).get(MyViewModel.class);
先看ViewModelProvider构造函数。
- public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
- this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
- ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
- : NewInstanceFactory.getInstance());
- }
1)owner.getViewModelStore()。他的实现是定义在了ComponentActivity
- @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;
- }
ensureViewModelStore方法
里面有以行注释,Restore the ViewModelStore from NonConfigurationInstances
从nc中恢复mViewModelStore 。如果为null就new 一个,如果不为null返回的就是这个。
系统就是通过这个对象,进行数据恢复的。
- 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();
- }
- }
- }
首先看ViewModelStore是啥?
ViewModelStore中有一个hashMap,
map中存放了ViewModel实例,而我们定义的数据就存放在ViewModel中。
- 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();
- }
- }
再看这行代码 NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
实现定义在了Activity中。
如果mLastNonConfigurationInstances不是null
就从 mLastNonConfigurationInstances.activity得到一个Object,赋值给FragmentActivity.NonConfigurationInstances
然后从这里面得到了ViewModelStore。那mLastNonConfigurationInstances又是啥,是在哪里赋值的呢?
- public Object getLastNonConfigurationInstance() {
- return mLastNonConfigurationInstances != null
- ? mLastNonConfigurationInstances.activity : null;
- }
mLastNonConfigurationInstances是在Activity#Attached赋值的。
- mLastNonConfigurationInstances是在Activity#Attached赋值的。
- final void attach(Context context, ActivityThread aThread,...,
- NonConfigurationInstances lastNonConfigurationInstances,
- ...) {
- mLastNonConfigurationInstances = lastNonConfigurationInstances;
- }
mLastNonConfigurationInstances是在attach方法中传进来的
attach是在performLaunchActivity方法中调用的。
- private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
-
- activity.attach(appContext, this, getInstrumentation(), r.token,
- r.ident, app, r.intent, r.activityInfo, title, r.parent,
- r.embeddedID, r.lastNonConfigurationInstances, config,
- r.voiceInteractor);
- }
r.lastNonConfigurationInstances就保存在了ActivityClientRecord中
- static final class ActivityClientRecord {
- Activity.NonConfigurationInstances lastNonConfigurationInstances;
- }
所以Activity在重建时,就从ViewModel中恢复了自定义的数据。
那么在Activity重建之前是如何保存数据的呢?
要清楚屏幕旋转后Activity重建的方法调用流程。
Activity正常开启时调用的方法是ActivityThread中的handleLaunchActivity()
旋转屏幕,Activity重建调用的是handleRelaunchActivity方法,
一个是Launch一个是Relaunch
callCallActivityOnSaveInstanceState会调用Activity的数据保存的方法onSaveInstanceState
我们可以通过onSaveInstanceState方法中存储要恢复的数据
在handleDestroyActivity方法中会调用performDestroyActivity方法
- private void handleRelaunchActivity(ActivityClientRecord tmp) {
- if (!r.paused) {
- performPauseActivity(r.token, false, r.isPreHoneycomb());
- }
- if (r.state == null && !r.stopped && !r.isPreHoneycomb()) {
- //在这个方法中调用Activity的数据恢复的方法onSaveInstanceState
- callCallActivityOnSaveInstanceState(r);
- }
- handleDestroyActivity(r.token, false, configChanges, true);
- //中间代码省略
- ..............
- //执行完上面的操作后,会调用handleLaunchActivity,开始调用Activity创建时的声明周期函数
- handleLaunchActivity(r, currentIntent);
- }
在performDestroyActivity中除了执行Activity正常的销毁的声明周期函数,
还给 r.lastNonConfigurationInstances进行了赋值,在这里把ViewModel中的数据存储起来的。
- private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
- int configChanges, boolean getNonConfigInstance) {
-
- if (!r.paused) {
- mInstrumentation.callActivityOnPause(r.activity);
- }
- if (!r.stopped) {
- r.activity.performStop();
- }
- if (getNonConfigInstance) {
- r.lastNonConfigurationInstances
- = r.activity.retainNonConfigurationInstances();
- }
- mInstrumentation.callActivityOnDestroy(r.activity);
- }
看r.activity.retainNonConfigurationInstances(),retain在英文中就有保持保留的意思。
在Activity中retainNonConfigurationInstances方法
- NonConfigurationInstances retainNonConfigurationInstances() {
- Object activity = onRetainNonConfigurationInstance();
- .....
- NonConfigurationInstances nci = new NonConfigurationInstances();
- nci.activity = activity;
- nci.children = children;
- nci.fragments = fragments;
- nci.loaders = loaders;
- ......
- return nci;
- }
onRetainNonConfigurationInstance方法是在FragmentActivity中定义的
在这个方法中返回了FragmentActivity.NonConfigurationInstances对象,并把Fragment中的mViewModelStore保存了起来
- public final Object onRetainNonConfigurationInstance() {
- Object custom = this.onRetainCustomNonConfigurationInstance();
- FragmentManagerNonConfig fragments = this.mFragments.retainNestedNonConfig();
- if (fragments == null && this.mViewModelStore == null && custom == null) {
- return null;
- } else {
- FragmentActivity.NonConfigurationInstances nci = new FragmentActivity.NonConfigurationInstances();
- nci.custom = custom;
- nci.viewModelStore = this.mViewModelStore;
- nci.fragments = fragments;
- return nci;
- }
- }
NonConfigurationInstances实例中持有一个activity对象,这个activity指向 FragmentActivity.NonConfigurationInstances 的实例。
而ActivityClientRecord中的lastNonConfigurationInstances 就是NonConfigurationInstances的实例。
再回头看看恢复的代码
mLastNonConfigurationInstances就是ActivityClientRecord中的lastNonConfigurationInstances
- final void attach(Context context, ActivityThread aThread,...,
- NonConfigurationInstances lastNonConfigurationInstances,
- ...) {
- mLastNonConfigurationInstances = lastNonConfigurationInstances;
- }
-
- FragmentActivity.NonConfigurationInstances nc =
- (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
- if (nc != null) {
- this.mViewModelStore = nc.viewModelStore;
- }
-
- public Object getLastNonConfigurationInstance() {
- return mLastNonConfigurationInstances != null
- ? mLastNonConfigurationInstances.activity : null;
- }
getLastNonConfigurationInstance返回的是NonConfigurationInstances的实例,如果不为空
从这里拿到activity赋值给FragmentActivity.NonConfigurationInstances,然后从这里面拿到保存的
nc.viewModelStore赋值给FragmentActivity的mViewModelStore。
而ViewModelStore保存了我们自定义的ViewModel实例和数据。
这样ViewModel就完成了数据的保存和恢复。
那有人可能会有这个疑问了,旋转屏幕会销毁Activity并执行Activity创建的声明周期函数,这样就能恢复数据。
按下back键或者finish,还能恢复之前的数据吗,肯定是不能的。为什么呢,同样都会执行重建的声明周期。
问题的关键在于这里performDestroyActivity方法中getNonConfigInstance这个boolean值是true还是false,
true就保存数据,false就不保存数据。
- private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
- int configChanges, boolean getNonConfigInstance) {
- if (getNonConfigInstance) {
- r.lastNonConfigurationInstances
- = r.activity.retainNonConfigurationInstances();
- }
- }
- }
Activity重建会调用handleRelaunchActivity在这传进来的值是true
- private void handleRelaunchActivity(ActivityClientRecord tmp) {
-
- handleDestroyActivity(r.token, false, configChanges, true);
- }
而正常finish也会调用handleDestroyActivity方法,但是这时传进来的值是false。
ActivityThread handler中
- case DESTROY_ACTIVITY:
- handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
- msg.arg2, false);
- break;