写点什么

【协程】LifecycleScope 源码解析

作者:yechaoa
  • 2022 年 6 月 15 日
  • 本文字数:4257 字

    阅读完需:约 14 分钟

前言

使用协程,相信很多同学已经信手拈来了,但是也有很多同学是不知道LifecycleScope的。


LifecycleScope,顾名思义,具有生命周期的协程。它是LifecycleOwner生命周期所有者的扩展属性,与 LifecycleOwner 生命周期绑定,并会在 LifecycleOwner 生命周期destroyed的时候取消掉。


推荐理由:


  • 自动取消,不会造成内存泄漏,可以替代 MainScope。

  • 可以基于指定的生命周期执行。


后面会重点介绍 LifecycleScope 是怎么做到的。

使用

引入

  • 协程:


implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0'implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
复制代码


  • Lifecycle:


implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
复制代码


LifecycleScope 虽然是协程,但属于 Lifecycle 中的扩展属性

示例:

lifecycleScope 默认主线程,可以通过withContext来指定线程。


lifecycleScope.launch {    // do    withContext(Dispatchers.IO) {        // do    }}
// or
lifecycleScope.launch(Dispatchers.IO){ // do}
// or
lifecycleScope.launch { whenResumed { // do }}
// or
lifecycleScope.launchWhenResumed { // do}
复制代码


whenResumedlaunchWhenResumed执行时机一样,区别在于:


  • whenResumed 可以有返回结果

  • launchWhenResumed 返回的是 Job 对象


共有三个对应生命周期的扩展函数:


  • whenCreated

  • whenStarted

  • whenResumed


使用非常简单,关键在于它是怎么保证不会内存泄露的,又是怎么知道在某个生命周期的时候去执行协程的?

源码分析

1、如何保证不会内存泄漏的

先看lifecycleScope源码:


val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope    get() = lifecycle.coroutineScope
复制代码


继承自LifecycleCoroutineScope,而 LifecycleCoroutineScope 是CoroutineScope的子类(协程层级关系)。


get()返回lifecycle.coroutineScope


这里有一个源码小技巧,当继承对象与返回对象不一致时,那么返回对象多半为继承对象的子类。


继续看 lifecycle.coroutineScope:


public val Lifecycle.coroutineScope: LifecycleCoroutineScope    get() {        while (true) {            val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?            if (existing != null) {                return existing            }            val newScope = LifecycleCoroutineScopeImpl(                this,                SupervisorJob() + Dispatchers.Main.immediate            )            if (mInternalScopeRef.compareAndSet(null, newScope)) {                newScope.register()                return newScope            }        }    }
复制代码


果不其然,也是继承 LifecycleCoroutineScope。关键在于,通过LifecycleCoroutineScopeImpl创建了协程,默认主线程,随后又调用了 newScope.register()


继续看 LifecycleCoroutineScopeImpl:


internal class LifecycleCoroutineScopeImpl(    override val lifecycle: Lifecycle,    override val coroutineContext: CoroutineContext) : LifecycleCoroutineScope(), LifecycleEventObserver {    //...
fun register() { launch(Dispatchers.Main.immediate) { if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) { lifecycle.addObserver(this@LifecycleCoroutineScopeImpl) } else { coroutineContext.cancel() } } }
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { if (lifecycle.currentState <= Lifecycle.State.DESTROYED) { lifecycle.removeObserver(this) coroutineContext.cancel() } }}
复制代码


register()方法中添加了 LifecycleEventObserver 接口的监听,LifecycleEventObserver 会在onStateChanged方法中派发当前生命周期,关键来了,在 onStateChanged 回调中,判断当前生命周期是destroyed的时候,移除监听,并取消协程


至此,相信大部分同学都明白了为什么不会造成内存泄露了,因为在页面 destroyed 的时候,协程会取消,并不会继续执行,而MainScope是需要手动取消的,否则会有内存泄露的风险。


插曲,我们进一步思考,在其他的开发场景中,也可以学习源码通过添加 LifecycleEventObserver 监听的方式,做回收清理操作,来避免内存泄漏。


author:yechaoa

2、如何知道在某个生命周期去执行协程

lifecycleScope.launchWhenResumed为例,一探究竟。


fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {    lifecycle.whenResumed(block)}
复制代码


调用whenResumed


suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T {    return whenStateAtLeast(Lifecycle.State.RESUMED, block)}
复制代码


接着调用whenStateAtLeast,并传入一个具体生命周期状态作为标识


继续看 whenStateAtLeast:


suspend fun <T> Lifecycle.whenStateAtLeast(    minState: Lifecycle.State,    block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.Main.immediate) {    val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job")    val dispatcher = PausingDispatcher()    val controller =        LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job)    try {        withContext(dispatcher, block)    } finally {        controller.finish()    }}
复制代码


这里创建了LifecycleController,并向下传入接收的具体状态,同时还有一个调度队列 dispatcher.dispatchQueue。


接着看 LifecycleController:


@MainThreadinternal 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) {            // cancel job before resuming remaining coroutines so that they run in cancelled            // state            handleDestroy(parentJob)        } else if (source.lifecycle.currentState < minState) {            dispatchQueue.pause()        } else {            dispatchQueue.resume()        }    }
init { // If Lifecycle is already destroyed (e.g. developer leaked the lifecycle), we won't get // an event callback so we need to check for it before registering // see: b/128749497 for details. if (lifecycle.currentState == Lifecycle.State.DESTROYED) { handleDestroy(parentJob) } else { lifecycle.addObserver(observer) } } //...}
复制代码


init初始化的时候,添加 LifecycleEventObserver 监听(又是一个使用案例,不过这里用的是 lambda 写法)。


在回调中,对生命周期进行了判断,当大于当前状态的时候,也就是生命周期执行到当前状态的时候,会调用dispatchQueue.resume()执行队列,也就是协程开始执行


dispatchQueue.resume:


    @MainThread    fun resume() {        if (!paused) {            return        }        check(!finished) {            "Cannot resume a finished dispatcher"        }        paused = false        drainQueue()    }        //...
@MainThread fun drainQueue() { if (isDraining) { // Block re-entrant calls to avoid deep stacks return } try { isDraining = true while (queue.isNotEmpty()) { if (!canRun()) { break } queue.poll()?.run() } } finally { isDraining = false } }
复制代码


关于怎么获取到当前生命周期状态的,就涉及到Lifecycle相关的知识了,简而言之,不管是Activity还是Fragment,都是LifecycleOwner,其实是父类实现的,比如 ComponentActivity。在父类中通过ReportFragmentActivityLifecycleCallbacks接口来派发当前生命周期状态,具体使用哪种派发方式要看Api等级是否在 29(10.0)及以上,及 则后者。

验证分析

验证一下我们的分析是否正确。


代码简单测试:


class MainActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)
Log.i("tag","onCreate")
lifecycleScope.launchWhenResumed { Log.i("tag","launchWhenResumed") } }
override fun onResume() { super.onResume() Log.i("tag","onResume") }}
复制代码


同时对源码进行debug


 I/tag: onCreate I/tag: onResume I/tag: launchWhenResumed
复制代码


通过打印,并结合断点执行顺序来看,以上分析是完全正确的。

总结

我们再来总结一下lifecycleScope协程执行时机的流程。


  1. 调用 lifecycleScope,返回 lifecycle.coroutineScope;

  2. 在 coroutineScope 中通过 LifecycleCoroutineScopeImpl 创建了协程,并调用了 register()方法添加了对生命周期的监听,这个监听其实是为了在生命周期 destroyed 的时候取消协程;

  3. 随后才是调用具体执行状态的代码,比如 launchWhenResumed;

  4. 然后调用 whenStateAtLeast,并传入协程具体要执行的状态,比如 Lifecycle.State.RESUMED;

  5. 在 whenStateAtLeast 中创建了 LifecycleController,并向下传入具体执行状态,和一个队列;

  6. 在 LifecycleController 初始化的时候,也添加了对生命周期的监听 LifecycleEventObserver,在回调中,通过当前生命周期的状态与具体要执行状态的判断,来决定是否执行协程队列,满足条件,即执行。


以上,就是lifecycleScope的使用,以及执行流程的具体分析。

最后

写作不易,如果对你有一丢丢帮助或启发,感谢点赞支持 ^ - ^

发布于: 刚刚阅读数: 4
用户头像

yechaoa

关注

优质作者 2018.10.23 加入

知名互联网大厂技术专家,多平台博客专家、优秀博主、人气作者,博客风格深入浅出,专注于Android领域,同时探索于大前端方向,持续研究并落地前端、小程序、Flutter、Kotlin等相关热门技术

评论

发布
暂无评论
【协程】LifecycleScope源码解析_android_yechaoa_InfoQ写作社区