ViewModel

ViewModel用于保存数据的值和状态。

1.一般情况下,屏幕发生选择后,Activity会销毁重新创建,这时定义在Activity中的变量的值就会丢失,重新变为初始状态。

1)可以在Activity标签下配置android:configChanges="orientation|screenSize",禁止屏幕旋转后Activity重新创建。

ViewModel_第1张图片

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

ViewModel_第2张图片

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 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 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;

你可能感兴趣的:(android开发,java,android)