Android——MVVM之ViewModel

个人博客:haichenyi.com。感谢关注

1. 目录

前言(MVVM演变路程)

  以前有说过MVC,MVP,MVVM之间的区别,这里就不再说了。

  MVC架构,最主要的就是循环引用造成的逻辑复杂,维护困难。

  为了解决MVC存在的问题,推出了MVP架构,MVP将View和Model完全隔离开了,直接P层从M层获取数据,从而更新V层,都是单向操作,逻辑就很明确。但是,这样重担就全部在P层了,所有的逻辑都在P层,造成接口过多,维护起来也困难。

  为了解决MVP存在的问题,演变出了MVVM。

目的

  1. 为了解决MVP的痛点,推出用ViewModel替代Presenter,vm的生命周期与activity(fragment)同步,不用手动维护P层的生命周期。
  2. 使用LiveData与View通信,避免使用接口。结合DataBinding的双向绑定,不用手动更新UI了

ViewModel为什么不会内存泄漏?

  内存泄漏都是生命周期不同步造成的。上文中说到了,vm的生命周期与v相同,自然就不会内存泄漏了。

  辣么,vm是怎么做到与v层生命周期同步的呢?其实,说到底就应该猜得到,为什么生命周期同步,肯定是v层销毁的时候,vm也被销毁。那么,我们就看一下activity销毁的时候,做了些什么?

我们先来说一下这个vm的简单用法:

//第一个参数:ViewModelStoreOwner
//第二个参数:ViewModelProvider.Factory
new ViewModelProvider(this,new ViewModelProvider.NewInstanceFactory()).get(HttpViewModel.class);

  用ViewModel,我们知道必须要用ComponentActivity,普通的activity不行。为什么呢?我们来看一下:

ComponentActivity图

  看到这个activity的实现了LifecycleOwner, ViewModelStoreOwner,这两个接口一个是监听生命周期的变化,一个是保存ViewModel的。再看看我们获取vm的时候传的参数。这就对应上了

  所以,并不是非要用ComponentActivity,只要是实现了上面那两个接口的activity就行了。

言归正传,为什么生命个周期同步?我们找一下这个activity的onDestoyr方法,搜了一下,它没有复写onDstory方法,这个activity的代码也并不多,两百行不到,所以,我找了一下,我看到了如下代码:

ComponentActivity的onDstory图

  它这里通过Lifecycle监听了onDestory事件,当activity销毁的时候,并且,后面那个判断满足的情况下,这里就会走。

//这一行代码是干什么的呢?
ComponentActivity.this.getViewModelStore().clear();

  辣么,这一行代码到底是干什么的呢?我们先来看看这个ViewModelStore,这个类:

public class ViewModelStore {
    //HashMap,以String为key,ViewModel为值存放ViewModel
    private final HashMap<String, ViewModel> mMap = new HashMap();

    public ViewModelStore() {
    }
    
    //存ViewModel
    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = (ViewModel)this.mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }

    }
    
    //获取ViewModel
    final ViewModel get(String key) {
        return (ViewModel)this.mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet(this.mMap.keySet());
    }
    
    //看代码,就是一个迭代器,循环调用每个ViewModel的clear方法,最后,清空迭代器
    //看到这里,就应该联想到了吧?
    public final void clear() {
        Iterator var1 = this.mMap.values().iterator();

        while(var1.hasNext()) {
            ViewModel vm = (ViewModel)var1.next();
            vm.clear();
        }

        this.mMap.clear();
    }
}

  我们再回过头来看一下,上面那一行代码是干什么?getViewModelStore() 方法

getViewModelStore()方法图

  过程咱们先不说,咱先说这个方法值,就是为了获取ViewModelStore,所以上面那一行代码的意思就是:获取ViewModelStore之后,调用它的clear方法,循环销毁ViewModel

这就是为什么ViewModel与Activity的生命周期同步:因为,在activity销毁的同时,系统会帮我们清空ViewModel

