Android 构建 MVVM 应用 with Kotlin(一)概述

9 6月

各种模式

MVVM 是一种开发模式,对应的还有 MVC、MVP。这些模式最早在前端领域比较火,后来因为便于维护,被应用到各个平台。但 Android 上却发展的很慢。先来看一下各个模式的区别,以及他们在 Android 中的对应关系。

MVC

  • Model:业务数据模型。用于获取、储存数据。
  • View:视图。Activity、Fragment 等,当然也包括 xml 布局文件。
  • Controller:控制器。处理用户交互,向 Model 发送数据。。

乍一看似乎这个架构还不错,而且和 Android 基本组件也能对应。但事实上,xml 作为 View,其功能实在是太差劲了,基本上仅仅用了描述一个视图。而 Activity 不得不处理大量的 UI 问题,干脆很多时候顺便连数据一起处理了。最后 Android 开发成为了 面向 Activity 编程。所有的逻辑全部糅杂在一起,在加上生命周期问题,简直就是噩梦。

MVP

为了解决上面 Activity 承担太多工作的问题。聪明的程序猿们专为 Android 改造了 MVC,变成 MVP。MAC 的本质问题是 Activity 同时承担了 View 与 Controller 的角色。而 MVP 把 Activity 中的 UI 逻辑抽象成 View 接口,把业务逻辑抽象成 Presenter 接口,Model 还是原来的 Mdel。 如此一来 Activity 的工作就简单多了,只用来同步生命周期,其他乱七八糟的东西都交给 Presenter。

但是,这并没有解决实际问题,不过把一堆恶心的代码从 V 搬到 P 而已。而且 P 必定还要持有 V 接口引用以便进行 UI 操作。这下 VP 又耦合在一起。要是改变 UI,又得改一堆接口,想想都很难受。

MVVM

主角登场啦~ 其实 MVVM 与 MVP 的思想是一样的,都是把 UI 逻辑与业务逻辑分离开来。但 MVP 的做法不彻底,虽然 P 只是关联 View 的抽象接口,但最终还是和 View 耦合了。MVVM 引入一个 ViewModel 来搞定 UI 与 Model 的交互,彻底解放 Activity。MVVM 是 Android 目前唯一官方提供框架支持的开发模式。

听起来很简单,但具体怎么实现呢?既然 MVP 我们不得不维护一堆接口,那为什么不干脆让它们自动维护。

MVVM 原则

模型驱动界面

无论 MVC 还是 MVP,UI 总是需要控制的。也就是我们必须直接或间接持有 View 的引用,然后根据数据对 UI 进行赋值。这也是导致 VP 耦合的直接原因。在 MVVM 中,我们不再需要持有 View 了,无论是抽象接口还是实体有不用。数据与 UI 的关联被框架自动完成。也就是当数据改变时,我们什么都不用做 UI 就可以刷新。在这种模式下,开发者可以完全忽略 UI,只需专心处理数据即可。UI 总会按照最佳实践在最合适的时机刷新。

举个例子:我们经常需要从网络异步加载数据(例如文章列表),但必须考虑如果获得响应时 View 已经被销毁了怎么办?现在不用考虑啦,专心获取数据就行。

关注点分离

说白了就是个人管好自己的事情。Activity 是个大框,什么都往里装,这是我们最常犯的错误。在 MVVM 模式下,ViewModel 只关心数据业务,完全无视 UI。Model 只需专心提供数据,完全无视用户交互。

Google 文档提到,另一个关键是 应用从未有过 Activity 的实现,它只不过是应用于操作系统的纽带,但不属于应用管理。操作系统可能随时销毁它。因此我们应当尽量减少对 Activity 的依赖,显然 MVVM 很好地实践了这一点。


上面这些是 MVVM 的思想,而实现则是 Android 提供的 Databinding,这一框架完成了 Model 与 View 的自动同步,解决了之前最恶心的问题。以及更多的 Android 架构组件

整体架构

现在是时候搭建 MVVM 的整体框架了。

Android 官方提供了一套非常完善的组件来搭建 MVVM 模式的应用。最上面是传统的 Activity,它只依赖 ViewModel,并且通过 Databinding 将 ViewModel 传给 xml 布局文件进行全自动的 UI 同步。ViewModel 内保存的并不是原始数据,而是经过了 LiveData 的包装。LiveData 是一个可观测的对象,如此一来,Activity (Databinding) 才可以实时获得数据变更的通知,并刷新 UI。

始终谨记 ViewModel 只是负责连接 View 与 Model,因此真正的数据是由 Repository 提供的。实际上 Repo 才是真正的 Model. 因为业务中我们往往不只有1个数据源,例如网络/缓存。根据原则,ViewModel 不应当关系数据究竟来源于哪。因此我们抽出1个 Repo 层来统一提供数据。

Repo 层可以依赖多个数据源,比如通过 Retrofit 从 http 获得数据,或者来自文件或者数据库。

倘若需要用到数据库,那么 Room 组件是一个非常棒的选择。它不仅仅是一个 Dao,更重要的是它能直接返回一个 LiveData 对象。也就是说当数据库更新时,我们不再需要手动刷新数据,之前从数据库取过数据的对象会全部更新,而且这一更新只在需要的情况下才会进行。

试想这样一个场景:

应用首先从数据库中取出文章列表缓存显示出来,并进行后台刷新。此时用户非常迫不及待地点进去看了,因此他离开了文章列表界面。这时应用拿到了服务端最新的文章列表,并更新数据库缓存,由于文章列表界面不可见,因此 UI 不会做无谓刷新。当用户看完文章返回到了列表,UI 会立即更新,而它更新的原因不是从网络拿到了新的数据,而是因为缓存数据库更新了。

事实上,更新缓存可能有很多原因,从网络刷新只是原因之一。但 Room + LiveData 可以始终保证 UI 显示最新的数据,全自动的。

通过这个例子相信已经可以感受到 MVVM 架构,以及 Android 实现的强大与便利了。

单一可信来源

上面的例子引出了另一个话题。我特别强调,UI 更新不是因为从网络刷新了数据,而是因为数据库更新了。在这个例子中,数据库充当了单一可信数据来源。 也就是说 ViewModel 将只从数据库拿取数据,其他数据来源将更新至数据库。这种做法的好处是可以最大程度保证数据的一致性,并且可以在弱网络环境下用户依然可以获得良好的体验。

  • 倘若没有数据库缓存。那么用户每次打开应用都不得不等待加载,在无网络环境下只有白屏,体验非常差。
  • 倘若没有单一可信来源。有可能网络返回的最新数据与数据库不一致,导致不同的组件拿到的数据不一致,给用户造成困扰。

第一篇我大概地讲了下 MVVM 模式的优点,以及 Android 官方提供的实现方案。下面将分开每部分说说它们的具体用法。

发表评论

电子邮件地址不会被公开。 必填项已用*标注