【协程】LifecycleScope源码解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【协程】LifecycleScope源码解析

前言

使用协程,相信很多同学已经信手拈来了,但是也有很多同学是不知道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
}

whenResumed和launchWhenResumed执行时机一样,区别在于:


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:


@MainThread
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) {
            // 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。

在父类中通过ReportFragment或ActivityLifecycleCallbacks接口来派发当前生命周期状态,具体使用哪种派发方式要看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协程执行时机的流程。


调用lifecycleScope,返回lifecycle.coroutineScope;

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

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

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

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

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

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


最后

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

目录
打赏
0
0
1
0
1254
分享
相关文章
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
166 29
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
10月前
|
使用python实现一个用户态协程
【6月更文挑战第28天】本文探讨了如何在Python中实现类似Golang中协程(goroutines)和通道(channels)的概念。文章最后提到了`wait_for`函数在处理超时和取消操作中的作
98 1
使用python实现一个用户态协程
|
7月前
|
python3 协程实战(python3经典编程案例)
该文章通过多个实战案例介绍了如何在Python3中使用协程来提高I/O密集型应用的性能,利用asyncio库以及async/await语法来编写高效的异步代码。
79 0
实战指南:用Python协程与异步函数优化高性能Web应用
【7月更文挑战第15天】Python的协程与异步函数优化Web性能,通过非阻塞I/O提升并发处理能力。使用aiohttp库构建异步服务器,示例代码展示如何处理GET请求。异步处理减少资源消耗,提高响应速度和吞吐量,适用于高并发场景。掌握这项技术对提升Web应用性能至关重要。
130 10

推荐镜像

更多
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等