Viewmodel#
用途#
用于关注界面相关的数据,关注生命周期期间所需的数据
例如:屏幕旋转、语言、时区发生更改后继续显示的数据。
关键:对数据的管理、存储数据,是一种管理数据的方式
优势#
- 销毁或重建后,页面瞬时态的数据会丢失,如rotate后,ViewModel不会被销毁,Activity的成员变了会销毁
- 比onSaveInstanceState能存的数据量更大,如网络请求查询回来的用户列表接口、bitmap、网络缓存的视频流适合放在Viewmodel里,不适合放在OnSaveInstanceState
- 避免页面销毁后,异步任务返回造成的内存泄漏;
- 减少代码维护量,避免手动编写代码管理这些资源的释放
- Activity、Fragment不应对关注数据的处理,而只应该关注将数据显示到界面上
- 从Activity和Fragment中分离出视图所需数据的管理责任,让代码逻辑解耦,让开发者管理业务和界面的关系更高效
- 在Activity、Fragment之间共享数据,无需持有引用,无需相互调用,无需担心被其他Fragment生命周期影响
生命周期#
activity销毁之前,会调用ViewModel的onCleared方法
1 | 2022-06-24 10:28:35.993 32581-32581/com.shaunsheep.mvvm E/MyRxjavaViewModel: onCleared: |
问:如何触发的onCLeared?
创建ViewModel时,传递给 ViewModelProvider
的Lifecycle
。
在ComponentActivity的构造函数里,里有如下代码
1 | getLifecycle().addObserver(new LifecycleEventObserver() { |
问:ViewModel结束时间,在Activity与Fragment的有什么区别?
答:对于 activity,是在 activity#ondestory时;对于 fragment,是在 fragment#detach
问:ViewModel是如何持续存在的,换句话说为什么持续存在?
初始化ViewModel的方式决定ViewModel会持续存在
初始化方式:ViewModelProviders.of(this).get(MyRxjavaViewModel.class);
get方法调用了:mViewModelStore.get(key)
get方法调用了:mMap.get(key)
调用了HashMap,根据key获取对应的ViewModel
答案是:hashmap不被销毁、不清空,ViewModel将持续存在,初始化ViewModel的时候,传入同一个class文件,将返回hashMap中的缓存
问:如何在Recycleview使用viewmodel?
问:如何在多个组件共享viewmodel?
问:如何在一个Activity的多个Fragment之间共享viewmodel
答:在fragment实例化的时候,创建viewmodel传入绑定的Activity
1 | model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class); |
基本类型#
名称 | 允许持有context |
---|---|
ViewModel | 否 |
AndroidViewModel | 是 |
基本用法#
创建#
继承ViewModel抽象类
1 | public class SharedViewModel extends ViewModel { |
初始化#
1 | // 方式1 静态方法,这个方法在androidx.lifecycle:lifecycle-extensions 扩展包里 |
发送数据#
其实是调用livedata发送数据
post方式:在子线程或主线程都可以,最终会在主线程执行
1 | public class SharedViewModel extends ViewModel { |
set方式:只能在主线程调用
1 | public void select(String item) { |
监听数据变化observer#
其实也是监听livedata的obser方法
1 | public class SharedViewModel extends ViewModel { |
在需要监听的地方
1 | model.getSelected().observe(this, new Observer<String>() { |
多个Fragment共享数据#
viewmodel核心代码就一句:初始化viewmodel的时候传入绑定者Activity
1 | model = new ViewModelProvider(this).get(SharedViewModel.class); |
这样做的优势:
- Activity无需操作数据,无需增加额外代码,无需对Fragment的通信有任何干涉
- Fragment之间解耦,无需互相引用,互相了解,任一销毁不影响其他Fragment
Activity切换Fragment
1 | tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { |
Fragment1初始化数据
1 | public class LeftFragment extends Fragment { |
Fragment2初始化数据
1 | public class RightFragment extends Fragment { |
保存界面状态#
根据官网资料,ViewModel仅在Activity销毁重建时可以恢复数据,换句话说,App进程被framwork杀死后Activity重建,viewmodel也会被重建,详情看官网的这个表格
ViewModel SavedStateHandle |
ViewModel | 已保存实例状态 Bundle |
持久性存储空间 | |
---|---|---|---|---|
存储位置 | 在独立进程中 | 在内存中 | 已序列化到磁盘 | 在磁盘或网络上 |
在配置更改后继续存在 | 是 | 是 | 是 | 是 |
在系统发起的进程终止后继续存在 | 是 | 否 | 是 | 是 |
在用户完成 Activity 关闭/onFinish() 后继续存在 | 否 | 否 | 否 | 是 |
数据限制 | 支持复杂对象,限制目前不清楚 | 支持复杂对象,但是空间受可用内存的限制 | 仅适用于基元类型和简单的小对象,例如字符串 | 仅受限于磁盘空间或从网络资源检索的成本/时间 |
读取/写入时间 | 快(仅限内存访问) | 快(仅限内存访问) | 慢(需要序列化/反序列化和磁盘访问) | 慢(需要磁盘访问或网络事务) |
典型的做法如下:
Activity#onCreate中,通常会获取数据,显示到界面上,针对进入onCreate的情况,分为以下3种case
- Activity从未被初始化过
- Activity被初始化过,但处于后台时,App进程存活,Activity被杀死了,如配置更改的时候会发生这个情况
- Activity被初始化过,但处于后台时,App进程和Activity都被杀死了,如系统资源不足的时候会发生这个情况
针对于case1:我们会调用viewmodel#geNetworkNumber
方法,从网络异步获取一个数值,并且这个数值缓存于viewmodel的某个livedata实例中
对于case2:App进程不销毁的情况下,viewmodel会持久存在,Activity重新创建的时候,viewmodel中的缓存的值也一直存在,我们直接拿来用即可,使用方式为:处于onSaveInstanceState回调时,将viewmodel缓存的值存储到Bundle中;处于onRestoreInstanceState时,从Bundle取出并设置到viewmodel上
对于case3:为了恢复App进程被framework杀死前的数据,我们必须使用Bundle跨进程,我们可以这样做:
第一步:onSaveInstanceState,将viewmodel缓存的值存储到Bundle中;
第二步:以下俩方法三选一(实现任意一个皆可以),只要能从Bundle中取值并调用viewmodel相应的数据更新setNumber方法即可
onCreate中判断Bundle是否为空
- 为空:表明是首次创建
- 不为空:表明被销毁过,从Bundle取值设置到viewmodel中
onRestoreInstanceState中从Bundle取出并设置到viewmodel上
SavedStateHandle
- viewmodel的构造函数传入SavedStateHandle
- viewmodel通过调用SavedStateHandle#getLiveData,使用这个livedata管理跨进程存储的数据。这份数据会在进程被系统终止后继续保存
Activity的具体用法:注意观察onCreate、onSaveInstanceState、onRestoreInstanceState的判断
1 |
|
这是 SavedStateHandleViewModel的具体用法
写一个viewmodel实现类,构造函数接收SavedStateHandle
1 | public class SavedStateHandleViewModel extends ViewModel { |
初始化viewmodel,默认 ViewModel 工厂会向您的 ViewModel 提供相应的 SavedStateHandle
1 | savedStateHandleViewModel = new ViewModelProvider(this).get(SavedStateHandleViewModel.class); |
如果你想问支持存储哪些类型,可以看官方文档ViewModel 的已保存状态模块支持的类型的一节,里面提到直接支持的可序列化数据类型(像使用Bundle那样),间接支持的非序列化数据类型(实现接口SavedStateRegistry.SavedStateProvider
)
常见问题#
问题1:创建时机不对
1 | Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call. |
不能在Activity、Fragment的oncreate方法之前创建viewmodel,如全局创建
问题2:配置发生变化,viewmodel会销毁重建吗
答:不会,rotated前后,NormalViewModel的地址空间一直是cc67d9d
1 | com.shaunsheep.mvvm E/ViewModelSampleActivity: onCreate: null |
问题3:能在ViewModel持有COntext、Activity、Fragment吗
答:普通的ViewModel不可以;AndroidViewModel可以
疑问#
- 配置更改期间会自动保留VIewModel对象,为什么不会自动保留Activity的成员变量?会自动保留Activity的静态变量吗?