• ViewModel


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

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

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

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

    3)另外一种就是将要介绍的ViewModel,可以用于保存数据,在发生屏幕旋转,Activity重新创建后,保存的数据不会丢失。

     2.简单使用

       1)创建ViewModel子类,用于保存数据

    1. public class MyViewModel extends ViewModel {
    2. public int age ;
    3. //数据回收时调用
    4. @Override
    5. protected void onCleared() {
    6. super.onCleared();
    7. }
    8. }

    2)Activity中使用,通过反射方式拿到MyViewModel对象,里面的定义的age就不会随着屏幕旋转导致的Activity销毁创建而发生数据的变化。

    1. public class ViewModelActivity extends AppCompatActivity {
    2. private MyViewModel viewModel;
    3. private TextView ageTxt;
    4. @Override
    5. protected void onCreate(@Nullable Bundle savedInstanceState) {
    6. super.onCreate(savedInstanceState);
    7. setContentView(R.layout.activity_view_model);
    8. ageTxt = findViewById(R.id.age);
    9. //通过反射方式获取MyViewModel实例
    10. viewModel = new ViewModelProvider(this).get(MyViewModel.class);
    11. ageTxt.setText(String.valueOf(viewModel.age));
    12. findViewById(R.id.set_value).setOnClickListener(v -> {
    13. ageTxt.setText(String.valueOf(++viewModel.age));
    14. });
    15. }
    16. }

    3.ViewModel能够进行数据恢复的原理,源码分析。

    1. //通过反射方式获取MyViewModel实例
    2. viewModel = new ViewModelProvider(this).get(MyViewModel.class);

    先看ViewModelProvider构造函数。

    1. public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    2. this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
    3. ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
    4. : NewInstanceFactory.getInstance());
    5. }

     1)owner.getViewModelStore()。他的实现是定义在了ComponentActivity

    1. @NonNull
    2. @Override
    3. public ViewModelStore getViewModelStore() {
    4. if (getApplication() == null) {
    5. throw new IllegalStateException("Your activity is not yet attached to the "
    6. + "Application instance. You can't request ViewModel before onCreate call.");
    7. }
    8. ensureViewModelStore();
    9. return mViewModelStore;
    10. }

         ensureViewModelStore方法

         里面有以行注释,Restore the ViewModelStore from NonConfigurationInstances

         从nc中恢复mViewModelStore 。如果为null就new 一个,如果不为null返回的就是这个。

       系统就是通过这个对象,进行数据恢复的。

    1. void ensureViewModelStore() {
    2. if (mViewModelStore == null) {
    3. NonConfigurationInstances nc =
    4. (NonConfigurationInstances) getLastNonConfigurationInstance();
    5. if (nc != null) {
    6. // Restore the ViewModelStore from NonConfigurationInstances
    7. mViewModelStore = nc.viewModelStore;
    8. }
    9. if (mViewModelStore == null) {
    10. mViewModelStore = new ViewModelStore();
    11. }
    12. }
    13. }

    首先看ViewModelStore是啥?

    ViewModelStore中有一个hashMap,

    map中存放了ViewModel实例,而我们定义的数据就存放在ViewModel中。

    1. public class ViewModelStore {
    2. private final HashMap<String, ViewModel> mMap = new HashMap<>();
    3. final void put(String key, ViewModel viewModel) {
    4. ViewModel oldViewModel = mMap.put(key, viewModel);
    5. if (oldViewModel != null) {
    6. oldViewModel.onCleared();
    7. }
    8. }
    9. final ViewModel get(String key) {
    10. return mMap.get(key);
    11. }
    12. Set<String> keys() {
    13. return new HashSet<>(mMap.keySet());
    14. }
    15. /**
    16. * Clears internal storage and notifies ViewModels that they are no longer used.
    17. */
    18. public final void clear() {
    19. for (ViewModel vm : mMap.values()) {
    20. vm.clear();
    21. }
    22. mMap.clear();
    23. }
    24. }

    再看这行代码 NonConfigurationInstances nc =
                        (NonConfigurationInstances) getLastNonConfigurationInstance();

    实现定义在了Activity中。

    如果mLastNonConfigurationInstances不是null
     就从  mLastNonConfigurationInstances.activity得到一个Object,赋值给FragmentActivity.NonConfigurationInstances
     然后从这里面得到了ViewModelStore。那mLastNonConfigurationInstances又是啥,是在哪里赋值的呢?

    1. public Object getLastNonConfigurationInstance() {
    2. return mLastNonConfigurationInstances != null
    3. ? mLastNonConfigurationInstances.activity : null;
    4. }

    mLastNonConfigurationInstances是在Activity#Attached赋值的。

    1. mLastNonConfigurationInstances是在Activity#Attached赋值的。
    2. final void attach(Context context, ActivityThread aThread,...,
    3. NonConfigurationInstances lastNonConfigurationInstances,
    4. ...) {
    5. mLastNonConfigurationInstances = lastNonConfigurationInstances;
    6. }

    mLastNonConfigurationInstances是在attach方法中传进来的
    attach是在performLaunchActivity方法中调用的。

    1. private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    2. activity.attach(appContext, this, getInstrumentation(), r.token,
    3. r.ident, app, r.intent, r.activityInfo, title, r.parent,
    4. r.embeddedID, r.lastNonConfigurationInstances, config,
    5. r.voiceInteractor);
    6. }

     r.lastNonConfigurationInstances就保存在了ActivityClientRecord中

    1. static final class ActivityClientRecord {
    2. Activity.NonConfigurationInstances lastNonConfigurationInstances;
    3. }

    所以Activity在重建时,就从ViewModel中恢复了自定义的数据。

     那么在Activity重建之前是如何保存数据的呢?
     要清楚屏幕旋转后Activity重建的方法调用流程。

    Activity正常开启时调用的方法是ActivityThread中的handleLaunchActivity()
    旋转屏幕,Activity重建调用的是handleRelaunchActivity方法,
    一个是Launch一个是Relaunch
    callCallActivityOnSaveInstanceState会调用Activity的数据保存的方法onSaveInstanceState
    我们可以通过onSaveInstanceState方法中存储要恢复的数据
    在handleDestroyActivity方法中会调用performDestroyActivity方法

    1. private void handleRelaunchActivity(ActivityClientRecord tmp) {
    2. if (!r.paused) {
    3. performPauseActivity(r.token, false, r.isPreHoneycomb());
    4. }
    5. if (r.state == null && !r.stopped && !r.isPreHoneycomb()) {
    6. //在这个方法中调用Activity的数据恢复的方法onSaveInstanceState
    7. callCallActivityOnSaveInstanceState(r);
    8. }
    9. handleDestroyActivity(r.token, false, configChanges, true);
    10. //中间代码省略
    11. ..............
    12. //执行完上面的操作后,会调用handleLaunchActivity,开始调用Activity创建时的声明周期函数
    13. handleLaunchActivity(r, currentIntent);
    14. }

    在performDestroyActivity中除了执行Activity正常的销毁的声明周期函数,
     还给 r.lastNonConfigurationInstances进行了赋值,在这里把ViewModel中的数据存储起来的。

    1. private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
    2. int configChanges, boolean getNonConfigInstance) {
    3. if (!r.paused) {
    4. mInstrumentation.callActivityOnPause(r.activity);
    5. }
    6. if (!r.stopped) {
    7. r.activity.performStop();
    8. }
    9. if (getNonConfigInstance) {
    10. r.lastNonConfigurationInstances
    11. = r.activity.retainNonConfigurationInstances();
    12. }
    13. mInstrumentation.callActivityOnDestroy(r.activity);
    14. }

    看r.activity.retainNonConfigurationInstances(),retain在英文中就有保持保留的意思。
    在Activity中retainNonConfigurationInstances方法

    1. NonConfigurationInstances retainNonConfigurationInstances() {
    2. Object activity = onRetainNonConfigurationInstance();
    3. .....
    4. NonConfigurationInstances nci = new NonConfigurationInstances();
    5. nci.activity = activity;
    6. nci.children = children;
    7. nci.fragments = fragments;
    8. nci.loaders = loaders;
    9. ......
    10. return nci;
    11. }

    onRetainNonConfigurationInstance方法是在FragmentActivity中定义的
    在这个方法中返回了FragmentActivity.NonConfigurationInstances对象,并把Fragment中的mViewModelStore保存了起来

    1. public final Object onRetainNonConfigurationInstance() {
    2. Object custom = this.onRetainCustomNonConfigurationInstance();
    3. FragmentManagerNonConfig fragments = this.mFragments.retainNestedNonConfig();
    4. if (fragments == null && this.mViewModelStore == null && custom == null) {
    5. return null;
    6. } else {
    7. FragmentActivity.NonConfigurationInstances nci = new FragmentActivity.NonConfigurationInstances();
    8. nci.custom = custom;
    9. nci.viewModelStore = this.mViewModelStore;
    10. nci.fragments = fragments;
    11. return nci;
    12. }
    13. }

    NonConfigurationInstances实例中持有一个activity对象,这个activity指向 FragmentActivity.NonConfigurationInstances 的实例。
    而ActivityClientRecord中的lastNonConfigurationInstances 就是NonConfigurationInstances的实例。
    再回头看看恢复的代码

    mLastNonConfigurationInstances就是ActivityClientRecord中的lastNonConfigurationInstances

    1. final void attach(Context context, ActivityThread aThread,...,
    2. NonConfigurationInstances lastNonConfigurationInstances,
    3. ...) {
    4. mLastNonConfigurationInstances = lastNonConfigurationInstances;
    5. }
    6. FragmentActivity.NonConfigurationInstances nc =
    7. (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
    8. if (nc != null) {
    9. this.mViewModelStore = nc.viewModelStore;
    10. }
    11. public Object getLastNonConfigurationInstance() {
    12. return mLastNonConfigurationInstances != null
    13. ? mLastNonConfigurationInstances.activity : null;
    14. }

    getLastNonConfigurationInstance返回的是NonConfigurationInstances的实例,如果不为空
     从这里拿到activity赋值给FragmentActivity.NonConfigurationInstances,然后从这里面拿到保存的
     nc.viewModelStore赋值给FragmentActivity的mViewModelStore。
     而ViewModelStore保存了我们自定义的ViewModel实例和数据。
     这样ViewModel就完成了数据的保存和恢复。

    那有人可能会有这个疑问了,旋转屏幕会销毁Activity并执行Activity创建的声明周期函数,这样就能恢复数据。
    按下back键或者finish,还能恢复之前的数据吗,肯定是不能的。为什么呢,同样都会执行重建的声明周期。
    问题的关键在于这里performDestroyActivity方法中getNonConfigInstance这个boolean值是true还是false,
    true就保存数据,false就不保存数据。

    1. private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
    2. int configChanges, boolean getNonConfigInstance) {
    3. if (getNonConfigInstance) {
    4. r.lastNonConfigurationInstances
    5. = r.activity.retainNonConfigurationInstances();
    6. }
    7. }
    8. }

    Activity重建会调用handleRelaunchActivity在这传进来的值是true

    1. private void handleRelaunchActivity(ActivityClientRecord tmp) {
    2. handleDestroyActivity(r.token, false, configChanges, true);
    3. }

    而正常finish也会调用handleDestroyActivity方法,但是这时传进来的值是false。
    ActivityThread handler中

    1. case DESTROY_ACTIVITY:
    2. handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
    3. msg.arg2, false);
    4. break;

  • 相关阅读:
    一个线程的生命周期有哪几种状态?它们之间如何流转的?
    excel函数的基本操作和案例演示
    2024字节跳动校招面试真题汇总及其解答(二)
    10_分类和static
    Android Jetpack之LiveData 源码分析
    Unity接入北斗探针SDK(基于UnityPlayerActivity)丨五、编写Unity代码,完成整个项目
    【zookeeper】zk集群安装与启动踩坑点
    【无标题】
    volatile 无法保证原子性 案例展示
    Linux部署Kafka2.8.1
  • 原文地址:https://blog.csdn.net/niuyongzhi/article/details/133383670