管理 activity 和 fragment 的生命周期
参看 https://developer.android.com/topic/libraries/architecture/lifecycle
使用生命周期感知组件来处理生命周期
生命周期感知组件执行操作来响应另一个组件(例如 Activity 或 Fragment )的生命周期变更。这些组件可以帮你写出更易于组织更轻量级更易于维护的代码。
一种常见的模式就是在 Activity 和 Fragment 的生命周期方法中实现相关的操作。但只这种模式会导致代码组织不良和各种错误。通过使用生命周期感知组件,你可以将组件代码移出生命周期的方法,并且放到组件当中去。
android.arch.lifecycle
包提供了类和借口,可以让你够贱生命周期感知组件,这些组件可以根据 Activity 和 Fragment 的当前生命周期状态自动调节其行为。
Android 框架中定义的大多数的应用程序组件都附加了生命周期,生命周期由操作系统或者进程中运行的框架代码管理。他们是 Android 工作原理的核心,你的 APP 必须尊重它们。不这样做的话可能会触发内存泄漏甚至应用程序崩溃。
想象我们有一个 Activity 显示当前的位置信息。一个最普通的实现大概应该是这样:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41class 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;
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, (location) -> {
// update UI
});
}
public void onStart() {
super.onStart();
myLocationListener.start();
// manage other components that need to respond
// to the activity lifecycle
}
public void onStop() {
super.onStop();
myLocationListener.stop();
// manage other components that need to respond
// to the activity lifecycle
}
}
即使这个例子看起来没什么问题,但在真实的应用程序中,到最后会有很多的调用来管理 UI 和其他组件一相应生命周期的当前状态。管理多个组件需要在生命周期方法中放置大量代码,例如 onStart()
和 onStop()
,这样会越来越难维护。
此外,没有百分之百的保证组件能在 Activity 或者 Fragment 停止之前完成启动。尤其是在我们需要执行长时间运行的操作的时候,例如在 onStart()
中进行某些配置检查。
1 | class MyActivity extends AppCompatActivity { |
android.arch.lifecycle
包提供了相应的类和接口来帮助你以弹性和隔离的方式解决这些问题。
LifeCycle
LifeCycle
类持有着组件的生命周期状态信息,并允许其他对象订阅状态。LifeCycle
主要使用两个枚举来跟踪其关联的组件的生命周期状态。
Event
从框架和Lifecycle
类调度的生命周期事件。这些时间映射到Activity
和Fragment
的生命周期方法上。State
Lifecycle
对象跟踪的组件的当前状态。
将 state
看作图形的节点,将 event
看作节点之间的连线。
一个类可以通过给它的方法添加注解来监听组建的生命周期状态。然后你可以通过点用 LifeCycle
类的 addObserver()
方法并传递观察者的示例来添加观察者,示例如下:1
2
3
4
5
6
7
8
9
10
11
12
13public class MyObserver implements LifecycleObserver {
(Lifecycle.Event.ON_RESUME)
public void connectListener() {
...
}
(Lifecycle.Event.ON_PAUSE)
public void disconnectListener() {
...
}
}
myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
上示示例中, myLifecycleOwner
对象实现了 LifecyclerOwner
接口
LifecycleOwner
LifecycleOwner
是一个具有单一方法的接口,用来表示这个类具有生命周期。它只有一个方法, getLifecycle()
。如果你想要管理整个应用进程的生命周期,请看 ProcessLifecycleOwner
。
这个接口从各个类(如 AppCompatActivity
或者 Fragment
)中抽象出生命周期的所有关系,允许编写可以跟其一同工作的组件。任何自定义的应用程序类都可以实现 LifecycleOwner
接口。
实现了 LifecycleObserver
的组件可以与实现了 LifecycleOwner
的组件无缝合作,因为 owner 可以提供生命周期, observer 可以观察生命周期。
就刚才的位置跟踪的例子而言,我们可以让 MyLocationListener
类实现 LifecycleObserver
,然后在 Activity 的 onCreate()
方法中初始化。这样做可以让 MyLocationListener
类自给自足,也就是相应生命周期状态变化的逻辑在 MyLocationListener
中而不是在 Activity 中声明。使各个组件存储自己的逻辑似的 Activity 和 Fragment 的逻辑更易于管理。1
2
3
4
5
6
7
8
9
10
11
12
13
14class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
// update UI
});
Util.checkUserStatus(result -> {
if (result) {
myLocationListener.enable();
}
});
}
}
一个常见的用例是,如果 Lifecycle
现在处于一个不合适的状态的时候,应该避免调用其回调。例如,如果回调在 Activity 保存状态后尝试执行 Fragment 事务,就会触发崩溃 ,因此我们永远也不想执行这些回调。
为了简化用例, Lifecycle
类允许其他对象查询当前的状态:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class MyLocationListener implements LifecycleObserver {
private boolean enabled = false;
public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
...
}
(Lifecycle.Event.ON_START)
void start() {
if (enabled) {
// connect
}
}
public void enable() {
enabled = true;
if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
// connect if not connected
}
}
(Lifecycle.Event.ON_STOP)
void stop() {
// disconnect if connected
}
}
通过这样的实现,我们的 LocationListener
类可以完全识别生命周期。如果我们需要在别的 Activity 或者 Fragment 里面使用 LocationListener
, 我们只需要实力化它就好了。所有的设置和写在操作都有类本身管理。
如果库提供了需要使用 Android 生命周期的类,我们建议你使用生命周期感知组件,你的继承库会轻松的集成这些组件而无需在客户端进行生命周期管理。
Implementing a custom LifecycleOwner
在 Support Library 26.1.0
及之后版本的 Fragment 和 Activity 都已经实现了 LifecycleOwner
接口。
如果你有一个自定义类需要实现 LifecyclerOwner
,你可以使用 LifecycleRegistry
,但是你需要在类中手动转发事件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class MyActivity extends Activity implements LifecycleOwner {
private LifecycleRegistry mLifecycleRegistry;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLifecycleRegistry = new LifecycleRegistry(this);
mLifecycleRegistry.markState(Lifecycle.State.CREATED);
}
public void onStart() {
super.onStart();
mLifecycleRegistry.markState(Lifecycle.State.STARTED);
}
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
}
Best practices for lifecycle-aware components
- 保持 UI controller( Activity 或者 Fragment ) 尽可能的简洁,他们不应该自己获取数据,使用
ViewModel
去做,并贯彻一个LiveData
对象来将变更反映到 UI 上。 - 尝试编写数据驱动的 UI , 其中 UI controller 负责在数据更改时更新 UI ,或是将用户的交互反馈给
ViewModel
- 将数据逻辑放在
ViewModel
类中。ViewModel
应该充当 UI controller 和应用其余部分之间的桥梁。但是要小心,ViewModel
不负责获取数据(从网络获取、从 DB 获取),相反,ViewModel
应调用适当的组件来获取数据,然后将结果提供回 UI 控制器。 - 使用数据绑定库来维护视图和 UI controller 的干净代码。这可以是视图更具声明性,并最大限度的减少 Activity 和 Fragment 中编写所需的更新代码。如果您更习惯使用 Java 来写,请使用
ButtterKnife
这样的库莱尽可能避免模板代码并且具有更好的抽象。 - 如果你的 UI 很复杂,请考虑创建一个
Presenter
类来处理 UI 的修改。这可能是一个艰巨的任务,但是这可以使你的 UI 组件更容易测试。 - 避免在
ViewModel
中引用View
或者Activity
context ,如果ViewModel
的生命周期超过了 Activity 的话,这会造成 Activity 泄漏不能被 GC 正确处理。
Use cases for lifecycle-aware components
生命周期感知组件可以使你在各种情况下更轻松的管理生命周期,一些例子:
- 在高精度和低精度位置更新服务之间切换。使用生命周期感知组件可以在您的位置应用程序课间时使用高精度位置更新,在应用程序处于后台的时候切换到低精度位置更新。
LiveData
是一个生命周期感知组建,允许你的应用在用户位置更改的时候自动更新 UI。 - 开始和停止视频缓冲。是用生命周期感知组建尽快启动视频缓冲,但是直到应用程序完全启动的时候才开始播放。你还可以使用生命周期感知组件在应用程序终止的时候终止缓冲。
- 开始和停止网络连接。使用生命周期感知组件在应用程序在前台的时候启用网络数据并且实时更新,并在应用程序进入后台之后自动暂停。
- 暂用和恢复 animated drawables 。当应用程序在后台时,使用生命周期感知组件处理暂停 animated drawable ,并在应用程序位于前台后恢复。
Handling on stop events
对于 AppCompatActivity
和 Fragment
来说,在 AppCompatActivity
和 Fragment
的 onSaveInstanceState()
方法被调用的时候, LifeCycle
的 state 会变为 CREATED
,并且会触发 ON_STOP
事件。
当 Fragment
和 AppCompatActivity
的状态通过 onSaveInstanceState()
保存了的时候,我们会认为直到 ON_START
之前 UI 都是不可变的了。在状态保存后企图修改 UI 会导致应用程序的导航状态不一致,这就为什么 FragmentManager
在保存状态后执行 FragmentTransaction
时会抛出异常了。
在如果观察者的生命周期还没有到 STARTED
的时候, LiveData
会通过禁止其调用观察者来避免上述情况发生。实际上,在他决定调用观察者之前,会调用 isAtLeast()
。
不幸的是, AppCompatActivity
的 onStop()
方法在 onSaveInstanceState()
之后调用,这样会导致有一小段时间 UI 状态不允许修改但是 Lifecycle
的状态还没有从 STARTED
变成 CREATED
。
为了解决上述问题, beta2
版本以及更低版本的 Lifecycle
类将上述情况的状态标记为 CREATED
,即使还没分发 ON_STOP
的事件。这样以便于获取的状态的代码能够在 onStop()
被调用之前未发出 ON_STOP
事件的情况下正常工作。
不幸的是,这个解决方案有两个主要的问题:
- 在
API 23
以及更低的情况下, Android 系统会在 Activity 被另一个 Activity 部分遮挡的情况下去保存状态。换句话说,就是 Android 系统会在不调用 onStop() 的情况下去调用onSaveInstanceState()
。这会导致在很长的一段时间内,即使无法修改 UI 状态,观察者仍然会认为生命周期处于活跃状态。 - 任何想要实现
LiveData
类类似行为的类都要去重新实现一边LifeCycle beta2
的补丁代码。
为了简化流程并且提供更好的旧版本的兼容性,从
1.0.0-rc1
版本开始,LifeCycle
对象会在onSaveInstanceState()
调用之后被标记为CREATED
,并且下发ON_STOP
事件,不再等待onStop()
调用。虽然这可能不太影响你的代码,但是你需要注意到它与API 26
以及更低版本的 Activity 的调用顺序不匹配。