jetpack探究之ViewModel

ViewModel

一 概述

ViewModel以注重生命周期的方式存储和管理界面相关的数据

因配置更改而重新创建 Activity 后,新 Activity 必须页面数据。对于简单的数据,Activity 可以使用 onSaveInstanceState() 方法从 onCreate() 中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。

ViewModel从界面控制器逻辑中分离出视图数据所有权,这种方法简单高效

二 实现

  1. 架构组件为界面控制器提供了 ViewModel辅助程序类,该类负责为界面准备数据。

    自定义一个辅助程序类,继承自ViewModel

    public class MyViewModel extends ViewModel {
        public int number=0;
    }
    

    在配置更改期间会自动保留 ViewModel 对象,以便它们存储的数据立即可供下一个 Activity 或 Fragment 实例使用。

  2. Activity中访问

    viewModel= new ViewModelProvider(this).get(MyViewModel.class);
    binding.textView.setText(String.valueOf(viewModel.number));
    binding.button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    viewModel.number++;
                    binding.textView.setText(String.valueOf(viewModel.number));
                }
    });
    

ViewModel对象存在的时间比视图或 LifecycleOwners 的特定实例存在的时间更长。这还意味着可以更轻松地编写涵盖 ViewModel的测试,因为它不了解视图和 Lifecycle 对象。ViewModel对象可以包含 LifecycleObservers,如 LiveData对象。但是,ViewModel对象绝不能观察对生命周期感知型可观察对象(如 LiveData对象)的更改。 如果 ViewModel 需要 Application 上下文(例如,为了查找系统服务),它可以扩展 AndroidViewModel类并设置用于接收 Application 的构造函数,因为 Application 类会扩展 Context

三 Fragment间传递数据

    public class SharedViewModel extends ViewModel {
        private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

        public void select(Item item) {
            selected.setValue(item);
        }

        public LiveData<Item> getSelected() {
            return selected;
        }
    }

    public class MasterFragment extends Fragment {
        private SharedViewModel model;

        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            itemSelector.setOnClickListener(item -> {
                model.select(item);
            });
        }
    }

    public class DetailFragment extends Fragment {

        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            model.getSelected().observe(getViewLifecycleOwner(), { item ->
               // Update the UI.
            });
        }
    }
    

两个 Fragment 各自获取 ViewModelProvider时,它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)

四 ViewModel的生命周期

在这里插入图片描述

一般来说,ViewModel从创建对象开始存在,直至 activity销毁消失(onClear()方法)。

五 向ViewModel传递数据

ViewModel传递数据需要借助ViewModelProvider.Factory接口

  1. 为ViewModel辅助类添加构造器

    public class MyViewModel<T extends ViewModel> extends ViewModel {
        public int number;
        public MyViewModel(int number){
            this.number=number;
        }
    }
    
  2. 新建一个类实现ViewModelProvider.Factory接口

    public class MyViewModelFactory implements ViewModelProvider.Factory {
        private int num;
        public MyViewModelFactory(int num){
            this.num=num;
        }
    
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            return (T) new MyViewModel(num);
        }
    }
    

    create()方法中创建了一个MyViewModel实例,并传入数据

  3. activity中使用

    viewModel= new ViewModelProvider(this, new MyViewModelFactory(num)).get(MyViewModel.class);
    

    传入了一个MyViewModelFactory对象作为参数,并传入了数据,最后返还到MyViewModel中

六 与onSaveInstanceState的区别

从存储位置上来说,ViewModel 是在内存中,因此其读写速度更快,但当进程被系统杀死后,ViewModel 中的数据也不存在了。从数据存储的类型上来看,ViewModel 适合存储相对较重的数据,例如网络请求到的 list 数据,而 onSaveInstanceState 适合存储轻量可序列化的数据

七 源码结构

ViewModel:抽象类,主要有 clear方法,final级,不可修改。开发者可重写 onCleared 方法来自定义数据的清空

ViewModelStore:内部维护一个 HashMap 以管理 ViewModel

ViewModelStoreOwner:接口,ViewModelStore 的作用域,实现类为 ComponentActivityFragment,此外还有 FragmentActivity.HostCallbacks,仅有一个方法getViewModelStore()

ViewModelProvider:用于创建 ViewModel,其构造方法有两个参数,第一个参数传入 ViewModelStoreOwner ,确定了 ViewModelStore 的作用域,第二个参数为 ViewModelProvider.Factory,用于初始化 ViewModel 对象,默认为 getDefaultViewModelProviderFactory() 方法获取的 factory

ViewModelStoreOwner 持有 ViewModelStore 持有 ViewModel

八 源码分析

1如何做到 activity 重建后 ViewModel 仍然存在

activity 有着 saveInstanceState 机制,但在ViewModel中并未使用该机制。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4TQdwmBJ-1596693689718)(D:\typora\md文档\操作系统\43.png)]

ComponentActivity实现了 ViewModelStoreOwner 接口,意味着需要重写 getViewModelStore() 方法,该方法为 ComponentActivitymViewModelStore 变量赋值。实际上需求就是Activity重建时,这个 mViewModelStore 变量不变。

由于 ActivityThread 中的 ActivityClientRecord 不受 activity 重建的影响,所以 activity 重建时 mLastNonConfigurationInstances 能够得到上一次的值,使得 mViewModelStore 值不变

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.");
        }
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

2如何做到 fragment 重建后 ViewModel 仍然存在

fragment 重建后其内部的 getViewModelStore() 方法返回的对象是相同的

FragmentManagerViewModel 管理着内部的 ViewModelStore 和 child 的 FragmentManagerViewModel 。因此保证 mNonConfig值不变即能确保fragment中的 getViewModelStore()不变。

只要保证作用域(viewModelStore)相同,即可获取相同的 ViewModel 实例

3如何控制作用域

ViewModelStoreOwner 代表着作用域,其内部唯一的方法返回 ViewModelStore 对象

只要保证相同作用域的 ViewModelStore 对象相同就能保证相同作用域获取到相同的 ViewModel 对象

重建时 ViewModelStore 对象不变

4如何避免内存泄漏

activity/fragment 依赖它,而 ViewModel 不依赖 activity/fragment。因此只要不让 ViewModel 持有 context 或 view 的引用,就不会造成内存泄漏

总结

  1. activity 重建后 mViewModelStore 通过 ActivityThread 的一系列方法能够保持不变,从而当 activity 重建时 ViewModel 中的数据不受影响

  2. 通过宿主 activity 范围内共享的 FragmentManagerViewModel 来存储 fragment 的 ViewModelStore 和子 fragment 的 FragmentManagerViewModel ,而 activity 重建后 FragmentManagerViewModel 中的数据不受影响,因此 fragment 内部的 ViewModel 的数据也不受影响

  3. 通过同一 ViewModelStoreOwner 获取的 ViewModelStore 相同,从而保证同一作用域通过 ViewModelProvider 获取的 ViewModel 对象是相同的
    的 FragmentManagerViewModel ,而 activity 重建后 FragmentManagerViewModel 中的数据不受影响,因此 fragment 内部的 ViewModel 的数据也不受影响

  4. 通过同一 ViewModelStoreOwner 获取的 ViewModelStore 相同,从而保证同一作用域通过 ViewModelProvider 获取的 ViewModel 对象是相同的

  5. 通过单向依赖(视图控制器持有 ViewModel )来解决内存泄漏的问题

参考自:【背上Jetpack之ViewModel】即使您不使用MVVM也要了解ViewModel ——ViewModel 的职能边界
https://juejin.im/post/6844904100493017095

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值