Activity屏幕旋,为什么ViewModel没有被重新创建还是使用的是之前的?

  讲道理activity屏幕旋转的时候,生命周期都更新了,也走了onDestory了,为什么View Model没有重新创建呢?

  既然,没有重新创建,那就肯定是没有销毁了,为什么呢?我们再来看看这个销毁代码:

this.getLifecycle().addObserver(new LifecycleEventObserver() {
    public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Event event) {
        //看这里销毁的条件,并不是只有一个,而是有两个,
        //销毁事件满足了,后面那个条件又是什么呢?
        if (event == Event.ON_DESTROY && !ComponentActivity.this.isChangingConfigurations()) {
            ComponentActivity.this.getViewModelStore().clear();
        }
    }
});

我们再来看看这个isChangingConfigurations()是返回的什么、

isChangingConfigurations()方法图

这里就是返回了一个boolean值,这里,我们可以试着翻译一下上面的注释:

//检查是否这个activity是否处于正在销毁的为过程中,为了重新创建带有一个新的配置
//有没有一点想法?
//意思就是说,这个activity是不是因为一个新的配置需要重新创建而被销毁了。
//(我不管你们认不认,反正,我觉得可以这样理解)
Check to see whether this activity is in the process of being destroyed in order to be recreated with a new configuration. 
...

//看这个return的注释
//如果,这个activity是需要根据新的配置重新创建而被销毁的话,就返回true,否则,就返回false。
Returns:
If the activity is being torn down in order to be recreated with a new configuration, returns true; else returns false.

public boolean isChangingConfigurations() {
    return mChangingConfigurations;
}

  看到这里,结合我们的实际情况,屏幕旋转,可不就是新的配置吗?所以,这里返回的是true。我们再看那个条件,取反了。结合前面的 &&,那么,屏幕旋转的时候,这里就是false,所以,这里进不去。所以ViewModel就不会清空。

  上面说了,ViewModel没有清空,那为什么传的activity的对象都不是同一个,获取到的ViewMode却是同一个呢?我们来看一下获取的位置:

//新建ViewModelProvider,调用了它两个参数的构造方法,获取ViewModelProvider,然后通过它的get方法获取ViewModel
new ViewModelProvider(this,new ViewModelProvider.NewInstanceFactory()).get(HttpViewModel.class);

//我们一层一层的追溯
public class ViewModelProvider {
    //默认的key值,这里后面存ViewModel有用到
    private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey";
    private final ViewModelProvider.Factory mFactory;
    private final ViewModelStore mViewModelStore;

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull ViewModelProvider.Factory factory) {
        //我们再来看看ViewModelStoreOwner的getViewModelStore是怎么实现的
        this(owner.getViewModelStore(), factory);
    }
    //上面的构造方法,调用的就是这个构造方法
    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull ViewModelProvider.Factory factory) {
        this.mFactory = factory;
        this.mViewModelStore = store;
    }
    ...
}

