JetPack之ViewModel最新源码详细分析

Stella981
• 阅读 734

本文会基于最新版ViewModel使用方法与源码进行详细分析,从注册到实现ViewModel界面数据如何保存与管理全部涉及。

**

简介:

**ViewModel是JetPack系列库之一,它用来对组件的界面数据进行管理,且当组件的状态发生改变时数据依然留存。

优点:1.当所依赖组件的状态发生改变时,例如屏幕旋转等,界面数据不会发生改变
2.实现MVVM架构的基础,在日常开发中,ViewModel就是MVVM架构的VM层,它相当于MVP架构的present,我们可以将业务逻辑以及数据交互的部分放入ViewModel中。

源码分析:

版本:viewmodel:1.1.1
此处使用的示例代码与livedata源码分析的相同,不影响流程分析

首先构造一个继承于ViewModel的类
JetPack之ViewModel最新源码详细分析
接着创建一个factory 实例传入ViewModelProvider(this,factory )中去并调用get方法将我们的ViewModel作为参数传入。
此处需要注意的是在以前的版本中直接通过 ViewModelProviders.of(this).get()方法来完成这个操作的。在最新的版本中已经取消了of方法,并且构造方法必须要传入2个参数
JetPack之ViewModel最新源码详细分析
查看ViewModelProvider的构造方法可看出构造ViewModelProvider时必须传入2个参数才行,其中第一个参数为ViewModelStore,第二个参数为factory,
ViewModelStore用来存储和管理ViewModelfactory则用来对ViewModel进行实例化。此时构建ViewModelProvider时传入的参数为所依赖的组件的ViewModelStore,也就是说,我们通过组件的ViewModelStore来对ViewModel进行存储和管理。
JetPack之ViewModel最新源码详细分析

其中factory需要我们在构造的时候进行传入,ViewModelProvider给我们提供了3种实例化的方法,分别是简单工厂模式构造,通过反射构造实例,以及我们自己实现factory构造实例。

当我们使用ViewModelProvider.NewInstanceFactory()构造实例时,查看源码可看出,它实现了Factory 接口,并且在其内部的create方法中会调用我们在之前get方法中传入的viewmodelnewInstance方法。这个方法需我们自己在viewmodel中去实现,用来返回viewmodel实例,如果实例化出错则会抛出异常

   public static class NewInstanceFactory implements Factory {

        @SuppressWarnings("ClassNewInstance")
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
            //返回modelClass实例对象
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
    }

使用AndroidViewModelFactory 方式反射生成ViewModel,该Factory使用单例模式进行构建 ,所以它的生命周期等同于应用程序的生命周期,且只有一个实例,推荐使用此模式来获取factory,当它的create方法被调用时,会通过application利用反射创建一个ViewModel实例并返回。

  public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

        private static AndroidViewModelFactory sInstance;
        @NonNull
        public static AndroidViewModelFactory getInstance(@NonNull Application application) {
            if (sInstance == null) {
                sInstance = new AndroidViewModelFactory(application);
            }
            return sInstance;
        }

        private Application mApplication;

        public AndroidViewModelFactory(@NonNull Application application) {
        //构造时进行传入
            mApplication = application;
        }

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
                //noinspection TryWithIdenticalCatches
                try {
                //通过反射创建viewmodel
                    return modelClass.getConstructor(Application.class).newInstance(mApplication);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InstantiationException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                }
            }
            return super.create(modelClass);
        }
    }

第三种方式就是直接实现Factory接口,并自己实现create方法,我们需要在该create方法内,返回viewmodel的实例

  /**
     * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
     */
    public interface Factory {
        /**
         * Creates a new instance of the given {@code Class}.
         * <p>
         *
         * @param modelClass a {@code Class} whose instance is requested
         * @param <T>        The type parameter for the ViewModel.
         * @return a newly created ViewModel
         */
        @NonNull
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

当我们在构造ViewModelProvider时,会将我们传入的这2个参数进行保存,

   public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

当我们调用get方法时

    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    //获取到ViewModel的类名
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        //再次进行回调
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

在该方法内首先会从ViewModelStore中通过key去获取ViewModel ,这个key就是上面的DEFAULT_KEY +类名。如果找到则直接返回,否则调用factorycreate方法创建ViewModel实例放入ViewModelStore进行管理和保存并返回该实例

    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    //从ViewModelStore中获取viewModel 
        ViewModel viewModel = mViewModelStore.get(key);
    //如果找到则直接返回
        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            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);
        }
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

ViewModelStore 内部通过hashmap来对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();
    }
}

