1. 目录
- 1–目录
- 2–前言(MVVM演变路程)
- 3–目的
- 4–ViewModel为什么不会内存泄漏?
- 5–Activity屏幕旋,为什么ViewModel没有被重新创建还是使用的是之前的?
- 6–Activity与Fragment之间数据如何共享的
- 7–GlobeScope,viewModelScope,lifecycleScope的相关问题
前言(MVVM演变路程)
以前有说过MVC,MVP,MVVM之间的区别,这里就不再说了。
MVC架构,最主要的就是循环引用造成的逻辑复杂,维护困难。
为了解决MVC存在的问题,推出了MVP架构,MVP将View和Model完全隔离开了,直接P层从M层获取数据,从而更新V层,都是单向操作,逻辑就很明确。但是,这样重担就全部在P层了,所有的逻辑都在P层,造成接口过多,维护起来也困难。
为了解决MVP存在的问题,演变出了MVVM。
目的
- 为了解决MVP的痛点,推出用ViewModel替代Presenter,vm的生命周期与activity(fragment)同步,不用手动维护P层的生命周期。
- 使用LiveData与View通信,避免使用接口。结合DataBinding的双向绑定,不用手动更新UI了
ViewModel为什么不会内存泄漏?
内存泄漏都是生命周期不同步造成的。上文中说到了,vm的生命周期与v相同,自然就不会内存泄漏了。
辣么,vm是怎么做到与v层生命周期同步的呢?其实,说到底就应该猜得到,为什么生命周期同步,肯定是v层销毁的时候,vm也被销毁。那么,我们就看一下activity销毁的时候,做了些什么?
我们先来说一下这个vm的简单用法:
1 | //第一个参数:ViewModelStoreOwner |
用ViewModel,我们知道必须要用ComponentActivity,普通的activity不行。为什么呢?我们来看一下:
看到这个activity的实现了LifecycleOwner, ViewModelStoreOwner,这两个接口一个是监听生命周期的变化,一个是保存ViewModel的。再看看我们获取vm的时候传的参数。这就对应上了
所以,并不是非要用ComponentActivity,只要是实现了上面那两个接口的activity就行了。
言归正传,为什么生命个周期同步?我们找一下这个activity的onDestoyr方法,搜了一下,它没有复写onDstory方法,这个activity的代码也并不多,两百行不到,所以,我找了一下,我看到了如下代码:
它这里通过Lifecycle监听了onDestory事件,当activity销毁的时候,并且,后面那个判断满足的情况下,这里就会走。
1 | //这一行代码是干什么的呢? |
辣么,这一行代码到底是干什么的呢?我们先来看看这个ViewModelStore,这个类:
1 | public class ViewModelStore { |
我们再回过头来看一下,上面那一行代码是干什么?getViewModelStore() 方法
方法图.png)
过程咱们先不说,咱先说这个方法值,就是为了获取ViewModelStore,所以上面那一行代码的意思就是:获取ViewModelStore之后,调用它的clear方法,循环销毁ViewModel
这就是为什么ViewModel与Activity的生命周期同步:因为,在activity销毁的同时,系统会帮我们清空ViewModel
Activity屏幕旋,为什么ViewModel没有被重新创建还是使用的是之前的?
讲道理activity屏幕旋转的时候,生命周期都更新了,也走了onDestory了,为什么View Model没有重新创建呢?
既然,没有重新创建,那就肯定是没有销毁了,为什么呢?我们再来看看这个销毁代码:
1 | this.getLifecycle().addObserver(new LifecycleEventObserver() { |
我们再来看看这个isChangingConfigurations()是返回的什么、
这里就是返回了一个boolean值,这里,我们可以试着翻译一下上面的注释:
1 | //检查是否这个activity是否处于正在销毁的为过程中,为了重新创建带有一个新的配置 |
看到这里,结合我们的实际情况,屏幕旋转,可不就是新的配置吗?所以,这里返回的是true。我们再看那个条件,取反了。结合前面的 &&,那么,屏幕旋转的时候,这里就是false,所以,这里进不去。所以ViewModel就不会清空。
上面说了,ViewModel没有清空,那为什么传的activity的对象都不是同一个,获取到的ViewMode却是同一个呢?我们来看一下获取的位置:
1 | //新建ViewModelProvider,调用了它两个参数的构造方法,获取ViewModelProvider,然后通过它的get方法获取ViewModel |
重复一下免得忘记了,ViewModelStore里面有一个HashMap是用来存和取ViewModel的,我们前面把这个类都贴出来了,忘记了可以再去回顾一下。
继续,这里通过getLastNonConfigurationInstance()获取mViewModelStore,方法如下:
这里为什么要截图呢?就是为了看注释,因为,这里啥逻辑也没有
1 | ... |
到这里也没了,没法向下追溯了,都是系统调用的。我们追溯到
1 | public Object getLastNonConfigurationInstance() { |
到这里,我也不知道该说啥了,都是系统赋值,我们这里就认为:
1 | public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull ViewModelProvider.Factory factory) { |
ViewModelStore是相同的,那么,我们再来看一下获取ViewModel
1 | @NonNull |
所以,这里就是先存,然后再取,所以,获取到的是同一个对象。
重要的事情说三遍:
这里存的key:系统写死的一个字符串+你的ViewModel类路径
这里存的key:系统写死的一个字符串+你的ViewModel类路径
这里存的key:系统写死的一个字符串+你的ViewModel类路径
很多人可能就以为,这里是+你的Activity的类路径,你仔细考虑一下也会发现不对,因为,你一个activity可能有多个ViewModel,那如果是activity的类路径,这里怎么返回?所以,这里是+你的ViewModel的类路径。
所以,即使,你重新创建activity,你这个viewModel获取到的都是同一个。
整体就是:即时activity旋转,你获取到的是同一个mViewModelStore对象(系统调用的),然后,根据key(系统的字符串+vm的类路径)获取到ViewModel对象。
Activity与Fragment之间数据如何共享
上面说到了这个ViewModel的获取流程,这个问题就应该比较容易回答了,想办法让Fragment获取到同一个ViewModel就可以了。我们一般activity的一级页面都是1个activity+多个fragment的模式,那么,fragment怎么获取ViewModel呢?
1 | //把我们之前第一个参数的位置,传递getActivity()即可。 |
这里有个位置需要注意,fragment的replace方法,移除view的时候,fragment并没有被销毁,ViewModel就没有被销毁,页面销毁了,ViewModel更新页面的时候,就可能会存在空指针的问题。所以,更新界面推荐用LiveData,LiveData在页面可见的时候更新,能避免这个问题
GlobeScope,viewModelScope,lifecycleScope的相关问题
- GlobeScope:生命周期与app同步,随着kotlin的更新,已经不推荐使用这个了,除非你能保证,你这里做的操作不会存在内存泄漏的问题。
- viewModelScope:在ViewModel里面使用协程,推荐使用viewModelScope,它会在ViewModel调用clear方法的时候取消。
- lifecycleScope:在activity或者fragment里面使用协程的时候,用lifecycleScope,它在Lifecycle执行onDestory的时候取消