Android Jetpack ViewModel解析

前言

最近几天把Jetpack中的3剑客Lifecycle+ViewModel+LiveData的使用和原理学习了一遍,这3者也是构建MVVM模式的核心。这个系列的工具,建议大家在官网学习:https://developer.android.google.cn/jetpack

目前网上对这3者使用和源码解析的文章也沉淀了不少,大家可以自行查阅。本文换个方向来跟大家聊聊我所理解的关于ViewModel的一些内容。

系列文章

Android Jetpack ViewModel解析
Android Jetpack LiveData解析
Android Jetpack DataBinding原理浅析(简版)

看看官方对Viewmodel作用的解释

官方的Viewmodel用法例子

官方的解释说明与案例还是很清晰的,所以就不做过多解释了。说一下我在学习过程中的一些疑问吧。

1、在官网和Google的github demo中都建议在mvvm模式中的vm层去继承ViewModel,那为什么一定要继承ViewModel呢?我直接在VM中定义LiveData数据不行吗?是否继承ViewModel的区别在哪?

下面通过一个例子来讲解,这里为了突出核心要点就用近乎伪代码的形式来表述。首先在mvvm的VM层去获取网络数据,实际上是model层执行的,然后当获取数据成功后调用LiveData对象的setValue()方法通知Activity的观察者去刷新UI。

public class NewsViewModel extends ViewModel {
    private NewsModel newsModel;
    //adapter数据源
    private List<ItemNews> mList = new ArrayList<>();
    public MutableLiveData<List<ItemNews>> itemNewsLiveData = new MutableLiveData<>();

    public NewsViewModel() {
        newsModel = new NewsModel();
    }

    public void getNews() {
      mList =   newsModel.getNews();
      //通知活跃的观察者更新数据
      itemNewsLiveData.setValue(mList);
    }
}

Activity里面的观察者

 newsViewModel.itemNewsLiveData.observe(this, new Observer<List<ItemNews>>() {
            @Override
            public void onChanged(@Nullable List<ItemNews> itemNews) {
             newsAdapter.updateAll(itemNews);
            }
        });

这里按照官方的规范,只是在View层持有VM的引用,而VM不持有View的引用。到这一步我就有一个疑问,貌似不继承ViewModel也可以完成VM与View的交互,那引入它到底有什么好处呢?

再回顾下官方的说法:

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel类让数据可在发生屏幕旋转等配置更改后继续存在。

上面说到ViewModel管理数据是和生命周期相关的,且能持久存储数据。那下面就简单解析一下是如何与生命周期相关的:

官方ViewModel生命周期

这里先说结论,源码验证待会儿再说。

现在可以看出,好处1:
因为ViewModel的生命周期是比Activity还要长,所以ViewModel可以持久保存UI数据,具体来说是Activity因为配置更改或者被系统意外回收的时候,会自动保存数据,然后在Activity重建的时候就可以继续使用销毁之前保存的数据。

好处2:
当使用ViewModel的 Activity 正常退出时,内部会调用ViewModel对象的 onCleared()方法,以便它可以清理资源。首先内部会释放ViewModel数据,同时你也可以在vm层重写这个方法去释放资源,取消网络等。

好处3:
在 Fragment 之间共享数据。这个比较简单,只要2个Fragment获取
ViewModel对象时传入的LifecycleOwner一致就可以共享ViewModel中定义的数据。

现在回头看刚才的例子,当配置改变导致Activity重建的时候,如果没有使用ViewModel存储数据,那么就会重新请求数据重绘页面;如果用ViewModel的话,就能用页面销毁之前保存的数据去直接显示视图。

2、在MVVM中用了ViewModel+LiveData之后还有必要在Activity/Fragment的onDestory()方法中手动取消耗时任务(网络请求)吗?

