• Android Jetpack之ViewModel的使用及源码分析


    ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。

    现在,常用的项目架构,在MVP向MVVM转变。相对于MVP中的P(presenter),MVVM中的ViewModel有哪些优势呢。

    ViewModel的优势

    • 可以持久的保留界面状态(比如,屏幕旋转)
    • 提供了对业务逻辑的访问权限()

    我们先通过代码来看下ViewModel的使用。然后,看下ViewModel的原理

    本文内容:

    一、viewModel的使用

    之前,学习了LiveData。这里,我们用LiveData+ViewModel组合的方式使用。

    在这里插入图片描述

    首先创建一个ViewModel,用来更新UI的数据

    public class MyViewModel extends ViewModel {
        //liveData
        public MutableLiveData name = new MutableLiveData<>();
    
        /**
         * 获取用户名字
         */
        public void obtainUserName() {
            name.setValue("张三");
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    下面创建一个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() {
                @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");
                }
            });
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    最后,就是清单文件,什么也不配置

    到这里,我们运行项目后,点击按钮,数据都是正常显示的。

    在这里插入图片描述

    但是,如果,我们旋转屏幕的话,就会发现第一个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);
    
    
    • 1
    • 2

    这里,我们分为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 
    	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    我们在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;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们发现,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;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    通过上面的代码,我们可以发现,如果是横竖屏切换的话。

    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;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    工厂如果是null的话,直接实例化一个SavedStateViewModelFactory工厂

    到这里new ViewModelProvider() 就看完啦

    整体的流程:

    在这里插入图片描述

    2.2,ViewModelProvider#get(MyViewModel.class)分析

    从传入的class,我们基本上也能猜测,应该是通过反射重建的ViewModel

    看下具体代码

    
        @MainThread
        public  T get(@NonNull Class 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);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    看下get重载方法

    
     @MainThread
        public  T get(@NonNull String key, @NonNull Class 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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    这里面,先查看了viewModeStore是否保存了该ViewModel,如果保存了直接返回,没有的话,调用create()方法创建,并且保存到viewModelStore中。

    主要作用:

    • 从ViewModelStore中获取ViewModel
    • 如果上面没有,通过Factory#create()方法获取ViewModel
    • 把ViewModel放到ViewModelStore中

    我们看看Factory#create()方法
    上面说到了,默认的Factory是SavedStateViewModelFactory,它继承了KeyedFactory.直接看它的create()方法

    SavedStateViewModelFactory.java
    
    	@Override
        public  T create(@NonNull String key, @NonNull Class modelClass) {
            boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
            Constructor 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());
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    Facory获取ViewModel,主要是获取合适的构造器通过反射创建ViewModel对象。

    到这里,我们查看完了ViewModel获取的全过程。

    获取ViewModel的整体流程图

    在这里插入图片描述

    三、其它的相关知识点

    3.1 ViewModelStore类

    有木有发现,里面出现最多的就是ViewModelStore类,我们看下这个类

    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();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    通过上面,我们知道,这个类就是封装了一下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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    这里,可以明显看到,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();
                        }
                    }
                }
            });
    
    	}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在Activity初始化的时候,已经为ViewModelStore设置了数据会在页面onDestroy时清理。

    官方文档:https://developer.android.google.cn/topic/libraries/architecture/viewmodel


    ViewModel类型:SharedViewModel,AndroidViewModel,ViewModel,SavedStateViewModel

  • 相关阅读:
    LeetCode204,计算质数,线性筛
    C++异常
    python实现办公自动化读书笔记——自动化整理文件
    【推荐系统】geohash召回
    使用debezium、kafka-connect将postgres数据实时同步到kafka中
    Au 入门系列之九:多轨混音
    NumPy 均匀分布模拟及 Seaborn 可视化教程
    推荐《一拳超人》
    “JSR303和拦截器在Java Web开发中的应用与实践“
    文件的物理结构(连续分配,链接分配,索引分配)
  • 原文地址:https://blog.csdn.net/ecliujianbo/article/details/128023830