风,它不肯说,
云,悄悄飘过,
黑夜它依旧沉默。
只有天上一颗星星说,她睡了,
你是否也该休息了。
—— 《让我心动的人》
本文翻译自 Android lifecycle-aware components codelab,不保证和官网内容同步,如有纰漏,还请指出。
目录
6. 步骤5 - 在 fragments 之间共享 ViewModel
1. 介绍
组件
Architecture components (架构组件)是一组 Android 库,可以帮助你以健壮、可测试和可维护的方式构建 app。
这个 codelab 向你介绍了以下用于构建 Android 应用程序的 lifecycle-aware(可感知生命周期的)架构组件:
- ViewModel - 提供了一种创建和检索绑定到特定生命周期的对象的方法。ViewModel 典型地存储视图数据的状态,并与其他组件(如数据存储库或处理业务逻辑的域层)通信。要阅读本主题的入门指南,请参阅 ViewModel。
- LifecycleOwner/LifecycleRegistryOwner - LifecycleOwner 和 LifecycleRegistryOwner 都是在 AppCompatActivity 和 Support Fragment 类中实现的接口。你可以向实现这些接口的所有者对象订阅其他组件,以观察所有者生命周期的变化。要阅读本主题的入门指南,请参阅处理生命周期。
- LiveData - 允许你观察应用程序多个组件之间的数据变化,而不需要在它们之间创建显示的、严格的依赖路径。LiveData 尊重应用程序组件的复杂生命周期,包括 activities、fragments、services 或 app 中定义的任何 LifecycleOwner。LiveData 通过暂停对已停止的 LifecycleOwner 对象的订阅和取消对已完成的 LifecycleOwner 对象的订阅,来管理观察者订阅。要阅读本主题的入门指南,请参阅 LiveData。
你将构建什么
在这个 codelab 中,你将实现上述每个组件的示例。从示例 app 开始,通过一系列步骤来添加代码,在进行过程中集成各种架构组件。
你需要什么
- Android Studio 2.3 或更高版本。
- 熟悉 Android actiivty 生命周期。
2. 步骤1 - 设置环境
在此步骤中,你将下载完整的 codelab 的代码,然后运行一个简单的示例 app。
单击下面的按钮下载此 codelab 的所有代码:
1.解压代码,然后在 Android Studio version 2.3 获取更新版本中打开项目。
2.在设备或模拟器上运行 Step1 run configuration。
该 app 运行并显示与下面截图相似的屏幕:
3.旋转屏幕并注意计时器重置!
现在,你需要更新 app 以在屏幕旋转中保持状态。可以使用ViewModel,因为这个类的实例在配置更改(如屏幕旋转)中得以存活。
3. 步骤2 - 添加 ViewModel
在这个步骤中,使用 ViewModel 在屏幕旋转中保存状态,并解决在上一步中观察到的行为。在前面的步骤中,运行一个显示计时器的 activity。当配置更改(如屏幕旋转)销毁 activity 时,将重置此计时器。
你可以使用 ViewModel 在 activity 或 fragment 的整个生命周期中保存数据。正如前面的步骤所示,activity 是管理 app 数据的糟糕选择。activity 和 fragment 是短暂存活的对象,在用户与 app 交互时频繁地被创建和销毁。ViewModel 同样很适用于管理与网络通信相关的任务,以及数据操作和持久化。
使用 ViewModel 保存计时器的状态
打开 ChronoActivity2 并检查该类是如何检索和使用 ViewModel 的:
ChronometerViewModel chronometerViewModel
= ViewModelProviders.of(this).get(ChronometerViewModel.class);
这是 LifecycleOwner 的一个实例。 只要 LifecycleOwner 的范围处于活动状态,框架就会使 ViewModel 保持活动状态。 如果 ViewModel 的所有者因配置更改(例如屏幕旋转)而被销毁,ViewModel 也不会被销毁。 所有者的新实例将重新连接到现有的 ViewModel,如下图所示:
.注意:activity 或 fragment 的范围从 created(创建 ) 到 finished (完成)(或 terminated(终止)),不能将其与销毁混淆。 请记住,当旋转设备时,activity 将被销毁,但与之关联的 ViewModel 的任何实例都不会被销毁。
试一试
运行 app(在 Run Configurations下拉菜单中选择 Step2)并确认计时器在执行以下操作时没有重置:
- 旋转屏幕。
- 导航到另一个 app,然后返回。
但是,如果你或系统退出了 app,那么计时器将重置。
注意:系统在生命周期所有者的整个生命周期中将 ViewModel 的实例保存在内存中,例如 fragment 或 activity。系统不会将 ViewModel 的实例保存到长期存储。
4. 步骤3 - 使用 LiveData 包装数据
在这个步骤中,你将用自定义的计时器替换前面步骤中使用的 Timer
,并每秒更新 UI。Timer
是一个 java.util 类,可用于在将来重复安排任务。将此逻辑添加到 LiveDataTimerViewModel 类,并使 activity 专注于管理用户和 UI 之间的交互。
当计时器通知(activity)时,activity 会更新 UI。为了帮助避免内存泄漏,ViewModel 不包含对 activity 的引用。例如,配置更改(例如屏幕旋转),可能导致 ViewModel 中引用应该被垃圾回收的 activity。系统将保留 ViewModel 的实例,直到相应的 activity 或生命周期所有者不再存在。
注意:在 ViewModel 中存储对 Context 或 View 的引用可能导致内存泄漏。 避免使用引用 Context 或 View 类实例的变量。 onCleared( ) 方法对于取消订阅或清除对具有更长生命周期的其他对象的引用非常有用,但不能用于清除对 Context 或 View对象的引用。
不直接从 ViewModel 修改视图,而是配置 activity 或 fragment 来观察数据源,当数据发生变化时接收数据。这种安排称为观察者模式。
注意:要将数据公开为可观察对象,请将类型包装在 LiveData 类中。
如果你使用过 Data Binding 库或其他响应式库(如RxJava),那你可能熟悉观察者模式。 LiveData 是一个特殊的可观察类,它是生命周期感知的,只通知活跃的观察者。
LifecycleOwner
ChronoActivity3 是 LifecycleActivity 的一个实例,它可以提供生命周期的状态。 这是类声明:
public class LifecycleActivity extends FragmentActivity implements LifecycleRegistryOwner {...}
LifecycleRegistryOwner 用于将 ViewModel 和 LiveData 实例的生命周期绑定到 activity 或 fragment。 fragment 的等价类是 LifecycleFragment。
更新 ChronoActivity
1. 在 ChronoActivity3 类的 subscribe( ) 方法中添加以下代码以创建订阅:
mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver);
2. 接下来,在 LiveDataTimerViewModel 类中设置新的已用时间值。 找到以下注释:
//TODO set the new value
用以下语句替换注释:
mElapsedTime.postValue(newValue);
3. 运行 app 并在Android Studio中打开 Android Monitor。 请注意,除非你导航到其他 app,否则日志每秒都会更新。 如果你的设备支持多窗口模式,你可能想尝试使用它。 旋转屏幕不会影响 app 的行为方式。
注意:LiveData 对象仅在活动或 LifecycleOwner 处于活动状态时发送更新。 如果你导航到其他 app,日志消息会暂停,直到返回。 LiveData 对象仅在其各自的生命周期所有者为 STARTED 或 RESUMED 时才将订阅视为活动。
5. 步骤4 - 订阅生命周期事件
许多Android组件和库要求你:
- 订阅,或初始化组件或库。
- 取消订阅,或停止组件或库。
未能完成上述步骤可能会导致内存泄漏和细微的错误。
可以将生命周期所有者对象传递给生命周期感知组件的新实例,以确保它们知道生命周期的当前状态。
你可以使用以下语句查询生命周期的当前状态:
lifecycleOwner.getLifecycle().getCurrentState()
上面的语句返回一个状态,例如 Lifecycle.State.RESUMED 或 Lifecycle.State.DESTROYED。
实现 LifecycleObserver 的生命周期感知对象还可以观察生命周期所有者状态的变化:
lifecycleOwner.getLifecycle().addObserver(this);
可以对对象进行注解,以指示它在需要时调用适当的方法:
@OnLifecycleEvent(Lifecycle.EVENT.ON_RESUME)
void addLocationListener() { ... }
创建一个生命周期感知组件
在此步骤中,你将创建一个对 activity 生命周期所有者作出反应的组件。 使用 fragment 作为生命周期所有者时,类似的原则和步骤同样适用。
你可以使用 Android 框架的 LocationManager 获取当前的纬度和经度,并将其显示给用户。 此添加允许你:
- 订阅更改并使用 LiveData 自动更新 UI。
- 根据 activity 状态的更改,创建注册和注销 LocationManager 的包装器。
你通常会将 LocationManager 订阅到活动的 onStart( ) 或 onResume( ) 方法中的更改,并删除 onStop( ) 或 onPause( ) 方法中的监听器:
// 在 activity 中的典型用法。
@Override
protected void onResume() {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mListener);
}
@Override
protected void onPause() {
mLocationManager.removeUpdates(mListener);
}
在此步骤中,你将在名为 BoundLocationManager 的类中使用名为 LifecycleRegistryOwner 的 LifecycleOwner 实现。 BoundLocationManager 类的名称指的是类的实例绑定到活动的生命周期。
要让类观察 activity 的生命周期,必须将其添加为观察者。 为此,请指示 BoundLocationManager 对象通过将以下代码添加到其构造函数来观察生命周期:
lifecycleOwner.getLifecycle().addObserver(this);
要在发生生命周期更改时调用方法,可以使用 @OnLifecycleEvent 注解。 使用 BoundLocationListener 类中的以下注解更新 addLocationListener( ) 和 removeLocationListener( ) 方法:
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void addLocationListener() {
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
void removeLocationListener() {
...
}
注意:观察者被带到提供者的当前状态,因此不需要从构造函数中调用 addLocationListener( )。 当观察者被添加到生命周期所有者时,它会被调用。
旋转设备时,运行应用程序并验证 Log Monitor 是否显示以下操作:
D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed
D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed
使用Android模拟器模拟更改设备的位置(单击三个点以显示扩展控件)。 TextView 在更改时会更新:
6. 步骤5 - 在 fragments 之间共享 ViewModel
在 fragments 之间共享 ViewModel
使用 ViewModel 完成以下附加步骤,以启用 fragments 与以下内容之间的通信:
运行此步骤并注意 SeekBar 的两个实例,它们彼此独立:
使用 ViewModel 连接 fragments,以便在更改一个 SeekBar 时更新另一个 SeekBar:
注意:你应该将 activity 用作生命周期的所有者,因为每个 fragment 的生命周期是独立的。
本练习没有分步手册,但你可以在 step5_solution
包中找到解决方案。