引入了ViewModelLiveData之后,可以实现vmview的解耦,只是view引用vm,而vm是不持有view的引用的。在activity退出之后即是还有网络在继续也不会引发内存泄漏和空指针异常,所以不在ondestory取消网络也是可以的。如果你还是想取消,可以重写onCleared()方法去做。

原理解析

ViewModel源码真的很简单,就几个类,实际上实际上真正的业务处理核心就只有ViewModelProvider,其他都是辅助。

  • ViewModel类是一个抽象方法,只有一个空方法onCleared(),它会在onDestory的时候被调用,所以你可以重写这个方法,在里面做些释放资源、取消网络的操作。
  • AndroidViewModel是继承ViewModel的,就多了一步,提供Application参数
  • ViewModelStore:和名字一样,就是存储ViewModel的,它里面定义了一个HashMap来存储ViewModel,key值是ViewModel全路径+一个默认的前缀。大概这样:key=android.arch.lifecycle.ViewModelProvider.DefaultKey:com.zx.mvvmdemo.vm.NewsViewModel
  • ViewModelStoreOwner:是一个接口,只有一个方法,是获取ViewModelStore对象的
    ViewModel核心类

通常定义ViewModel之后,通过如下方式获取ViewModel对象

newsViewModel = ViewModelProviders.of(this).get(NewsViewModel.class);

of传FragmentActivity或者Fragment对象。
这里主要分析2个问题:

  • 1、ViewModel是如何创建的?
  • 2、ViewModel是如何保存数据的?

这里不按照执行流程去阅读源码那样分析,因为这么详细的内容已经有人写了,我就讲一下关键的思路,细节的话,推荐一遍不错的:
Android ViewModel,再学不会你砍我

从入口来看,是外观模式,ViewModelProviders就是外观类,真正的业务处理是在ViewModelProvider注意前者多了一个s,核心就在ViewModelProviderget()方法

ViewModelProvider.get

逻辑大致如下:

首先从ViewModelStore的HashMap这个缓存中取取数据,如果有直接返回ViewModel,如果没有通过ViewModelProvider内部的工厂类去创建,ViewModel的创建具体来说是通过反射区做的,并且保存在缓存中,这样就获取到了ViewModel对象。

到这里,问题1就解决了。下面看看问题2,数据是如何保存的。
数据保存是在FragmentActivity中实现的。

因为配置变化导致Activity销毁重建以前的方案是保存数据在onSaveInstanceState,而还原在onRestoreInstanceState,但是这2个方法是有很大瑕疵的,于是在新的SDK中FragmentActivity内置了2个新的方法onRetainNonConfigurationInstancegetLastNonConfigurationInstance,更多细节可以参考:
https://www.cnblogs.com/dengxianzhi/articles/2248655.html

其中onRetainNonConfigurationInstance是在onStop() 和 onDestroy()之间被调用,它内部会保存ViewModel数据;而在onCreate的时候会调用getLastNonConfigurationInstance来恢复数据。

借用刚才文章的一张图来说明保存恢复数据流程
保存恢复数据流程

总结

1、让mvvm的vm去继承ViewModel,并在其内部定义、管理UI所需的数据,不仅可以让View和ViewModel解耦,同时其内部自动关联生命周期,可以减少在Activity的生命周期方法中写大量的样板代码。

2、ViewModel保存数据这个功能还是有点用的。一开始我觉得当屏幕旋转的时候你可以通过configChanges的设置来阻止它的重建,这样就不需要viewmodel保存数据了。但是其它的一些意外情况也是有可能导致Activity重建的,比如当前activity在填写一堆表单数据,中间打开了其他app,在后台被回收了,后面再进入的时候如果没有保存就得重填,如果你的UI数据放在ViewModel的话就会自动回复,这样看来,ViewModel的保存数据功能是不是还挺有用呢

3、利用ViewModel共享数据可以更简洁,减少很多不必要的接口。

4、友情提示:在创建ViewModel对象的时候不能手动去new一个,而是通过Provider的方式去获取,这样的话才能利用ViewModel的那些优势。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容