Android官方架构组件ViewModel_从前世今生到追本溯源(1)

同时,Google官方建议ViewModel尽量保证 纯的业务代码,不要持有任何View层(Activity或者Fragment)或Lifecycle的引用,这样保证了ViewModel内部代码的可测试性,避免因为Context等相关的引用导致测试代码的难以编写(比如,MVP中Presenter层代码的测试就需要额外成本,比如依赖注入或者Mock,以保证单元测试的进行)。

3.2 更便于保存数据

由系统响应用户交互或者重建组件,用户无法操控。当组件被销毁并重建后,原来组件相关的数据也会丢失——最简单的例子就是屏幕的旋转,如果数据类型比较简单,同时数据量也不大,可以通过onSaveInstanceState()存储数据,组件重建之后通过onCreate(),从中读取Bundle恢复数据。但如果是大量数据,不方便序列化及反序列化,则上述方法将不适用。

ViewModel的扩展类则会在这种情况下自动保留其数据,如果Activity被重新创建了,它会收到被之前相同ViewModel实例。当所属Activity终止后,框架调用ViewModelonCleared()方法释放对应资源:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这样看来,ViewModel是有一定的 作用域 的,它不会在指定的作用域内生成更多的实例,从而节省了更多关于 状态维护(数据的存储、序列化和反序列化)的代码。

ViewModel在对应的 作用域 内保持生命周期内的 局部单例,这就引发一个更好用的特性,那就是FragmentActivity等UI组件间的通信。

3.3 更方便UI组件之间的通信

一个Activity中的多个Fragment相互通讯是很常见的,如果ViewModel的实例化作用域为Activity的生命周期,则两个Fragment可以持有同一个ViewModel的实例,这也就意味着数据状态的共享:

public class AFragment extends Fragment {
private CommonViewModel model;
public void onActivityCreated() {
model = ViewModelProviders.of(getActivity()).get(CommonViewModel.class);
}
}

public class BFragment extends Fragment {
private CommonViewModel model;
public void onActivityCreated() {
model = ViewModelProviders.of(getActivity()).get(CommonViewModel.class);
}
}

上面两个Fragment getActivity()返回的是同一个宿主Activity,因此两个Fragment之间返回的是同一个ViewModel

我不知道正在阅读本文的您,有没有冒出这样一个想法:

ViewModel提供的这些特性,为什么感觉互相之间没有联系呢?

这就引发下面这个问题,那就是:

这些特性的本质是什么?

4. ViewModel:对状态的持有和维护

ViewModel层的根本职责,就是负责维护UI的状态,追根究底就是维护对应的数据——毕竟,无论是MVP还是MVVM,UI的展示就是对数据的渲染。

  • 1.定义了ViewModel的基类,并建议通过持有LiveData维护保存数据的状态;
  • 2.ViewModel不会随着Activity的屏幕旋转而销毁,减少了维护状态的代码成本(数据的存储和读取、序列化和反序列化);
  • 3.在对应的作用域内,保正只生产出对应的唯一实例,多个Fragment维护相同的数据状态,极大减少了UI组件之间的数据传递的代码成本。

现在我们对于ViewModel的职责和思想都有了一定的了解,按理说接下来我们应该阐述如何使用ViewModel了,但我想先等等,因为我觉得相比API的使用,掌握其本质的思想会让你在接下来的代码实践中如鱼得水

不,不是源码解析…

通过库提供的API接口作为开始,阅读其内部的源码,这是标准掌握代码内部原理的思路,这种方式的时间成本极高,即使有相关源码分析的博客进行引导,文章中大片大片的源码和注释也足以让人望而却步,于是我理所当然这么想

先学会怎么用,再抽空系统学习它的原理和思想吧…

发现没有,这和上学时候的学习方式竟然截然相反,甚至说本末倒置也不奇怪——任何一个物理或者数学公式,在使用它做题之前,对它背后的基础理论都应该是优先去系统性学习掌握的(比如,数学公式的学习一般都需要先通过一定方式推导和证明),这样我才能拿着这个知识点对课后的习题举一反三。这就好比,如果一个老师直接告诉你一个公式,然后啥都不说让你做题,这个老师一定是不合格的。

我也不是很喜欢大篇幅地复制源码,我准备换个角度,站在Google工程师的角度看看怎么样设计出一个ViewModel

站在更高的视角,设计ViewModel

现在我们是Google工程师,让我们再回顾一下ViewModel应起到的作用:

  • 1.规范化了ViewModel的基类;
  • 2.ViewModel不会随着Activity的屏幕旋转而销毁;
  • 3.在对应的作用域内,保正只生产出对应的唯一实例,保证UI组件间的通信。

1.设计基类

这个简直太简单了:

public abstract class ViewModel {

protected void onCleared() {
}
}

我们定义一个抽象的ViewModel基类,并定义一个onCleared()方法以便于释放对应的资源,接下来,开发者只需要让他的XXXViewModel继承这个抽象的ViewModel基类即可。

2.保证数据不随屏幕旋转而销毁

这是一个很神奇的功能,但它的实现方式却非常简单,我们先了解这样一个知识点:

setRetainInstance(boolean)Fragment中的一个方法。将这个方法设置为true就可以使当前FragmentActivity重建时存活下来