到此处为止源码部分分析完毕,如何触发viewmodel对组件界面的状态保存继续分析

上面说过在构建ViewModelProvider时传入的ViewModelStore来自于构建它所传入的this,也就是当前组件,我们知道ViewModelStore起到了对viewmodel的管理作用,viewmodel的获取和存储都是通过它来完成。

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    在这里调用了组件的owner.getViewModelStore()方法
        this(owner.getViewModelStore(), factory);
    }

追踪getViewModelStore方法,发现它是一个ViewModelStoreOwner 接口中的一个方法

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

这里发现它的实现类有很多个,我们一般在ViewModelProvider中传入的是fragment或者activity,而fragment其实最终也是调用的activity的getViewModelStore方法这里查看activity如何处理。
JetPack之ViewModel最新源码详细分析

发现在activity的父类ComponentActivity中有这个方法的实现,通过getViewModelStore去获取一个ViewModelStore ,当ViewModelStore 获取为空时,会调用getLastNonConfigurationInstance方法去恢复状态改变前的NonConfigurationInstances,和这个方法对应的是onRetainNonConfigurationInstance,该方法会在组件状态改变时去保存数据信息,由系统调用

   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.");
        }
        //如果当前组件的mViewModelStore 为空,则从NonConfigurationInstances 中去获取
        if (mViewModelStore == null) {
        //返回之前
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            //如果在NonConfigurationInstances 中没有找到,则创建一个新的ViewModelStore并保存起来
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

在代码中ViewModelStore会从NonConfigurationInstances 中去尝试获取,我们查看viewModelStore的赋值时机即可

  static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }

查看发现它的赋值时机就是在onRetainNonConfigurationInstance中完成的,也就是说在组件的状态发生改变时,会去尝试获取viewModelStore,并将viewModelStore保存在NonConfigurationInstances 中并返回。
也就是在onRetainNonConfigurationInstance中会对viewModelStore进行保存,在getLastNonConfigurationInstance中会对viewModelStore进行恢复
JetPack之ViewModel最新源码详细分析
具体流程:
当我们在activity中去实例化ViewModelProvider时,所依赖的activity在其内部会调用getViewModelStore方法去获取一个ViewModelStore,如果没有则创建一个,并保存起来,然后调用ViewModelProvider.get方法将我们的viewmodel实例通过ViewModelStore进行保存管理,当我们的activity的状态发生改变,如旋转屏幕,此时系统会调用onRetainNonConfigurationInstance方法,在这个方法内会将我们的ViewModelStore进行保存。

我们一般会搭配livedata来使用viewmodellivedata常用来存储与界面相关的数据元素,当我们的activity状态改变后,界面再次从livedata中获取界面数据时,由于livedata实例由viewmodel保管,而viewmodel存储在ViewModelStore中,当前activity去获取ViewModelStore时会通过getLastNonConfigurationInstance方法恢复之前的ViewModelStore,所以状态改变前后的ViewModelStore是同一个,所以数据没有发生任何改变,从而界面相关数据不会发生丢失。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
劳伦斯 劳伦斯
3年前
前端面试题自检 Vue 网络 浏览器 性能优化部分
框架VueMVVM是什么?ModelViewViewModel,Model表示数据模型层。view表示视图层,ViewModel是View和Model层的桥梁,数据绑定到viewModel层并自动渲染到页面中,视图变化通知viewModel层更新数据。Vue的生命周期
Stella981 Stella981
3年前
Android Jetpack 库架构组件 ViewModel+LiveData 基础使用
ViewModel是什么ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel类让数据可在发生屏幕旋转等配置更改后继续留存。为什么ViewModel类中的数据可在发生屏幕旋转等配置更改后继续留存?因为ViewModel的生命周期长于组件(Activi
Stella981 Stella981
3年前
Nginx + lua +[memcached,redis]
精品案例1、Nginxluamemcached,redis实现网站灰度发布2、分库分表/基于Leaf组件实现的全球唯一ID(非UUID)3、Redis独立数据监控,实现订单超时操作/MQ死信操作SelectPollEpollReactor模型4、分布式任务调试Quartz应用
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Stella981 Stella981
3年前
ReactNative state更新,视图不更新的问题
开发中遇到这样的问题,我更新了state一个数组的某个元素的选中状态,打印出的数据也显示修改正确了,但是界面却没更新。例如下图点击某项修改选中状态。!(https://oscimg.oschina.net/oscnet/c3291a62b5f638d1e35dd7a719ade39f226.png)代码中之前是这样写的,结果界面没有更新。