本篇文章主要是讲解如何使用lifecycle创建协程 、源码解析以及lifecycle在协程中的应用。
private fun test55() {
//创建协程
lifecycleScope.launch {
}
}
如上很简单,如果想要和Activity的生命周期
绑定,还有下面一系列方法供你使用:
private fun test55() {
//创建协程
lifecycleScope.launchWhenResumed { }
lifecycleScope.launchWhenCreated { }
lifecycleScope.launchWhenStarted { }
}
只有当对应生命周期执行了,才会执行协程块中的代码。
lifecycleScope
是什么# LifecycleOwner.kt
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
通过调用链看到,它是lifecycle提供的一个扩展属性
:
val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
//1.
val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
if (existing != null) {
return existing
}
//2.
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main.immediate
)
//3.
if (mInternalScopeRef.compareAndSet(null, newScope)) {
//4.
newScope.register()
return newScope
}
}
}
1.mInternalScopeRef
是lifecycle内部的一个属性:
AtomicReference
初次调用其get()
方法肯定是一个空的,所以会走到2处。
2.创建一个LifecycleCoroutineScopeImpl
对象,看下它是个啥:
再往下走:
可以看到这就是一个CoroutineScope
的子类,所以我们这样就创建了一个协程作用域对象
,并且指定:
SupervisorJob
,这样子job间发生异常而不会互相影响,阻止向上传递异常;Dispatchers.Main.immediate
,默认分发到主线程执行在这里顺便说下Dispatchers.Main.immediate
和Dispatchers.Main
的区别:
首先这两个都是指定把协程块内容分发到主线程中执行,但是前者多了个
immediate
,这其实是一种优化手段,我们看下官方文档怎么说:
简单说,如果创建协程块的线程和要指定的调度线程都是主线程,使用immediate
的就不需要额外使用分发器进行分发了,这算是一个优化小手段
。
3.将创建的这个协程作用域对象通过CAS
写入lifecycle的mInternalScopeRef
,这样当 lifecycleScope.launch
在此获取协程作用域就不会进行重复创建了,直接从mInternalScopeRef
获取即可。
综上所述, lifecycleScope
就是个协程作用域对象,用来在特定job和主线程中执行协程块代码逻辑。
4.注册观察者,当界面销毁时取消所有协程的执行
LifecycleCoroutineScopeImpl
本身就是观察者对象,所以看下register()
就是注册观察者:
fun register() {
launch(Dispatchers.Main.immediate) {
if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
} else {
coroutineContext.cancel()
}
}
}
同时LifecycleCoroutineScopeImpl
重写了onStateChanged()
方法:
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
lifecycle.removeObserver(this)
coroutineContext.cancel()
}
}
就是为了方便当界面销毁了移除观察者,并取消所有的子协程代码块的执行,避免内存的泄漏。
这个问题很简单,大家应该都可以想到:协程借助于添加观察者LifecyclObserver
的方式实现对Activity生命周期的监听,这里主要通过两个非常典型的源码例子进行分析:
lifecycleScope.launchWhenXXX
:可以指定在特定的生命周期执行协程协程代码,当不再该状态时就会暂停协程块的执行
lifecycle.repeatOnLifecycle
:可以指定在特定的生命周期执行协程协程代码,当不再该状态时就会取消协程块的执行
请大家一定记住这两者的区别,当我们使用MutableStateFlow
、MutableSharedFlow
时,在协程作用域添加观察者时强烈推荐使用第二种方式,之后应该会写一篇文章进行分析的。
lifecycleScope.launchWhenXXX
我看先看下调用链分析,这里以launchWhenResumed
举例:
public fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenResumed(block)
}
往下走:
public suspend fun Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T {
return whenStateAtLeast(Lifecycle.State.RESUMED, block)
}
最终走到:
public suspend fun Lifecycle.whenStateAtLeast(
minState: Lifecycle.State,
block: suspend CoroutineScope.() -> T
): T = withContext(Dispatchers.Main.immediate) {
val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job")
//1.
val dispatcher = PausingDispatcher()
val controller =
//2\. 重点关注这个对象
LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job)
try {
withContext(dispatcher, block)
} finally {
controller.finish()
}
}
所以所有的lifecycleScope.launchWhenXXX
都会走到whenStateAtLeast()
这个方法:
1.创建可以暂停的分发器,就是通过这个对象实现在指定生命周期执行协程代码块,超出该生命周期暂停协程代码块的执行
canRun()
方法是重点关注的,当暂停协程块执行时,该方法就会返回true,重新进行分发。
2.LifecycleController
实现Activity生命周期监听,并借助上面创建的分发器,实现协程代码块的暂停执行和恢复执行,深入该类源码查看:
internal class LifecycleController(
private val lifecycle: Lifecycle,
private val minState: Lifecycle.State,
private val dispatchQueue: DispatchQueue,
parentJob: Job
) {
private val observer = LifecycleEventObserver { source, _ ->
if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) {
handleDestroy(parentJob)
} else if (source.lifecycle.currentState < minState) {
//1.
dispatchQueue.pause()
} else {
//2.
dispatchQueue.resume()
}
}
}
一目了然,如果当前生命周期状态小于传入的minState
,就调用DispatchQueue.pause()
暂停协程代码块执行,也就是挂起
,大于等于指定的生命周期就调用resume()
方法恢复执行。
综上所诉,该方法lifecycleScope.launchWhenXXX当小于指定生命周期状态时,是暂停协程的执行,而不是取消
。
lifecycle.repeatOnLifecycle
直接看下源码:
public suspend fun Lifecycle.repeatOnLifecycle(
state: Lifecycle.State,
block: suspend CoroutineScope.() -> Unit
) {
coroutineScope {
withContext(Dispatchers.Main.immediate) {
//1.
if (currentState === Lifecycle.State.DESTROYED) return@withContext
var launchedJob: Job? = null
var observer: LifecycleEventObserver? = null
try {
//2.
suspendCancellableCoroutine { cont ->
val startWorkEvent = Lifecycle.Event.upTo(state)
val cancelWorkEvent = Lifecycle.Event.downFrom(state)
observer = LifecycleEventObserver { _, event ->
//3.
if (event == startWorkEvent) {
launchedJob = [email protected] {
block()
}
return@LifecycleEventObserver
}
//4.
if (event == cancelWorkEvent) {
launchedJob?.cancel()
}
//5.
if (event == Lifecycle.Event.ON_DESTROY) {
cont.resume(Unit)
}
}
//6.
[email protected](observer as LifecycleEventObserver)
}
} finally {
//7.
launchedJob?.cancel()
observer?.let {
[email protected](it)
}
}
}
}
}
上面的代码是经过精简过的(也没啥可以精简的),我们来一步步进行分析:
当页面状态处于销毁DESTROYED
状态,直接return。
suspendCancellableCoroutine
是用来捕捉传递过来的Continuation
,这样我们就可以决定挂起的协程什么时候可以恢复执行,比如delay()
的实现机制就是如此。
PS: 插一嘴,不管是
suspendCancellableCoroutine
还是coroutineScope
底层方法都是通过suspendCoroutineUninterceptedOrReturn
实现,但是由于这个方法不正确使用会对代码产生安全影响,比如栈溢出,所以官方提供了前面两个封装方法。
创建了一个LifecycleEventObserver
对象,用来绑定界面生命周期,当达到指定执行的生命周期后,就创建一个协程执行我们传递过来的代码块.
当小于当前指定的生命周期状态,就直接取消协程
执行(请注意,和上面的whenStateAtLeast
区别)。
当界面销毁时,才将挂起的协程恢复执行
,所以这里我们就实现了根据具体场景来决定什么时候恢复协程执行。
6处和7处就是注册和反注册观察者。
综上所诉,该方法lifecycle.repeatOnLifecycle当小于指定生命周期状态时,是取消协程的执行,而不是暂停
。
作者:长安皈故里
链接:https://juejin.cn/post/7127057225247359012
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……
1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程
1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》
从一个膜拜大神的 Demo 开始
Kotlin 写 Gradle 脚本是一种什么体验?
Kotlin 编程的三重境界
Kotlin 高阶函数
Kotlin 泛型
Kotlin 扩展
Kotlin 委托
协程“不为人知”的调试技巧
图解协程:suspend
1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习
1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)
…
1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,可扫描下方CSDN官方认证卡片免费领取↓↓↓