ViewModels 简单入门

简介

两年前,我在作 给 Android 入门的课程,教零基础学生开发 Android App。其中有一部分是教学生构建一个简单 App 叫作 Court-Counter.前端

Court-Counter 是一个只有几个按钮来修改篮球比赛分数的 App。最终的App有一个bug,若是你旋转手机,当前保存的分数会莫名归零。java

这是什么缘由呢?由于旋转设备会致使 App 中一些 配置发生改变 ,好比键盘是否可用,变动设备语言等。这些配置的改变都会致使 Activity 被销毁重建。

这种表现可让咱们在作一些特殊处理,好比设备旋转时变动为横向特定布局。 然而对于新手(有时候老鸟也是)工程师来讲,这可能会让他们头疼。git

在 Google I/O 2017,Android Framework团队推出了一套 Architecture Components 的工具集,其中一个处理设备旋转的问题。github

ViewModel 类旨在以有生命周期的方式保存和管理与UI相关的数据。 这使得数据能够在屏幕旋转等配置变化的状况下不丢失。数据库

这篇文章是详细探索ViewModel系列文章中的第一篇。 在这篇文章中,我会:后端

  • 解释ViewModel知足的基本需求
  • 经过更改 Court-Counter 代码以使用 ViewModel 解决旋转问题
  • 仔细审视 ViewModel 和 UI 组件的关联

潜在的问题

潜在的挑战是 Android Activity 生命周期 中有不少状态,而且因为配置更改,单个Activity可能会屡次循环进入这些不一样的状态。架构

Activity 会经历全部这些状态,也可能须要把暂时的用户界面数据存储在内存中。这里将把临时UI数据定义为UI所需的数据。例子中包括用户输入的数据,运行时生成的数据或者是数据库加载的数据。这些数据能够是bitmap, RecyclerView 所需的对象列表等等,在这个例子中,是指篮球得分。

之前你可能用过 onRetainNonConfigurationInstance 方法在配置更改期间保存和恢复数据。可是,若是你的数据不须要知道或管理 Activity 所处的生命周期状态,这样写会不会致使代码过于冗杂?若是 Activity 中有一个像scoreTeamA 这样的变量,虽然与 Activity 生命周期紧密相连,但又存储在Activity以外的地方呢?这就是 ViewModel 类的目的ide

在下面的图表中,能够看到一个 Activity 的生命周期,该 Activity 经历了一次旋转,最后被 finish 掉。 ViewModel 的生命周期显示在关联的Activity生命周期旁边。注意,ViewModels 能够很简单的用与Fragments 和 Activities,,这里称他们为 UI 控制器。本示例着重于 Activities。工具

ViewModel从你首次请求建立ViewModel(一般在onCreate的Activity)时就存在,直到Activity完成并销毁。Activity 的生命周期中,onCreate可能会被调用屡次,好比当应用程序被旋转时,但 ViewModel 会一直存在,不会被重建。

一个简单的例子

分三步骤来设置和使用ViewModel:布局

  1. 经过建立一个扩展 ViewModel 类来从UI控制器中分离出你的数据
  2. 创建你的 ViewModel 和UI控制器之间的通讯
  3. 在 UI 控制器中使用你的 ViewModel

**第一步: 建立 ViewModel 类 **

通常来说,须要为每一个界面都建立一个ViewModel类。这个ViewModel类将保存与该屏相关的全部数据,提供 getter 和 setter。这样就将数据与 UI 显示逻辑分开了,UI逻辑在Activities 或 Fragments中,数据保存在 ViewModel 中。好了,接下来为 Court-Counter 中的一个屏建立ViewModel类:

public class ScoreViewModel extends ViewModel {
// Tracks the score for Team A
public int scoreTeamA = 0;

// Tracks the score for Team B
public int scoreTeamB = 0;
}
复制代码

为了简洁,这里我采用了公共成员存储在ScoreViewModel.java中,也能够选择用 getter 和 setter 来更好地封装数据。

第二步:关联UI控制器和ViewModel

你的UI控制器(Activity或Fragment)须要访问你的ViewModel。这样,UI控制器就能够在UI交互发生时显示和更新数据,例如按下按钮以增长 Court-Counter 中的分数。

ViewModels不该该持有 Activities ,Fragments 或者 Context 的引用。

此外,ViewModels也不该包含包含对UI控制器(如Views)引用的元素,由于这将建立对Context的间接引用。

之因此不这样作是由于,ViewModel 比 UI控制器生命周期长,好比你旋转一个Activity三次,会获得三个不一样的Activity实例,但ViewModel只有一个。

