[译]ViewModels:一个简单的示例

引言

大约两年前,我在传授Android for Beginners课程,这是一门让零编程基础的学生学习如何编写第一个Android应用程序的课程。做为课程的一部分,学生们将开发一个很是简单的名为Court-Counter的应用程序。html

Court-Counter是一个很是简单的应用程序,只有一个界面,里面提供了一些按钮用于修改篮球比赛的比分。学生们最终完成的应用程序有都存在一个bug: 若是旋转手机的屏幕,应用程序界面上的当前比分将莫名其妙地丢失。java

这是怎么回事?旋转设备的屏幕是应用程序在其生命周期中可能经历的一些configuration changes中的一种,其它还包括等改设备的语言等。 全部这些configuration changes都会致使Activity被销毁并从新建立。 Android系统的这种机制可让咱们作一些有趣的事情,好比在设备旋转的时候使用横向布局,但它却可能会让Android开发新手头疼不已。android

在2017年Google I / O大会上,Android Framework团队推出了一套新的Architecture Components,其中的一个组件ViewModel就是用来处理这个屏幕的旋转问题。git

ViewModel 类旨在以一种可以感知生命周期的方式来保存和管理与UI相关的数据,这使得数据可以在configuration changes(如屏幕旋转)的时候不会丢失。github

这篇文章是探索ViewModel细节的系列文章中的第一篇。 在这篇文章中,我会:数据库

  • 解释ViewModel所能知足的基本需求
  • 使用ViewModel来重构Court-Counter的代码从而解决屏幕旋转问题
  • 深刻研究ViewModel和UI组件之间的关系

根本的问题

问题的根本缘由在于Activity的生命周期有不少不一样的状态,而且因为configuration changes,一个Activity可能会屡次经历这些不一样的状态。编程

当一个Activity正在经历全部的这些状态时,您可能还须要在内存中保存一些UI的临时数据。我将UI的临时数据定义为UI所需的数据。它包括用户输入的数据,应用在运行时生成的数据或者是从数据库加载的数据。这些数据多是位图图像,RecyclerView所需的对象列表,或者是本文中提到的篮球得分。bash

ViewModel出现以前,在configuration changes的时候您可能会使用onRetainNonConfigurationInstance方法来保存此数据,并使用getLastNonConfigurationInstance方法来取出这些数据。可是若是你的数据不须要知道Activity正处于处生命周期的哪一种状态,它会不会无限膨胀?若是这些数据不是像Activity的变量scoreTeamA那样,与Activity的生命周期紧密相,而是存储在Activity以外的其余位置,该怎么办? 这正是ViewModel类存在的意义。架构

在下面的图表中,您能够看到一个Activity的生命周期,该Activity经历了一次屏幕旋转,而后最终被finish。ViewModel的生命周期显示在相对应的Activity生命周期的旁边。 请注意,ViewModels能够很方便的用在Fragment和Activity里,我将称其为UI controllers。本文重点介绍的是在Activity里如何使用ViewMode。ide

从你第一次请求ViewModel(一般在onCreate Activity中)开始到Activity最终被销毁,ViewModel会一直存在。 onCreate可能会在Activity的生命周期中屡次调用,例如设备的屏幕发生旋转,但ViewModel仍是同一个ViewModel。

一个很简单的示例

ViewModel的使用能够分为一下三个步骤:

  1. 建立一个继承ViewModel的类,将数据从UI controllers中分离出来。
  2. 将ViewModel和你的UI controllers关联起来。
  3. 在您的UI controllers中使用ViewModel。

Step 1: 建立一个ViewModel类

通常来讲,您须要为您应用中的每一个界面建立一个ViewModel类。 这个ViewModel类将保存与界面相关的全部数据,并为存储的数据提供getter和setter方法。这样就将用户界面(在Activity和Fragment中实现)中须要显示的数据从UI controllers中分离出来,如今该数据位于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 controllers和ViewModel关联起来

