往期文章:
你好,我是朱涛。最近我打算系统化梳理下 Android Jetpack 的一些核心组件。今天让我们把 Lifecycle 作为开篇吧。
Lifecycle在整个Jetpack组件当中的地位非常特殊。
举个例子,其他的组件比如WorkManager,如果我们实际工作中用不上,那么我们不去学它是不会有什么问题的。Lifecycle不一样,只要我们是做Android开发的,我们就绕不开Lifecycle。
Activity里面有Lifecycle、Fragment里面也有、LiveData里面也有、ViewModel底层也用到了Lifecycle、使用协程也离不开Lifecycle。
可以说,Lifecycle也是很多Jetpack组件的基础,只有彻底理解并掌握好Lifecycle以后,我们才能更好的理解其他组件。
1. 为什么需要Lifecycle?
生命周期,是我们每个Android开发者一开始就会接触的概念。Activity有生命周期,Fragment有生命周期。也许你会有这样的疑问:既然Android原生组件已经有了生命周期概念了,为什么Android团队还要单独再定义一个Lifecycle组件呢?
这个问题的答案其实很简单。因为Android里组件之间的生命周期并不一致。如图:Activity生命周期。
Activity生命周期 (摘自官网)
Android的生命周期函数我们是再熟悉不过的。然后我们再来看看Fragment的生命周期,它的生命周期函数和Activity是不一样的。
Fragment生命周期 (摘自官网)
如上图所示,Fragment生命周期函数比Activity多了几个:onCreateView、onViewCreated、onViewStateRestore、onDestoryView。
最重要的是,Fragment生命周期、回调函数、Fragment内部View的生命周期,它们三者之间还有很复杂的对应关系。换句话说,Fragment的生命周期函数要比Activity复杂一些。
加之,Activity和Fragment结合的情况下,它们的生命周期行为在不同版本的Android系统上行为可能还会不一致。 这在某些边界条件下,还会引发一些难以排查的bug,进一步增加我们Android程序员的维护成本。
在我们实际开发工作当中,很多逻辑是Activity、Fragment通用的,很多功能在Activity当中能用,我们也希望这些功能可以直接挪到Fragment当中来。然而,当我们将Activity、Fragment当做一个整体来看的时候,它们的生命周期是非常凌乱而没有特定规律的。其实,对于我们开发者来说,真正关心的生命周期状态就那么几个:create、start、resume、pause、stop、destroy。
说了这么多,问题的根源就在于:Android内部的组件它们的生命周期不够统一,无形中增加了开发者的使用和维护成本。在计算机世界里,大部分问题都可以通过增加一个抽象层来解决。Android团队的做法就是推出了Lifecycle这个架构组件,用它来统一Activity、Fragment的生命周期行为。
Lifecycle与Activity、Fragment的关系
在有了Lifecycle以后,我们开发者在大部分情况下就只需要与Lifecycle打交道了。也就是说,我们编写的代码逻辑只需要依赖Lifecycle即可,根本不需要关心这个逻辑是在Activity里面用还是在Fragment里面用,还是在其他的地方用。借助Lifecycle,我们甚至可以定义自己的生命周期组件。
2. Lifecycle的使用场景
让我们看一个实际的开发场景,我们需要实现一个地理位置请求的功能,具体的功能我们可能会使用高德地图之类的第三方 SDK,不过我们代码里面肯定需要一个类似的管理类,比如:LocationManager。
internal class LocationManager(
private val context: Context,
private val callback: (Location) -> Unit
) {
fun start() {
// 使用高德之类的 SDK 请求地理位置
}
fun stop() {
// 停止
}
}
我们并不直接依赖第三方地图 SDK,而是定义一个管理类将其封装起来。这样做的好处是:我们随时可以更换第三方地图 SDK,而不影响到业务代码。
接着,我们需要在我们的Activity当中使用它。
class LifecycleExampleActivity : AppCompatActivity() {
private lateinit var locationManager: LocationManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_life_cycle_example)
locationManager = LocationManager(this) {
// 展示地理位置信息
}
}
override fun onStart() {
super.onStart()
locationManager.start()
}
override fun onStop() {
super.onStop()
locationManager.stop()
}
}
其实,类似这样的代码在我们实际开发场景中十分的常见:我们经常需要在onStart的时候做一些事情,在onStop的时候停止。
在一些复杂的业务场景当中,我们的生命周期函数当中会充斥着这样的代码。这样的代码模式会导致我们的代码难以维护,有时候不小心删除了 onStop 里的一段代码,或者 Git 合并代码的时候丢失了一行代码,这都会引起内存泄漏。
而Android团队创造的Lifecycle组件,则可以很好地解决这种问题。
2.1 引入Lifecycle依赖
要在Android工程中引入Lifecycle,我们只需要在Gradle文件中增加它对应的依赖即可。由于我们是使用Kotlin作为开发语言的,Android团队我们提供了对应KTX版本,这里面会有一些非常方便的扩展函数。
// Lifecycles 依赖
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
// 注解处理器支持
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
接下来我们就可以使用 Lifecycle 来重构我们的LocationManager了。这里主要主要有两种方式:第一种方式,是利用Lifecycle提供的注解。第二种方式是直接使用Lifecycle提供的Observer。
让我们看看第一种方式,使用OnLifecycleEvent注解。在正式使用之前,让我们看看它的定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnLifecycleEvent {
Lifecycle.Event value();
}
“RetentionPolicy.RUNTIME”代表了,OnLifecycleEvent注解是运行时可见的。
“ElementType.METHOD”则代表了:它只能作用于“方法”之上,这么设计的原因也是显而易见,毕竟我们需要在特定生命周期的时机调用特定的方法。 它的具体用法如下:
internal class LocationManagerAnnotation(
private val context: Context,
private val callback: (Location) -> Unit
): LifecycleObserver {
@OnLifecycleEvent(value = Lifecycle.Event.ON_START)
fun start() {
// 使用高德之类的 SDK 请求地理位置
}
@OnLifecycleEvent(value = Lifecycle.Event.ON_STOP)
fun stop() {
// 停止
}
}
我们定义了一个新的LocationManagerAnnotation,它实现了LifecycleObserver这个接口,同时,它的start、stop方法上增加了对应的OnLifecycleEvent注解。
需要注意的是,只有在Gradle文件中使用了Lifecycle提供的注解处理器,这种方式才会真正生效。由于我们的start函数被注解“@OnLifecycleEvent(value = Lifecycle.Event.ON_START)”修饰了,这个函数会在宿主生命周期的onStart执行时被调用。对应的,stop函数则会在宿主生命周期的onStop被执行。这样一来,这个程序的逻辑就和我们之前的一模一样了。
借助Lifecycle,我们再也不需要在Activity生命周期函数当中去调用一堆start、stop函数了,更重要的是,我们不可能忘记调用stop函数,这也意味着不会因此引发内存泄漏。
Lifecycle还有另一种使用方式,直接使用它提供的DefaultLifecycleObserver。让我们来看看它的定义:
public interface LifecycleObserver { }
interface FullLifecycleObserver extends LifecycleObserver {
void onCreate(LifecycleOwner owner);
void onStart(LifecycleOwner owner);
void onResume(LifecycleOwner owner);
void onPause(LifecycleOwner owner);
void onStop(LifecycleOwner owner);
void onDestroy(LifecycleOwner owner);
}
public interface DefaultLifecycleObserver extends FullLifecycleObserver {
default void onCreateLifecycleOwner owner) {}
default void onStart(LifecycleOwner owner) {}
default void onResume(LifecycleOwner owner) {}
default void onPause(LifecycleOwner owner) {}
default void onStop(LifecycleOwner owner) {}
default void onDestroy(LifecycleOwner owner) {}
}
从上面的代码我们能看到:DefaultLifecycleObserver是一个接口,它继承自另一个接口FullLifecycleObserver,而FullLifecycleObserver继承自一个空接口:LifecycleObserver。需要注意的是,DefaultLifecycleObserver使用了Java 1.8 “接口默认方法”这一特性,这让我们在实现DefaultLifecycleObserver的时候可以选择性的去实现部分接口。
具体用法如下:
internal class LocationManagerDefault(
private val context: Context,
private val callback: (Location) -> Unit
): DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
start()
}
override fun onStop(owner: LifecycleOwner) {
stop()
}
private fun start() {
// 使用高德之类的 SDK 请求地理位置
}
private fun stop() {
// 停止
}
}
我们使用DefaultLifecycleObserver而不是FullLifecycleObserver的原因是:如果我们使用FullLifecycleObserver,我们将被迫实现该接口所有的方法。但我们实际上只关心onStart、onStop两个生命周期,因此DefaultLifecycleObserver才是正确的选择。
说完了LocationManager的变化,我们再来看看我们在Activity当中如何使用它们,其实它们的用法非常简单,我们只需要在Activity当中构造出对应的实例,然后再将其作为Observer添加到对应Lifecycle里即可:
class LifecycleExampleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_life_cycle_example)
val locationManager = LocationManagerDefault(this) {
// 展示地理位置信息
}
lifecycle.addObserver(locationManager)
}
}
可以看到,我们已经将外部Activity的onStart、onStop回调删除了。这样的代码更简洁了,更容易维护,也更不容易出错了。另外,虽然以上代码例子使用的是LocationManagerDefault,但实际上LocationManagerAnnotation也可以同样这么使用。
这里的addObserver方法,起到了关键的作用。与此同时,这行代码也牵涉到了一个重要的设计模式,叫做:“观察者模式”。这行代码的含义是这样的:Activity作为生命周期的“提供方”,它可以对外传递生命周期的事件。而我们的locationManager实现了DefaultLifecycleObserver的接口,它需要接收生命周期事件,对应的作为了“接收方”。
以上的“提供方”、“接收方”都是我自己造的名字,在设计模式当中,它们的名字分别是“被观察者”和“观察者”。它们两者的关系,就有点像:“公众号”与“粉丝”的关系。“公众号”提供文章,“粉丝”则接收文章。
介绍完了观察者模式,我们再回过头看看上面的代码。
如果你足够细心,你会惊喜的发现,我们的LocationManagerDefault和LocationManagerAnnotation并没有直接依赖于Activity或者Fragment。这就意味着,它们不仅仅只是用于Activity,它们同样也可以用到Fragment里面去。准确来说,只要是实现了LifecycleOwner的组件,我们都可以直接用了。
因为Lifecycle这个框架已经帮我们封装好了所有的实现,我们只需要关心我们所需要的生命周期函数即可。我们定义的类到底是用在Activity当中还是Fragment当中,对于我们的Manager类已经不重要了。这种设计也大大提升了我们架构的灵活性。
3. 感知Service、Application生命周期
Lifecycle组件除了可以感知Activity、Fragment生命周期以外,它还可以用于感知Service生命周期,以及Application生命周期。 想要感知Service生命周期,我们需要单独再依赖一个库:
implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version"
在理解了Lifecycle在Activity、Fragment当中的用法后,我们使用类似的方式也可以将其应用到Service当中。所以,这里就不重复介绍了。
我们来重点看看如何感知Application生命周期。在没有Lifecycle之前,我们想要实现应用前后台切换的监听是比较困难的。我们需要监听每一个Activity的生命周期,自己在内存当中维护一个堆栈信息,根据一定的计算来判断应用是否发生了前后台的切换。这种方式不仅麻烦,还非常的不可靠。
现在,Android在已经提供了一套完整的解决方案,它不仅易用,也更加的可靠。
3.1 ProcessLifecycleOwner
想要监听安卓应用的前后台切换,我们需要ProcessLifecycleOwner,它是Lifecycle下面一个单独的SDK所提供的。所以,在已有的依赖基础上,我们需要引入其对应的依赖:
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
其实监听Application生命的方式和Activity的也是类似的,我们同样可以使用注解或者DefaultLifecycleObserver。在这里我们以DefaultLifecycleObserver作为例子,我们需要创建一个ProcessLifecycleObserver,然后我们只需要在我们的Application的onCreate函数当中调用即可:
class AppApplication: BaseApplication() {
override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(ProcessLifecycleObserver())
}
}
我们定义了ProcessLifecycleObserver,使其实现了DefaultLifecycleObserver这个接口,并且复写了它里面的所有生命周期函数。
class ProcessLifecycleObserver: DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {}
override fun onStart(owner: LifecycleOwner) {}
override fun onResume(owner: LifecycleOwner) {}
override fun onPause(owner: LifecycleOwner) {}
override fun onStop(owner: LifecycleOwner) {}
override fun onDestroy(owner: LifecycleOwner) {}
}
虽然监听Application生命周期的用法和Activity的十分类似,但是ProcessLifecycleObserver当中的各个生命周期函数的意义和我们之前在Activity当中的并不一样。以下这几点我们需要尤为注意:
- onCreate代表应用启动,在整个Application生命周期当中,它只会调用一次。
- onStart会在应用从后台变为前台的时候调用,它会被调用多次。
- onResume会在应用从后台变为前台的时候调用,它会被调用多次,它与onStart的区别跟Activity的类似。
- onPause会在应用从前台变为后台的时候调用,它会被调用多次。
- onStop会在应用从前台变为后台的时候调用,它会被调用多次,它与onPause的区别跟Activity的类似。
- onDestroy代表了应用进程被销毁,不过它并不会被调用,开发者一般也不关心这个事件。 当我们在应用当中按下HOME键,onPause和onStop会被先后调用;对应的,这时候再回到应用,onStart、onResume会被先后调用。
- 由于Activity在发生横竖屏切换的时候Activity生命周期也会发生变化。所以,为了防止这种情况也被认为是切换了前后台,ProcessLifecycleOwner预留了一些缓冲时间。因此,以上的前后台切换的回调并不会马上执行。也就是说,当我们在应用当中按下HOME键以后,onPause和onStop并不会马上就被调用,它们会延迟一小段时间执行。
4. Lifecycle结合Kotlin协程
在前面的章节当中,我们已经学习过Kotlin协程。我们也知道,如果要启动一个协程,我们首先要有一个协程作用域(CoroutineScope)。在前面的例子当中,我们都是直接或间接使用的 GlobalScope。事实上,这样的方式在Android当中是应该被尽量避免的。
Kotlin协程之所以强制要求使用作用域,就是希望利用好它的“结构化并发”的特点:将协程任务统一放在一个作用域当中管理,方便及时取消。然而,GlobalScope启动的协程任务,是没有办法统一取消的,你只能记录它的每一个Job并且缓存起来,然后在需要的时候取消它。所以说,GlobalScope打破了“结构化并发”的规则。
加之,Android本身是一个对内存、CPU、电量资源非常敏感的系统,这些资源都是非常宝贵的。任何多余的协程任务都应该及时的取消,防止浪费。我们常常说的内存泄漏就是一种对内存的浪费。在Android当中,直接使用GlobalScope也会导致协程的泄漏。
那么,在Android当中,我们应该怎么做呢?如果我们要在Activity和Fragment当中启动协程,我们应该使用lifecycleScope。
class LifecycleCoroutineExampleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// ..
lifecycleScope.launch {
load()
}
}
private suspend fun load() {
// do something
}
}
如果你去看Activity或Fragment的源码,你会发现,它们都实现了LifecycleOwner这个接口。而lifecycleScope则是LifecycleOwner这个类的扩展属性。这就是为什么我们可以直接在Activity和Fragment当中直接使用lifecycleScope的原因了。
lifecycleScope,顾名思义,它就是一个与当前宿主生命周期息息相关的协程作用域。以我们的代码为例,当LifecycleCoroutineExampleActivity被销毁的时候,所有通过lifecycleScope创建的协程任务都将被统一取消。
想象一个这样的场景:用户快速地在页面A与页面B之间来回切换,这会导致页面B当中的协程不停地启动,这当中可能有网络请求、可能有数据库请求、可能有CPU密集型的计算,这都是非常耗资源的操作。如果我们在页面B销毁的时候不将这些协程取消,这会导致非常严重的资源浪费。
这也是Android官方为我们提供lifecycleScope的原因。
另外,还有一个协程初学者容易犯的错误,需要引起重视:
当我们使用“lifecycleScope.launch {}”的时候,由于我们没有指定Dispatchers,所以花括号内的协程代码将会默认执行在主线程。这也就意味着,我们的“load”函数体内不能直接执行耗时任务,因为这会导致我们的主线程阻塞,引起ANR,这是非常危险的。对于这种情况,我们要么在load函数当中使用withContext,要么就需要在launch函数当中指定Dispatchers线程池。
结尾
看到这里,相信你对 Jetpack Lifecycle 的用法已经了然于胸了。 好了,关于 Lifecycle 的用法, 我们今天就分享到这里,下一篇我们继续讲讲 Lifecycle 的底层原理。