基于这一点,咱们来建立 UI控制器/ ViewMode l的关联。在UI控制器中将 ViewModel 建立为一个成员变量。而后在 onCreate中这样调用:

ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class)
复制代码

在 Court-Counter 例子中,会是这样:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = ViewModelProviders.of(this).get(ScoreViewModel.class);
// Other setup code below...
}
复制代码

注意: 这里对 “no contexts in ViewModels” 规则有个例外。有时候你可能会须要一个 Application context(as opposed to an Activity context) 调用系统服务。这种状况下在 ViewModel 中持有 Application context 是没问题的,由于 Application context 是存在于 App 整个生命周期的,这点与 Activity context 不一样, Activity context 只存在与 Activity 的生命周期。事实上,若是你须要 Application context,最好继承 AndroidViewModel ,这是一个持有 Application 引用的 ViewModel。

第三步:在 UI 控制器中使用 ViewModel

要访问或更改UI数据,可使用ViewModel中的数据。下面是一个新的 onCreate 方法的示例,以及一个增长 team A 分数的方法:

// The finished onCreate method
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   mViewModel = ViewModelProviders.of(this).get(ScoreViewModel.class);
   displayForTeamA(mViewModel.scoreTeamA);
   displayForTeamB(mViewModel.scoreTeamB);
复制代码

}

// An example of both reading and writing to the ViewModel
public void addOneForTeamA(View v) {
    mViewModel.scoreTeamA = mViewModel.scoreTeamA + 1;
    displayForTeamA(mViewModel.scoreTeamA);
}
复制代码

tips: ViewModel 也能够很好地与另外一个架构组件 LiveData 一块儿工做,在这个系列中我不会深刻探索。使用LiveData 的额外好处是它是可观察的:它能够在数据改变时触发UI更新。能够在这里了解更多关于LiveData的信息。

进一步审视 ViewModelsProviders.of

第一次调用 ViewModelProviders.of 方法是在 MainActivity 中,建立了一个新的 ViewModel 实例。每次调用 onCreate 方法都会再次调用这个方法。它会返回以前 Court-Counter MainActivity 中建立的 ViewModel。 这就是它持有数据的方式。

只有给 UI controller 提供正确的UI控制器做为参数才能够。切记不要在 ViewModel 内存储 UI 控制器,ViewModel 会在后台跟踪 UI 控制器实例和 ViewModel 之间的关联。

ViewModelProviders._of_(**<THIS ARGUMENT>**).get(ScoreViewModel.**class**);
复制代码

这可让你有一个应用程序,打开同一个 Activity or Fragment 的不一样实例,但具备显示不一样的 ViewModel 信息。让咱们想象一下,若是咱们扩展 Court-Counter 程序,使其能够支持不一样的篮球比赛得分。比赛呈如今列表里,而后点击列表中的比赛就会开启一屏与 MainActivity 同样的画面,后面我就叫它 GameScoreActivity。

对于你打开的每个不一样的比赛画面,在 onCreate 中关联ViewModel和GameScoreActivity 后,它将建立不一样的 ViewModel 实例。旋转其中一个屏幕,则保持与同一个ViewModel的链接。

全部这些逻辑都是经过调 ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class) 实现的。 你只须要传递正确的UI 控制器实例就好。

最后的思考: ViewModel很是好的把你的UI控制器代码与UI的数据分离出来。 这就是说,它并非能完成数据持久化和保存App 状态的工做。 在下一篇文章中,我将探讨Activity生命周期与ViewModels之间的微妙交互,以及 ViewModel 与 onSaveInstanceState 进行比较。

结论和进一步的学习

在这篇文章中,我探索了新的ViewModel类的基础知识。关键要点是:

  • ViewModel类旨在一个连续的生命周期中保存和管理与UI相关的数据。这使得数据能够在屏幕旋转等配置变化的状况下得以保存。
  • ViewModels将UI实现与 App 数据分离开来。
  • 通常来讲,若是某屏应用中有瞬态数据,则应该为该屏的数据建立一个单独的ViewModel。
  • ViewModel的生命周期从关联的UI控制器首次建立时开始,直到彻底销毁。
  • 不要将UI控制器或 Context 直接或间接存储在ViewModel中。这包括在ViewModel中存储 View。对UI控制器的直接或间接引用违背了从数据中分离UI的目的,并可能致使内存泄漏。
  • ViewModel对象一般会存储LiveData对象,您能够在 这里了解更多。
  • ViewModelProviders.of 方法经过做为参数传入的 UI控制器与 ViewModel 进行关联。

想要了解更多 ViewModel 化的好处? 能够进一步阅读下面文章:

感谢 Mark Lu, Florina Muntenescu, 以及 Daniel Galpin.


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博知乎专栏

相关文章
相关标签/搜索