内容简介:两年多前,我正在开发Android初学者;一个课程,将学生从零编程带到他们的第一个Android应用程序。作为课程的一部分,学生们构建了一个非常简单的一个名为Court-Counter 是一个非常简单的应用程序,带有修改篮球得分的按钮。完成的应用程序虽然有一个bug; 如果你旋转手机,你的当前分数将莫名其妙地消失。这是怎么回事?旋转设备是应用程序在其生命周期内可以进行的一些配置更改之一,包括键盘可用性和更改设备的语言。所有这些配置更改都会导致活动被拆除并重新创建。
两年多前,我正在开发Android初学者;一个课程,将学生从零编程带到他们的第一个Android应用程序。作为课程的一部分,学生们构建了一个非常简单的一个名为 Court-Counter 的屏幕应用程序。
Court-Counter 是一个非常简单的应用程序,带有修改篮球得分的按钮。完成的应用程序虽然有一个bug; 如果你旋转手机,你的当前分数将莫名其妙地消失。
这是怎么回事?旋转设备是应用程序在其生命周期内可以进行的一些配置更改之一,包括键盘可用性和更改设备的语言。所有这些配置更改都会导致活动被拆除并重新创建。
这种行为允许我们在设备旋转时使用横向方向特定布局。不幸的是,对于新的(有时候不是那么新的)工程师来说,他们可能会头疼。
在Google I / O 2017中,Android Framework 团队引入了一组新的架构组件,其中一个处理这个确切的轮换问题。
该视图模型类是旨在支持并在生命周期意识的方式管理UI相关的数据。这允许数据在配置更改(如屏幕旋转)后继续存在。
这篇文章是探索 ViewModel 细节的系列文章中的第一篇。在这篇文章中,我将:
- 解释 ViewModels 实现的基本需求
- 通过更改 Court-Counter 代码以使用 ViewModel 来解决轮换问题
- 仔细查看 ViewModel 和 UI Component 关联
根本问题
潜在的挑战是 Android Activity 生命周期 有很多状态,并且由于配置更改,单个 Activity 可能会多次循环通过这些不同的状态。
当一个Activity正在经历所有这些状态时,您可能还需要保留在内存中的瞬态UI数据。我将把瞬态UI数据定义为UI所需的数据。示例包括用户输入的数据,运行时生成的数据或从数据库加载的数据。这些数据可以是位图图像,RecyclerView所需的对象列表,或者在这种情况下是篮球得分。
以前,您可能习惯 onRetainNonConfigurationInstance在
配置更改期间保存此数据并在另一端解压缩。但是,如果您的数据不需要知道或管理 Activity 所处的生命周期状态,那么它不会膨胀吗?而不是像 scoreTeamA
Activity 中那样拥有变量,因此与 Activity 生命周期的所有奇思妙想相关联,如果该数据存储在 Activity 之外的其他地方,该怎么办?这是 ViewModel 类的目的。
在下图中,您可以看到活动的生命周期,该活动经历轮换然后最终完成。ViewModel 的生命周期显示在关联的 Activity 生命周期旁边。请注意,ViewModel 可以很容易地与 Fragments 和 Activities 一起使用,我称之为 UI 控制器。此示例重点关注活动。
ViewModel 从您第一次请求 ViewModel(通常在 onCreate
Activity 中)到 Activity 完成并销毁之时就存在。 onCreate
可以在活动的生命周期中多次调用,例如当应用程序旋转时,但 ViewModel 始终存在。
一个非常简单的例子
设置和使用 ViewModel 有三个步骤:
- 通过创建扩展 ViewModel 的类,从 UI 控制器中分离出数据
- 设置 ViewModel 和 UI 控制器之间的通信
- 在UI控制器中使用 ViewModel
第1步:创建一个 ViewModel 类
注意:要创建 ViewModel,首先需要添加正确的生命周期依赖项。看看这里怎么样。
通常,您将为应用中的每个屏幕创建一个 ViewModel 类。此 ViewModel 类将保存与屏幕关联的所有数据,并具有存储数据的 getter 和 setter。这将代码分离,以显示您的活动和片段中实现的 UI,该数据现在位于 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 以更好地封装数据是一个好主意。
第2步:关联 UI 控制器和 ViewModel
您的 UI 控制器(也称为 Activity 或 Fragment )需要了解您的 ViewModel。这样您的UI控制器就可以在 UI 交互发生时显示数据并更新数据,例如按下按钮以增加团队在 Court-Counter 中的得分。
但是,ViewModels 不应该包含对 Activities,Fragments 或 Context 的引用。**此外,ViewModel 不应包含包含对UI控制器的引用的元素,例如 Views,因为这将创建对 Context 的间接引用。
您不应存储这些对象的原因是 ViewModels 比您的特定 UI 控制器实例更长 - 如果您将 Activity 旋转三次,您刚刚创建了三个不同的 Activity 实例,但您只有一个 ViewModel。
考虑到这一点,让我们创建这个 UI 控制器/ViewModel 关联。您需要在 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... } 复制代码
**注意: “ViewModels 中的无上下文”规则有一个例外。有时您可能需要一个Application 上下文 (而不是 Activity 上下文)来与系统服务一起使用。在 ViewModel 中存储应用程序上下文是可以的,因为应用程序上下文与应用程序生命周期相关联。这与 Activity 上下文不同,后者与 Activity 生命周期相关联。实际上,如果您需要 Application 上下文,则应该扩展AndroidViewModel,它只是一个包含 Application 引用的 ViewModel。
第3步:在 UI 控制器中使用 ViewModel
要访问或更改 UI 数据,您现在可以使用 ViewModel 中的数据。以下是 onCreate
通过向团队A添加一个点来更新分数的新方法和方法的示例:
专业提示: ViewModel 也可以与另一个体系结构组件LiveData 很好地配合,我将不会在本系列中深入探讨。使用 LiveData 的额外好处是它可以观察到:它可以在数据发生变化时触发 UI 更新。您可以在此处了解有关 LiveData 的更多信息。
// 完成的 onCreate 方法 @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); } // ViewModel 读取和写入的示例 public void addOneForTeamA(View v) { mViewModel.scoreTeamA = mViewModel.scoreTeamA + 1; displayForTeamA(mViewModel.scoreTeamA); } 复制代码
仔细看看 ViewModelsProviders.of
ViewModelProviders.ofMainActivity 第一次调用该方法时,它会创建一个新的 ViewModel 实例。当再次调用此方法时,无论何时 onCreate
调用此方法,它都将返回与特定 Court-Counter MainActivity 关联的预先存在的 ViewModel。这是保留数据的原因。
仅当您将正确的 UI 控制器作为第一个参数传递时,此方法才有效。虽然您永远不应该在 ViewModel 中存储 UI 控制器,但 ViewModel 类会使用您传入的 UI 控制器作为第一个参数来跟踪 ViewModel 和幕后 UI 控制器实例之间的关联。
ViewModelProviders.of(<THIS ARGUMENT>).get(ScoreViewModel.class); 复制代码
这允许您拥有一个应用程序,可以打开相同活动或片段的许多不同实例,但具有不同的 ViewModel 信息。让我们想象一下,如果我们扩展我们的 Court-Counter 示例来获得多个篮球比赛的分数。游戏以列表形式呈现,然后单击列表中的游戏将打开一个看起来像我们当前 MainActivity 的屏幕,但我将其称为 GameScoreActivity。
对于您打开的每个不同的游戏屏幕,如果您将 ViewModel 与 GameScoreActivityin
关联 onCreate
,它将创建一个不同的 ViewModel 实例。如果旋转其中一个屏幕,则会保持与同一 ViewModel 的连接。
所有这些逻辑都是通过调用完成的 ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class)
。因此,只要您传入正确的UI控制器实例,它就可以正常工作。
最后一点想法:ViewModel 非常适合将 UI 控制器代码与填充 UI 的数据分开。也就是说,它们并不能解决数据持久性和保存应用状态的问题。在下一篇文章中,我将探讨 Activity 生命周期与 ViewModels 的微妙交互以及 ViewModels 的比较方式 onSaveInstanceState。
结论和进一步学习
在这篇文章中,我探讨了新 ViewModel 类的基础知识。关键要点是:
- 该视图模型类是旨在支持并在生命周期意识的方式管理 UI 相关的数据。这允许数据在配置更改(如屏幕旋转)后继续存在。
- ViewModels 将 UI 实现与应用程序的数据分开。
- 通常,如果应用中的屏幕具有瞬态数据,则应为该屏幕的数据创建单独的 ViewModel。
- ViewModel 的生命周期从首次创建关联的 UI 控制器时开始,直到完全销毁。
- 切勿直接或间接地将 UI 控制器或 Context 存储在 ViewModel 中。这包括在 ViewModel 中存储 View。对 UI 控制器的直接或间接引用会破坏将 UI 与数据分离的目的,并可能导致内存泄漏。
- ViewModel 对象通常会存储 LiveData 对象,您可以在此处了解更多信息。
- 该 ViewModelProviders.of 方法跟踪什么 UI 控制器视图模型与通过传递作为参数的 UI 控制器相关联。
想要更多ViewModel-ly goodness?查看:
- 添加gradle依赖项的说明
- ViewModel 文档
- 使用带有视图 和生命周期 Codelab 的Room 引导 ViewModel 练习
架构组件是根据您的反馈创建的。如果您对 ViewModel 或任何架构组件有任何疑问或意见,请查看我们的反馈页面。关于这个系列的问题或建议?发表评论!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Google API开发详解
江宽,龚小鹏等编 / 电子工业 / 2008-1 / 59.80元
《Google API开发详解:Google Maps与Google Earth双剑合璧》从易到难、由浅入深、循序渐进地介绍了Google Maps API和Google Earth API的开发技术。《Google API开发详解:Google Maps与Google Earth双剑合璧》知识讲解通俗易懂,并有大量的实例供读者更加深刻地巩固所学习的知识,帮助读者更好地进行开发实践。 《Go......一起来看看 《Google API开发详解》 这本书的介绍吧!