你的UI controllers(这里是指Activity或Fragment)须要知道你的ViewModel,由于用户在与UI发生交互的时候须要显示数据和更新数据,例如按下按钮以增长Court-Counter计数器中的团队得分。 ViewModels不该该包含Activity、Fragment或Context的引用。此外,ViewModels还不该包含对UI controllers中的变量(如Views)的引用,由于这将建立对Context的间接引用。

不要在ViewModels里存储这些对象的缘由是ViewModels是独立于你的UI controllers实例以外的- 若是你三次旋转一个Activity的屏幕方向,那么系统会建立三个不一样的Activity实例,但你只有一个ViewModel。

考虑到这一点,咱们须要实现这个UI controller 与 ViewModel之间关联。 您须要在UI controller中为您的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...
}
复制代码

注意: “ViewModels中不该包含Context”这个原则有一个例外状况。 有时您可能须要Application context (而不是Activity context )以获取诸如系统服务之类的东西。将Application context存储在ViewModel中是能够的,由于Application context是与应用程序的生命周期相关联的。这与Activity context不一样,后者与Activity的生命周期相关联。事实上,若是你须要一个Application context,你应该继承AndroidViewModel类,它是一个包含Application context的ViewModel。

Step 3: 在你的UI Controller中使用ViewModel

如今你能够在ViewModel中获取或更改UI中数据了。 下面是一个新的onCreate方法的示例:

// 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);
}

复制代码

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

ViewModelProviders.of的原理

当MainActivity第一次调用ViewModelProviders.of方法的时候,它将建立一个新的ViewModel实例。 以后MainActivity里的onCreate()方法每一次被调用的时候,ViewModelProviders.of也一样会被调用,可是它将返回与MainActivity相关联的预先存在的ViewModel,这就是ViewModel能够保存数据的缘由。

前提条件是你必须传入正确的UI controller来做为ViewModelProviders.of的第一个参数。 虽然你不该该在ViewModel中存储UI controller,但ViewModel类会使用您传入的UI controller做为第一个参数来跟踪ViewModel和UI controller之间的关联关系。

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

ViewModelProviders.of使得你的应用能够打开同一Activity或Fragment的不一样实例,但ViewModel中却保存着不一样的信息。

咱们能够把Court-Counter扩展一下,使它能记录和显示多场篮球比赛的分数。比赛以列表形式呈现,而后点击列表中的某一场比赛会打开一个看起来像咱们当前的MainActivity的界面,这里我称之为GameScoreActivity。

对于您打开的每场比赛所对应的GameScoreActivity,若是在GameScoreActivity的onCreate方法将其与ViewModel关联起来,它将建立一个不一样的ViewModel实例。若是旋转其中一个界面的屏幕,则保持与同一ViewModel的链接。

全部这些逻辑都是经过调用ViewModelProviders.of(Your UI controller)get(Your ViewModel.class)方法来完成的。 因此只要你传入一个UI控制器的正确实例,它就能够正常工做。

最后我想说:ViewModels真的很好,能够将填充数据到视图的逻辑从UI controller分离出来。这意味着,它并非一种数据持久化和保存应用程序状态的解决方案。在下一篇文章中,我将研究Activity生命周期与ViewModels之间的交互,并将ViewModel与onSaveInstanceState进行比较。

结束语

在这篇文章中,我介绍了一些关于ViewModel类的基础知识。关键要点是:

  • ViewModel的目标是以一种可以感知生命周期的方式来保存和管理与UI相关的数据,这使得数据可以在configuration changes(如屏幕旋转)的时候不会丢失。
  • ViewModels实现了UI与数据的分离。
  • 通常来讲,若是您应用中的界面有临时数据,你应该为该界面上的数据建立一个单独的ViewModel。
  • ViewModel的生命周期从首次建立UI controller与ViewModel的关联开始,直至UI controller被彻底销毁。
  • 切勿将UI controller或Context直接或间接的存储在ViewModel中,包括在ViewModel中存储View。直接或间接的引用UI controller违背了将UI与数据分离的目的,并可能致使内存泄漏。
  • ViewModel对象一般会存储LiveData对象,您能够在这里了解更多信息。
  • ViewModelProviders.of方法经过它的参数(传入的UI controller)来跟踪它与对应的UI controller之间的关联关系。
相关文章
相关标签/搜索