Jetpack实践指南:lifecycle与协程的“猫腻”事

奋斗吧
奋斗吧
擅长邻域:未填写

标签: Jetpack实践指南:lifecycle与协程的“猫腻”事 HarmonyOS博客 51CTO博客

2023-06-19 18:24:24 239浏览

Jetpack实践指南:lifecycle与协程的“猫腻”事,本篇文章主要是讲解如何使用lifecycl


本篇文章主要是讲解如何使用lifecycle创建协程 、源码解析以及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<Object> mInternalScopeRef = new AtomicReference<>();

初次调用其get()方法肯定是一个空的,所以会走到2处。

2.创建一个LifecycleCoroutineScopeImpl对象,看下它是个啥:

Jetpack实践指南:lifecycle与协程的“猫腻”事_android

再往下走:

Jetpack实践指南:lifecycle与协程的“猫腻”事_生命周期_02

可以看到这就是一个CoroutineScope的子类,所以我们这样就创建了一个协程作用域对象,并且指定:

  • job类型为SupervisorJob,这样子job间发生异常而不会互相影响,阻止向上传递异常;
  • 分发器类型为Dispatchers.Main.immediate,默认分发到主线程执行

在这里顺便说下Dispatchers.Main.immediateDispatchers.Main的区别:

首先这两个都是指定把协程块内容分发到主线程中执行,但是前者多了个immediate,这其实是一种优化手段,我们看下官方文档怎么说:

Jetpack实践指南:lifecycle与协程的“猫腻”事_生命周期_03

简单说,如果创建协程块的线程和要指定的调度线程都是主线程,使用immediate的就不需要额外使用分发器进行分发了,这算是一个优化小手段

3.将创建的这个协程作用域对象通过CAS写入lifecycle的mInternalScopeRef,这样当 lifecycleScope.launch在此获取协程作用域就不会进行重复创建了,直接从mInternalScopeRef获取即可。

综上所述, lifecycleScope就是个协程作用域对象,用来在特定job和主线程中执行协程块代码逻辑。

4.注册观察者,当界面销毁时取消所有协程的执行

Jetpack实践指南:lifecycle与协程的“猫腻”事_开发语言_04

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()
    }
}

就是为了方便当界面销毁了移除观察者,并取消所有的子协程代码块的执行,避免内存的泄漏。

协程是如何绑定Activity的生命周期

这个问题很简单,大家应该都可以想到:协程借助于添加观察者LifecyclObserver的方式实现对Activity生命周期的监听,这里主要通过两个非常典型的源码例子进行分析:

  1. lifecycleScope.launchWhenXXX:可以指定在特定的生命周期执行协程协程代码,当不再该状态时就会暂停协程块的执行
  2. lifecycle.repeatOnLifecycle:可以指定在特定的生命周期执行协程协程代码,当不再该状态时就会取消协程块的执行

请大家一定记住这两者的区别,当我们使用MutableStateFlowMutableSharedFlow时,在协程作用域添加观察者时强烈推荐使用第二种方式,之后应该会写一篇文章进行分析的。

lifecycleScope.launchWhenXXX

我看先看下调用链分析,这里以launchWhenResumed举例:

public fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
    lifecycle.whenResumed(block)
}

往下走:

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

最终走到:

public suspend fun <T> 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.创建可以暂停的分发器,就是通过这个对象实现在指定生命周期执行协程代码块,超出该生命周期暂停协程代码块的执行

Jetpack实践指南:lifecycle与协程的“猫腻”事_生命周期_05

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<Unit> { cont ->
                    val startWorkEvent = Lifecycle.Event.upTo(state)
                    val cancelWorkEvent = Lifecycle.Event.downFrom(state)
                    observer = LifecycleEventObserver { _, event ->
                        //3.
                        if (event == startWorkEvent) {
                            launchedJob = this@coroutineScope.launch {
                                block()
                            }
                            return@LifecycleEventObserver
                        }
                        //4.
                        if (event == cancelWorkEvent) {
                            launchedJob?.cancel()
                        }
                        //5.
                        if (event == Lifecycle.Event.ON_DESTROY) {
                            cont.resume(Unit)
                        }
                    }
                    //6.
                    this@repeatOnLifecycle.addObserver(observer as LifecycleEventObserver)
                }
            } finally {
                //7.
                launchedJob?.cancel()
                observer?.let {
                    this@repeatOnLifecycle.removeObserver(it)
                }
            }
        }
    }
}

上面的代码是经过精简过的(也没啥可以精简的),我们来一步步进行分析:

  1. 当页面状态处于销毁DESTROYED状态,直接return。
  2. suspendCancellableCoroutine是用来捕捉传递过来的Continuation,这样我们就可以决定挂起的协程什么时候可以恢复执行,比如delay()的实现机制就是如此。

PS: 插一嘴,不管是suspendCancellableCoroutine还是coroutineScope底层方法都是通过suspendCoroutineUninterceptedOrReturn实现,但是由于这个方法不正确使用会对代码产生安全影响,比如栈溢出,所以官方提供了前面两个封装方法。

  1. 创建了一个LifecycleEventObserver对象,用来绑定界面生命周期,当达到指定执行的生命周期后,就创建一个协程执行我们传递过来的代码块.
  2. 当小于当前指定的生命周期状态,就直接取消协程执行(请注意,和上面的whenStateAtLeast区别)。
  3. 当界面销毁时,才将挂起的协程恢复执行,所以这里我们就实现了根据具体场景来决定什么时候恢复协程执行。
  4. 6处和7处就是注册和反注册观察者。

综上所诉,该方法lifecycle.repeatOnLifecycle当小于指定生命周期状态时,是取消协程的执行,而不是暂停

作者:长安皈故里

好博客就要一起分享哦!分享海报

此处可发布评论

评论(0展开评论

暂无评论,快来写一下吧

展开评论

您可能感兴趣的博客

客服QQ 1913284695