//看到这里就大概能够猜到,这里这么多判断空,肯定是复用了呀。
@NonNull
public ViewModelStore getViewModelStore() {
    //判断application有没有,没有就直接抛异常了
    if (this.getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
    } else {
        //mViewModelStore为空就重新赋值
        if (this.mViewModelStore == null) {
            //这里是先获取了上一次配置的NonConfigurationInstances对象,它不为空,就把它的viewModelStore变量,赋值到这里
            ComponentActivity.NonConfigurationInstances nc = (ComponentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
            if (nc != null) {
                this.mViewModelStore = nc.viewModelStore;
            }
            //到这里还是空,说明没有获取到,就重新new一个
            if (this.mViewModelStore == null) {
                this.mViewModelStore = new ViewModelStore();
            }
        }

        return this.mViewModelStore;
    }
}

  重复一下免得忘记了,ViewModelStore里面有一个HashMap是用来存和取ViewModel的,我们前面把这个类都贴出来了,忘记了可以再去回顾一下。

  继续,这里通过getLastNonConfigurationInstance()获取mViewModelStore,方法如下:

getLastNonConfigurationInstance()方法图

  这里为什么要截图呢?就是为了看注释,因为,这里啥逻辑也没有

...
//返回先前的对象,通过onRetainNonConfigurationInstance()方法
Returns:
the object previously returned by onRetainNonConfigurationInstance()


//我们再来看这个方法,直接返回了null,我们再看这个注释
public Object onRetainNonConfigurationInstance() {
    return null;
}

//先直译:
//被系统调用,作为activity销毁的一部分,由于配置发生了改变
//当它知道一个新对象会被立马创建为了这个新配置
//就是说:当activity需要被立马被重新创建,因为配置发生变化被销毁的时候,这个方法会被系统调用
Called by the system, as part of destroying an activity due to a configuration change, when it is known that a new instance will immediately be created for the new configuration. 

//你能返回任意对象,你想要的在这里包括activity对象本身,
//稍后被恢复通过调用getLastNonConfigurationInstance()方法在新的activity对象里面
//就是说:activity前面不是说重新创建了吗?
//当在新的activity里面调用getLastNonConfigurationInstance方法的时候,能返回你想要的任意对象包括activity本身
You can return any object you like here, including the activity instance itself, which can later be retrieved by calling getLastNonConfigurationInstance() in the new activity instance.

  到这里也没了,没法向下追溯了,都是系统调用的。我们追溯到

public Object getLastNonConfigurationInstance() {
    //mLastNonConfigurationInstances这个变量赋值的位置是attach方法,这个方法也是系统调用的。
    return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
}

  到这里,我也不知道该说啥了,都是系统赋值,我们这里就认为:

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull ViewModelProvider.Factory factory) {
        //在ViewModelProvider重新创建的时候,owner.getViewModelStore()获取到的ViewModelStore还是原先的
        //因为,这里的获取最终都是系统调用赋值的,也不知道最开始的值是不是相同的。就认为是相同的
        this(owner.getViewModelStore(), factory);
    }

ViewModelStore是相同的,那么,我们再来看一下获取ViewModel

@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    //这里获取了类的getCanonicalName值
    String canonicalName = modelClass.getCanonicalName();
    //判断是否为空
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    } else {
        //看这里字符串眼熟吗?上面说过了那个DEFAULT_KEY变量,然后加上这个类的路径名
        return this.get("androidx.lifecycle.ViewModelProvider.DefaultKey:" + canonicalName, modelClass);
    }
}

@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    //通过这个key先获取ViewModel
    ViewModel viewModel = this.mViewModelStore.get(key);
    //判断是不是这个类的对象,怎么实现的,我不知道,是native方法
    if (modelClass.isInstance(viewModel)) {
        return viewModel;
    } else {
        if (viewModel != null) {
        }
        
        //这个mFactory是我们创建的时候传的单例对象,这都不是重点
        if (this.mFactory instanceof ViewModelProvider.KeyedFactory) {
            viewModel = ((ViewModelProvider.KeyedFactory)((ViewModelProvider.KeyedFactory)this.mFactory)).create(key, modelClass);
        } else {
            viewModel = this.mFactory.create(modelClass);
        }
        //重点就是这里,它会先把这个对象存到ViewModelStore里面,然后,再返回这个对象。第一行,就是先通过这个对象先获取。
        this.mViewModelStore.put(key, viewModel);
        return viewModel;
    }
}

所以,这里就是先存,然后再取,所以,获取到的是同一个对象。

重要的事情说三遍:

这里存的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呢?

//把我们之前第一个参数的位置,传递getActivity()即可。
new ViewModelProvider(getActivity(),new ViewModelProvider.NewInstanceFactory()).get(HttpViewModel.class);

  这里有个位置需要注意,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的时候取消
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海晨忆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值