7c51c7f0d34f56b7527d89d369107993
重学安卓:有了 Jetpack ViewModel . . . 真的可以为所欲为!

前言

很高兴见到你!

上一期,我们借着现实中对音乐播放器的开发,来帮助大家体会为何存在 LiveData 这样的设计 —— 它究竟是在怎样的背景下、为了解决什么样的问题而诞生。

相信阅读过上一期的朋友,对 LiveData 的存在缘由、目标使命、职责边界 的印象已经跑赢了 90% 的 Android 开发者。

并且,就算将来不用 LiveData,也能因 LiveData 树立起的 通过 “唯一可信源” 完成状态分发 的理念,而减少 90% 不可预期的错误,从而让开发工作顺风顺水、畅快无比。😉

被严重低估的 Jetpack ViewModel

提到 ViewModel,大部分人都会觉得 “没啥可说的”。或许他们对 ViewModel 的认知,还停留在 MVVM - Clean 的时代,或者干脆误以为是 Presenter 般的存在。

这使得 ViewModel 的价值 就没有真正地 被重视和利用起来。

Jetpack ViewModel 果真是某些人所说的,是 MVP Presenter 的阉割版,或作为 Clean ViewModel 的存在吗?

答案是否定的。

作为在 面向成熟的、标准化、规范化的架构设计 的背景下诞生的 Jetpack ViewModel,绝不可与 Presenter 或是 Clean ViewModel 同日而语。

Jetpack ViewModel 在实现状态管理框架的 单向依赖 理念中,有着不可替代的作用。

所以长话短说,让我们来一起领略一下,在 Jetpack ViewModel(下文皆以 ViewModel 来指代)问世前和问世后,软件开发发生了怎样的剧变。😉

文章目录一览

  • 前言
  • 被严重低估的 Jetpack ViewModel
  • ViewModel 的目标只有三个
  • ViewModel 问世前的混沌世界
  • ViewModel 为何能解决这三个问题?
  • 引入 ViewModel 后的世界
  • 需要明确的 3 个小细节
  • 综上

ViewModel 的目标只有三个

ViewModel 最早是在 2017 年的 Google I/O 大会上被提出,同时间问世的还有 LifeCycler 和 LiveData。

在谈论 ViewModel 时常常避不开 LiveData。LiveData 就像 ViewModel 的左臂右膀,支撑 ViewModel 作为中间层 对状态管理所尽的“承上启下”的职责。

所以尽管本文的目标是介绍 ViewModel 存在缘由、设计依据、职责边界,本着介绍相互间关系时的需要,还是会顺带展示 LiveData 的身影。

ViewModel 的目标只有三个:

1.让状态管理独立于视图控制器,从而做到重建状态的分治、状态在多页面的共享,以及跨页面通信。

2.为状态设置作用域,使状态的共享做到作用域可控。

3.实现单向依赖,避免内存泄漏。

如果光是阅读了以上三点,你还是不太理解的话,那接下来我就分别介绍 99% 网文都不曾介绍的真实状况,来方便你迅速建立起感性的认识。😉

ViewModel 问世前的混沌世界

例如我们开发了一款单 Activity 的地图应用。应用的需求是:

1.地图作为背景供全局查看和操作。

2.当前页面皆以 Fragment 的形式展示和管理。

 2.1.其中 ListFragment(列表页)持有一级图形工具的单例 ListGraphicManager,可以 对地图上的图斑进行选中 和 跳转到详情页。

 2.2.List 中的每个 item 可跳转到 DetailFragment(详情页),详情页持有二级图形工具的单例 DetailGraphicManager,可以对地图上的图斑进行选中和编辑轮廓。并且在退出详情页时重置二级图形工具中的状态数据。

值得注意的一点是,对图斑的操作导致的图斑的变化,需要及时反映在 Fragment 的展示中。因而此前不仅是 Fragment 去引用 GraphicManager,GraphicManager 也反过来持有了 Fragment 的引用。

这造成了什么问题呢?

首先,由于图形工具的生命周期比 Fragment 长,当 Fragment destroy 时,因被图形工具持有,而可能造成内存泄漏。

并且,列表页和详情页之间如果要通信,只能另外编写接口,通过 Activity 来中转,这样项目中便产生了大量的接口。

如果不用接口,而是用 EventBus 的话,就容易导致上一节我们提到的,因 缺失 唯一可信源理念的 约束 造成状态分发的滥用,而指数级地增长不可预期的错误。

再者,如果页面彼此间不能共享数据,那么就需要编写大量的重复代码,例如 ListPresenter 和 DetailPresenter 包含某些同样的请求和逻辑,却因隶属于不同的页面,而不得不将同样的参数注入两份,并额外增加了状态管理的负担(例如页面退出时,你需要手动地一个个将状态对象置空,不然有内存泄漏的风险)。

最后,在屏幕旋转并发生重建后,Presenter 因直接隶属于 Activity,而同样被销毁,起不到存储大数据供视图控制器恢复状态的作用,状态恢复完全靠视图控制器的重建机制支撑着。

而该重建机制的设计目标本是支持轻量级的状态的存储与恢复,重量级的势必影响效率,而可能发生主线程阻塞 从而造成旋屏时的卡顿。

ViewModel 为何能解决这三大问题?

在 ViewModel 问世前,我自主设计过 现如今在我 GitHub 主页展示的 VIABUS 架构的雏形。

VIABUS 的设计目标之一,就是让 页面 和 业务 完全分离,使得 业务 能够完全独立于特定页面的生命周期,单独地存活和工作。并且业务能够为多个页面共享,无需每个页面都单独创建一个业务实例。

VIABUS 业务的职责更像是 Jetpack 中的 DataRepository,负责后端逻辑,因而 VIABUS 的缺陷是少了作为中间层的状态管理。

与 VIABUS 表面看上去有点类似,实际上又有所不同的是,ViewModel 的职责是专用于状态管理。

例如在我们开发的单 Activity 软件中,Activity 的生命周期是视图控制器中最长的,所有的 Fragment 都不如它长命。

如果我们将 Activity 作为页面的容器,将 Fragment 作为实际的页面,那么对状态的需求存在以下两种情况:

1.有些状态是跨页面共享的,也即退出某个页面时,状态不会跟随该页面的 destory 而被清空。

2.另有些状态是私有的,那么当退出该页面时,状态跟随页面的 destory 而被清空。

—— 这使得 ViewModel 实例的管理,绝非 VIABUS 那么简单粗暴 —— 直接在 BUS 单例中通过 Map 来管理多个业务实例来搞定。它 有作用域可控的需求

那怎么实现在状态可共享的情况下,还做到作用域可控呢?

Google 是这么设计的:

首先,ViewModel 的职责是专门管理状态,尤其是那些比较重的状态(例如从后端请求到的 List 数据)。

并且,ViewModel 归根结底是被对应的 Activity/Fragment 持有。只不过 ViewModel 和 LifeCycler 一样,通过模版方法模式封装得太好了,乃至于开发者感觉不到 它实际上与视图控制器的关系十分紧密,并不是表面看上去的 “那么独立” 和 “逍遥法外”。

👆👆👆 划重点

top Created with Sketch.