大约两年前,我在传授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细节的系列文章中的第一篇。 在这篇文章中,我会:数据库
问题的根本缘由在于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的使用能够分为一下三个步骤:
通常来讲,您须要为您应用中的每一个界面建立一个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(这里是指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。
如今你能够在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的信息。
当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类的基础知识。关键要点是: