Handling Lifecycles with Lifecycle-Aware Components
生命周期感知组件执行操作以响应另一个组件(例如 Activity 和 Fragment)的生命周期状态的更改。这些组件可帮助您生成更易于组织且更轻量级的代码,这些代码更易于维护。
一种常见模式是在 Activity 和 Fragment 的生命周期方法中实现依赖组件的操作。但是,这种模式导致代码组织不良和错误扩散。通过使用生命周期感知组件,您可以将依赖组件的代码移出生命周期方法并移入组件本身。
android.arch.lifecycle
包提供了类和接口,使您可以构建生命周期感知组件 - 这些组件可以根据 activity 或 fragment 的当前生命周期状态自动调整其行为。
注意:要将
android.arch.lifecycle
导入 Android 项目,请参阅 Adding Components to your Project。
Android framework 中定义的大多数应用组件都附加了生命周期。生命周期由操作系统或进程中运行的 framework 代码管理。它们是 Android 运作方式的核心,您的应用程序必须遵守它们。不这样做可能会触发内存泄漏甚至应用程序崩溃。
想象一下,我们有一个 activity,在屏幕上显示设备位置。常见的实现可能如下所示:
class MyLocationListener {
public MyLocationListener(Context context, Callback callback) {
// ...
}
void start() {
// connect to system location service
}
void stop() {
// disconnect from system location service
}
}
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
@Override
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, (location) -> {
// update UI
});
}
@Override
public void onStart() {
super.onStart();
myLocationListener.start();
// manage other components that need to respond
// to the activity lifecycle
}
@Override
public void onStop() {
super.onStop();
myLocationListener.stop();
// manage other components that need to respond
// to the activity lifecycle
}
}
虽然这个示例看起来很好,但在真实的应用程序中,最终会有太多的调用来管理 UI 和其他组件以响应生命周期的当前状态。管理多个组件会在生命周期方法中放置大量代码,例如 onStart()
和 onStop()
,这使得它们难以维护。
此外,无法保证组件在 activity 或 fragment 停止之前启动。如果我们需要执行长时间的操作,例如 onStart()
中的某些配置检查,则尤其如此。这可能会导致 onStop()
方法在 onStart()
之前完成的竞态条件,从而使组件保持活动的时间超过其所需的时间。
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, location -> {
// update UI
});
}
@Override
public void onStart() {
super.onStart();
Util.checkUserStatus(result -> {
// what if this callback is invoked AFTER activity is stopped?
if (result) {
myLocationListener.start();
}
});
}
@Override
public void onStop() {
super.onStop();
myLocationListener.stop();
}
}
android.arch.lifecycle
包提供了类和接口,可帮助您以弹性和隔离的方式解决这些问题。
一、Lifecycle
Lifecycle 是一个类,它包含有关组件生命周期状态的信息(如 activity 或 fragment),并允许其他对象观察此状态。
Lifecycle 使用两个主要枚举来跟踪其关联组件的生命周期状态:
Event
从 framework 和 Lifecycle 类分发的生命周期事件。这些事件映射到 activity 和 fragment 中的回调事件。
State
Lifecycle 对象跟踪的组件的当前状态。
将状态视为图形的节点,将事件视为这些节点之间的边。
类可以通过向其方法添加注解来监视组件的生命周期状态。然后,您可以通过调用 Lifecycle 类的 addObserver()
方法并传递观察者的实例来添加观察者,如以下示例所示:
public class MyObserver implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void connectListener() {
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void disconnectListener() {
...
}
}
myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
在上面的示例中,myLifecycleOwner
对象实现了 LifecycleOwner 接口。
二、LifecycleOwner
LifecycleOwner 是一个单一方法接口,表示该类具有生命周期。它有一个方法 getLifecycle()
,它必须由类实现。如果您正尝试管理整个应用程序进程的生命周期,请参阅 ProcessLifecycleOwner。
此接口从各个类(如 Fragment 和 AppCompatActivity)中抽象出生命周期的所有权,并允许编写与其一起使用的组件。任何自定义类都可以实现 LifecycleOwner
接口。
实现 LifecycleObserver 的组件与实现 LifecycleOwner 的组件可以无缝协作,因为 owner 可以提供生命周期,observer 可以注册观察。
对于位置跟踪示例,我们可以使 MyLocationListener
类实现 LifecycleObserver
,然后在 onCreate()
方法中使用 activity 的 Lifecycle 初始化它。这允许 MyLocationListener
类自给自足,意味着响应生命周期状态变化的逻辑在 MyLocationListener
而不是 activity 中声明。使各个组件存储自己的逻辑使得 activity 和 fragment 逻辑更易于管理。
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
// update UI
});
Util.checkUserStatus(result -> {
if (result) {
myLocationListener.enable();
}
});
}
}
一个常见的情况是,如果生命周期没有处于良好状态,则应避免调用某些回调。例如,如果回调在保存 activity 状态后运行 fragment 事务,则会触发崩溃,因此我们永远不会想要调用该回调。
为了简化此用例,Lifecycle 类允许其他对象查询当前状态。
class MyLocationListener implements LifecycleObserver {
private boolean enabled = false;
public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
void start() {
if (enabled) {
// connect
}
}
public void enable() {
enabled = true;
if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
// connect if not connected
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
void stop() {
// disconnect if connected
}
}
通过此实现,我们的 LocationListener 类可以完全感知生命周期。如果我们需要使用来自另一个 activity 或 fragment 的 LocationListener,我们只需要初始化它。所有设置和拆卸操作都由类本身管理。
如果库提供了需要使用 Android 生命周期的类,我们建议您使用生命周期感知组件。您的库客户端可以轻松地集成这些组件,而无需在客户端进行手动生命周期管理。
2.1 实现自定义 LifecycleOwner
支持库 26.1.0 及更高版本中的 fragment 和 activity 已实现 LifecycleOwner 接口。
如果您有一个想要创建 LifecycleOwner
的自定义类,则可以使用 LifecycleRegistry
类,但需要将事件转发到该类中,如以下代码示例所示:
public class MyActivity extends Activity implements LifecycleOwner {
private LifecycleRegistry lifecycleRegistry;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lifecycleRegistry = new LifecycleRegistry(this);
lifecycleRegistry.markState(Lifecycle.State.CREATED);
}
@Override
public void onStart() {
super.onStart();
lifecycleRegistry.markState(Lifecycle.State.STARTED);
}
@NonNull
@Override
public Lifecycle getLifecycle() {
return lifecycleRegistry;
}
}
三、生命周期感知组件的最佳实践
尽可能保持 UI 控制器(activity 和 fragment)的简洁。它们不应该试图获取自己的数据;而是应该使用 ViewModel 执行此操作,并观察 LiveData 对象以将更改通知给视图。
尝试编写数据驱动的 UI,您的 UI 控制器负责在数据更改时更新视图,或将用户操作通知给 ViewModel。
将您的数据逻辑放在 ViewModel 类中。ViewModel 应该充当 UI 控制器和应用程序其余部分之间的连接器。但要小心,ViewModel 不负责获取数据(例如,从网络获取)。相反,ViewModel 应调用适当的组件来获取数据,然后将结果提供回 UI 控制器。
使用数据绑定来维护视图和 UI 控制器之间的简洁接口。这使您可以使视图更具说明性,并最大限度地减少在 activity 和 fragment 中编写所需的更新代码。如果您更喜欢用 Java 编程语言执行此操作,请使用像 Butter Knife 这样的库来避免样板代码并具有更好的抽象。
如果您的 UI 很复杂,请考虑创建一个 presenter 类来处理 UI 修改。这可能是一项艰巨的任务,但它可以使您的 UI 组件更容易测试。
避免在 ViewModel 中引用 View 或 Activity 上下文。如果 ViewModel 的生存时间超过 activity(在配置更改的情况下),您的 activity 将发生泄漏,并且垃圾收集器未能正确处理。
四、生命周期感知组件的用例
生命周期感知组件可以使您在各种情况下更轻松地管理生命周期。一些例子如下:
在粗粒度和细粒度位置更新之间切换。使用生命周期感知组件可在您的位置应用程序可见时启用细粒度位置更新,并在应用程序位于后台时切换到粗粒度更新。LiveData 是一个生命周期感知组件,允许您的应用在用户更改位置时自动更新UI。
停止并开始视频缓冲。使用生命周期感知组件尽快启动视频缓冲,但推迟播放直到应用程序完全启动。您还可以使用生命周期感知组件在销毁应用程序时终止缓冲。
启动和停止网络连接。使用生命周期感知组件在应用程序处于前台时启用网络数据的实时更新(流式传输),并在应用程序进入后台时自动暂停。
暂停和恢复动画 drawables。当 app 在后台时使用生命周期感知组件处理暂停动画 drawables,并在 app 在前台后恢复动画 drawables。
五、处理 on stop 事件
当 Lifecycle 属于 AppCompatActivity 或 Fragment 时,Lifecycle 的状态将更改为 CREATED,并且在调用 AppCompatActivity 或 Fragment 的 onSaveInstanceState()
时分发 ON_STOP
事件。
当通过 onSaveInstanceState()
保存 Fragment 或 AppCompatActivity 的状态时,在调用 ON_START
之前,它的 UI 被认为是不可变的。保存状态后尝试修改 UI 可能会导致应用程序的导航状态不一致,这就是如果应用程序在保存状态后运行 FragmentTransaction
时,FragmentManager
会抛出异常的原因。相关的详细信息,请参阅 commit()。
如果观察者的关联生命周期没有 STARTED,LiveData 会通过禁止调用其观察者来防止这种边缘情况开箱即用。在背后,它在决定调用其观察者之前调用 isAtLeast()
。
不幸的是,在 onSaveInstanceState()
之后调用 AppCompatActivity
的 onStop()
方法,这留下了一个间隙,其中不允许 UI 状态更改但 Lifecycle 尚未移至 CREATED
状态。
为防止出现此问题,版本 beta2 及更低版本的 Lifecycle 类将状态标记为 CREATED
,而不调度事件,以便检查当前状态的任何代码都获得实际值,即使事件未调度,直到系统调用 onStop()
为止。
不幸的是,这个解决方案有两个主要问题:
在 API 级别 23 及更低级别,Android 系统实际上会保存 activity 的状态,即使它被另一个 activity 部分覆盖。换句话说,Android 系统调用
onSaveInstanceState()
但它不一定调用onStop()
。这会创建一个可能很长的时间间隔,即使无法修改其 UI 状态,观察者仍然认为生命周期处于活动状态。任何想要向 LiveData 类公开类似行为的类都必须实现 Lifecycle 版本
beta 2
及更低版本提供的解决方法。
注意:为了使此流程更简单并提供与旧版本更好的兼容性,从版本 1.0.0-rc1 开始,Lifecycle 对象被标记为
CREATED
,并且在调用onSaveInstanceState()
时调度ON_STOP
,而无需等待onStop()
方法的调用。这不太可能影响您的代码,但您需要注意这一点,因为它与 API 级别 26 及更低级别的 Activity 类中的调用顺序不匹配。
六、Additional resources
查看生命周期组件代码实验室并查看架构组件 BasicSample。Sunflower 演示应用程序中也使用了生命周期感知组件。