这似乎和我们的功能非常吻合,于是我们不禁这样想,可不可以让Activity持有这样一个不可见的Fragment(我们干脆叫他HolderFragment),并让这个HolderFragment调用setRetainInstance(boolean)方法并持有ViewModel——这样当Activity因为屏幕的旋转销毁并重建时,该Fragment存储的ViewModel自然不会被随之销毁回收了:

public class HolderFragment extends Fragment {

public HolderFragment() { setRetainInstance(true); }

private ViewModel mViewModel;
// getter、setter…
}

当然,考虑到一个复杂的UI组件可能会持有多个ViewModel,我们更应该让这个不可见的HolderFragment持有一个ViewModel的数组(或者Map)——我们干脆封装一个叫ViewModelStore的容器对象,用来承载和代理所有ViewModel的管理:

public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
// put(), get(), clear()…
}

public class HolderFragment extends Fragment {

public HolderFragment() { setRetainInstance(true); }

private ViewModelStore mViewModelStore = new ViewModelStore();
}

好了,接下来需要做的就是,在实例化ViewModel的时候:

1.当前Activity如果没有持有HolderFragment,就实例化并持有一个HolderFragment 2.Activity获取到HolderFragment,并让HolderFragmentViewModel存进HashMap中。

这样,具有生命周期的Activity在旋转屏幕销毁重建时,因为不可见的HolderFragment中的ViewModelStore容器持有了ViewModelViewModel和其内部的状态并没有被回收销毁。

这需要一个条件,在实例化ViewModel的时候,我们似乎还需要一个Activity的引用,这样才能保证 获取或者实例化内部的HolderFragment并将ViewModel进行存储

于是我们设计了这样一个的API,在ViewModel的实例化时,加入所需的Activity依赖:

CommonViewModel viewModel = ViewModelProviders.of(activity).get(CommonViewModel.class)

我们注入了Activity,因此HolderFragment的实例化就交给内部的代码执行:

HolderFragment holderFragmentFor(FragmentActivity activity) {
FragmentManager fm = activity.getSupportFragmentManager();
HolderFragment holder = findHolderFragment(fm);
if (holder != null) {
return holder;
}
holder = createHolderFragment(fm);
return holder;
}

这之后,因为我们传入了一个ViewModelClass对象,我们默认就可以通过反射的方式实例化对应的ViewModel,并交给HolderFragment中的ViewModelStore容器存起来:

public T get(Class modelClass) {
// 通过反射的方式实例化ViewModel,并存储进ViewModelStore
viewModel = modelClass.getConstructor(Application.class).newInstance(mApplication);
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}

3.在对应的作用域内,保正只生产出对应的唯一实例

如何保证在不同的Fragment中,通过以下代码生成同一个ViewModel的实例呢?

public class AFragment extends Fragment {
private CommonViewModel model;
public void onActivityCreated() {
model = ViewModelProviders.of(getActivity()).get(CommonViewModel.class);
}
}

public class BFragment extends Fragment {
private CommonViewModel model;
public void onActivityCreated() {
model = ViewModelProviders.of(getActivity()).get(CommonViewModel.class);
}
}

其实很简单,只需要在上一步实例化ViewModelget()方法中加一个判断就行了:

public T get(Class modelClass) {
// 先从ViewModelStore容器中去找是否存在ViewModel的实例
ViewModel viewModel = mViewModelStore.get(key);

// 若ViewModel已经存在,就直接返回
if (modelClass.isInstance(viewModel)) {
return (T) viewModel;
}

// 若不存在,再通过反射的方式实例化ViewModel,并存储进ViewModelStore
viewModel = modelClass.getConstructor(Application.class).newInstance(mApplication);
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}

现在,我们成功实现了预期的功能——事实上,上文中的代码正是ViewModel官方核心部分功能的源码,甚至默认ViewModel实例化的API也没有任何改变:

CommonViewModel viewModel = ViewModelProviders.of(activity).get(CommonViewModel.class);

当然,因为篇幅所限,我将源码进行了简单的删减,同时没有讲述构造方法中带参数的ViewModel的实例化方式,但对于目前已经掌握了设计思想原理的你,学习这些API的使用几乎不费吹灰之力。

总结与思考

ViewModel是一个设计非常精巧的组件,它功能并不复杂,相反,它简单的难以置信,你甚至只需要了解实例化ViewModel的API如何调用就行了。

同时,它的背后掺杂的思想和理念是值得去反复揣度的。比如,如何保证对状态的规范化管理?如何将纯粹的业务代码通过良好的设计下沉到ViewModel中?对于非常复杂的界面,如何将各种各样的功能抽象为数据状态进行解耦和复用?随着MVVM开发的深入化,这些问题都会一个个浮出水面,这时候ViewModel组件良好的设计和这些不起眼的小特性就随时有可能成为璀璨夺目的闪光点,帮你攻城拔寨。

--------------------------广告分割线------------------------------

系列文章

争取打造 Android Jetpack 讲解的最好的博客系列

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结

开发是面向对象。我们找工作应该更多是面向面试。哪怕进大厂真的只是去宁螺丝,但你要进去得先学会面试的时候造飞机不是么?

作者13年java转Android开发,在小厂待过,也去过华为,OPPO等,去年四月份进了阿里一直到现在。等大厂待过也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

960页全网最全Android开发笔记

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

jN3bhQ-1712218363849)]

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 13
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值