Handling Lifecycles
处理生命周期
附官网超链接:go
由于官网会定时更新,故本篇翻译于2017年12月9日。
Handling Lifecycles with Lifecycle-Aware Components
使用生命周期感知组件来处理生命周期
生命周期感知组件会响应另一个组件的生命周期状态变化,来做相应的事件处理,比如(Activitys和Fragments),这些组件有助于你组织你的代码,使代码变得更简洁,更利于后期维护。
通常我们会在在Activity或Fragment的生命周期中实现依赖组件的动作,比如在onCreate()中做相应的初始化工作,这种方式不利于代码的组织,提高耦合性,也会导致错误的扩散。然后,通过这些生命周期感知组件,可以将上述功能的代码从所依赖的组件的生命周期方法中移出,放入组件内部实现。
在android.arch.lifecycle包中提供了一些类和接口用于构建生命周期感知的组件,这些组件会自动基于当前activity/fragment的生命周期作出状态的调整,比如数据更新,加载等等。
注:如何导入android.arch.lifecycle这个包请查看:Android Arch Comp - Adding Components to your Project
应用中使用的大部分由Android框架定义的组件都有对应的生命周期与之关联。这些生命周期由操作系统或者进程所运行的框架代码管理。这些组件是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
从框架和生命周期类派生的生命周期事件。 这些事件对应Activity和Fragment中的生命周期回调事件。
State
生命周期对象跟踪的组件的当前状态。
试着将状态看作是试图的节点,而事件则是作为节点间的边界。
一个类可以通过向其方法添加注释来监视组件的生命周期状态。然后,可以通过调用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
LifecycleOwner是一个只有一个方法的接口,表示该类拥有生命周期。继承该接口的类必须实现getLifecycle()方法。如果试图管理整个应用进程的生命周期,可以查看ProcessLifecycleOwner.
该接口从各个类(如Fragment和AppCompatActivity)抽象生命周期的所有权,并允许编写与它们一起工作的组件。 任何自定义Application都可以实现LifecycleOwner接口。
实现LifecycleObserver的组件与实现LifecycleOwner的组件可以无缝的进行协作,因为持有生命周期的组件可以提供生命周期为观察者进行注册观察。
比如上述的例子,我们可以将MyLocationListener类实现LifecycleOberver接口,并且在Activity的生命周期方法onCreate()中进行初始化。这允许MyLocationListener类是自给自足的,也意味着对生命周期状态变化做出反应的逻辑是在MyLocationListener中声明的,而不是在Activity中,也就实现了解耦操作。
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生命周期的类,我们建议您使用生命周期感知组件。 开源库的使用者可以在客户端无需手动生命周期管理即可轻松集成这些组件。
Implementing a custom LifecycleOwner
实现一个自定义的生命周期持有者。
Support 26.1.0及以后的所有Fragment和Activity均已经实现了LifecycleOwner接口。
如果想要自定义一个实现LifecycleOwner接口的类,可以使用LifecycleRegistry,必须将生命周期转发到代理类中,如下代码:
public class MyActivity extends Activity implements LifecycleOwner {
private LifecycleRegistry mLifecycleRegistry;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLifecycleRegistry = new LifecycleRegistry(this);
mLifecycleRegistry.markState(Lifecycle.State.CREATED);
}
@Override
public void onStart() {
super.onStart();
mLifecycleRegistry.markState(Lifecycle.State.STARTED);
}
@NonNull
@Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
}
我个人还是不建议使用这种方法。
Best practices for lifecycle-aware components
生命周期感知组件的最佳实践
- 让UI控制器代码尽可能的简单(比如Activity/Fragment),控制器不该去管理UI数据,使用ViewModel来处理,并观察LiveData对象以响应数据的变化,并通知试图进行更新。
- 用数据去驱动UI更新,而UI控制器的责任就是当数据变化时,及时去更新UI控件,或将用户交互事件通知给ViewModel。
- 将数据逻辑放入到ViewModel中。ViewModel应该作为你的UI控制器和其他应用程序之间的连接器。但要小心,ViewModel不负责提取数据(比如网络请求数据)。 相反,ViewModel应调用相应的组件来获取数据,然后将结果提供给UI控制器。
- 使用Data Binding来保持视图和UI控制器之间的简洁接口。 这样可以让你的视图更具说明性,并最大限度地减少需要在Activity和Fragment中写入的更新代码。 如果你喜欢用Java编程语言来完成这个工作,可以使用像Butter Knife这样的库来避免样板代码,并且有一个更好的抽象。
- 如果UI界面很复杂,可以考虑创建一个Presenter类来处理UI修改。 这可能是一项艰巨的任务,但它可以使UI组件更容易测试。
- 避免在ViewModel中应用View或者Activity的上下文,如果ViewModel超出Activity生命周期(在配置更改的情况下),则会导致Activity泄漏,并且垃圾收集器无法正确回收。
Use cases for lifecycle-aware components
生命周期感知组件可以让在各种个样的场景中管理生命周期。比如说:
- 在粗略和细粒度的位置更新之间切换。 使用生命周期感知的组件在位置应用程序可见时启用细粒度的位置更新,并在应用程序处于后台时切换到粗粒度更新。 LiveData是一种生命周期感知型组件,允许应用程序在使用更改位置时自动更新UI。
- 停止并开始视频缓存。 尽可能使用支持生命周期的组件来启动视频缓冲,但延迟播放直到应用程序完全启动。 也可以使用生命周期感知组件在应用程序销毁时停止缓存。
- 启动和停止网络连接。 使用生命周期感知组件可以在应用程序处于前台时实时更新(流式传输)网络数据,并在应用程序进入后台时自动暂停。
- 暂停和恢复动画绘制。 使用生命周期感知组件来处理在应用程序处于后台时暂停动画的可绘画,并在应用程序处于前台后恢复绘制。
Handling on stop events
处理停止事件
当生命周期属于AppCompatActivity或Fragment时,生命周期的状态从CREATED到ON_STOP的事件分发,是在onSaveInstanceState()中调用的。
当Fragment或AppCompatActivity的状态通过onSaveInstanceState()保存时,UI被认为是不可变的,直到ON_START被调用。尝试在保存状态后修改用户界面可能会导致应用程序的导航状态不一致,这就是为什么如果应用程序在保存状态后运行FragmentTransaction,FragmentManager将抛出异常。有关详细信息,请参阅commit()。
如果观察者的关联生命周期还没有被启动,LiveData可以通过避免调用其观察者来防止这种边缘情况的出现。在这样的场景下,在决定是否通知观察者之前调用isAtLeast()。
不幸的是,AppCompatActivity是在onSaveInstanceState()之后调用onStop()方法,这会在UI状态更改不被允许但生命周期尚未移至CREATED状态的情况下留下空隙。
为了避免这个问题,beta2版本中的生命周期类将lower状态标记为CREATED,而不分派事件,以便任何检查当前状态的代码都能获得真实值,即使事件未被分派,直到onStop()被调用系统。
不幸的是,这个解决方案有两个主要问题
- 在API23及以下,Android系统实际上保存着Activity的状态,即使它被另一活动部分覆盖。换句话说,Android系统调用onSaveInstanceState(),但不一定调用onStop()。这将创建一个潜在的长时间间隔,即使其UI状态不能被修改,观察者仍认为生命周期是活动的。
- 任何希望向LiveData类公开类似行为的类都必须实现生命周期版本2和更低版本提供的解决方法。
注意:为了使此流程更简单,并提供与旧版本的更好的兼容性,从版本1.0.0-rc1开始,Lifecycle对象被标记为CREATED,并且在调用onSaveInstanceState()而不等待onStop()调用时调度ON_STOP。这不太可能影响您的代码,但需要了解这一点,因为它与API级别26及更低级别的Activity类中的调用顺序不匹配。