Kotlin:lifecycleScope与GlobalScope以及MainScope的区别,详细分析为什么在Android中推荐使用lifecycleScope!

  • 如果是Android基本都要调度到主线程进行操作,但是GlobalScope.launch默认的调度器是Default。每次都要显示的写Main不是很方便。

  • 上面也说到了Android这种UI驱动的程序,比较适合主从作用域,但是GlobalScope是顶级作用域。那有人就说了supervisorScope启动的不是主从作用域吗?但是supervisorScope是一个挂起函数(可自行查看源码。其实是内部引用了一个私有的SupervisorCoroutine类,继承自ScopeCoroutine。重写了childCancelled方法,就干了一件事,返回了false。简单来说就是不处理子协程的异常🤦‍♀️,这可忒不负责任了😢)。如果要调用supervisorScope必须要在一个协程或者挂起函数内。啊 ~ 这。那我不得先启动一个协程?😅

  • 还要一个问题,内存泄漏的问题。需要在页面销毁的适合取消掉当前协程。对于GlobalScope来说,就必须进行手动调用cancel的操作了。emmm,这波操作不仅麻烦而且危险(万一忘了取消咋整)!

MainScope()


先看一下源码:

public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

老话说得好啊,柿子的挑软的捏!Dispatchers.Main 好嘛!默认调度器是主线程,解决了上面说的第一个问题。

那SupervisorJob()是个啥呢?再进一步看看

public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)

private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {

override fun childCancelled(cause: Throwable): Boolean = false

}

emm,结局显而易见。顺着方法一路点进去,发现了一个重写的childCancelled返回了false。还记得上面我写的supervisorScope简要分析吗?supervisorScope不就是也只干了这么个事情嘛😂。所以说这玩意其实也是一个主从作用域。解决了第二个问题。

顺手提一嘴,会不会有什么好奇为什么SupervisorJob() + Dispatchers.Main以及上面刚开始写的Dispatchers.Main + coroutineExceptionHandler,是个什么鬼,为什么可以加呢。其实调度器和异常处理器它就是CoroutineContext;而作用域接口内部就一个常量就是CoroutineContext。CoroutineContext是什么呢?这玩意就是协程上下文。说白了就是一个集合。在协程内部操作的过程中调度器、异常处理都是从CoroutineContext里面取的,包括作用域分发异常,也是从上下文中取得得父子协程。为什么可以加呢?因为内部重写了operator fun plus操作符,所以能进行加的操作(理解为集合即可,想要具体了解可以考虑撸一波源码😴)。

那好像就剩下生命周期没有解决了。再去瞅瞅Android对lifecycleScope做了什么封装吧!

lifecycleScope


emm,看源码

public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope

get() = lifecycle.coroutineScope

返回了LifecycleCoroutineScope,而LifecycleCoroutineScope可以启动协程,所以肯定是CoroutineScope作用域的子类。另外receiver是LifecycleOwner,所以先猜测:应该是绑定了Android的声明周期了。

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

}

}

}

本着带着问题看源码的思维,直接忽略掉那些CAS的操作。简单分析一波。返回了LifecycleCoroutineScopeImpl对象,而LifecycleCoroutineScopeImpl是LifecycleCoroutineScope类型的。所以重点就是LifecycleCoroutineScopeImpl了!

仔细看LifecycleCoroutineScopeImpl传入了this以及SupervisorJob() + Dispatchers.Main.immediate。this不就是生命周期Lifecycle嘛。而SupervisorJob()上面已经分析过了,成为主从作用域Dispatchers.Main.immediate就是切换到主线程。那可能有人就会问题了,immediate是个什么鬼,别着急。先分析完主流程。其他的写在后面感兴趣的话可以看看

下面看看LifecycleCoroutineScopeImpl的实现源码!

internal class LifecycleCoroutineScopeImpl(

override val lifecycle: Lifecycle,

override val coroutineContext: CoroutineContext

) : LifecycleCoroutineScope(), LifecycleEventObserver {

init {

if (lifecycle.currentState == Lifecycle.State.DESTROYED) {

coroutineContext.cancel()

}

}

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

}

}

}

方便上面的分析,放上生命周期状态枚举类。DESTROYED的ordinal就是0,RESUMED的ordinal就是4。枚举可以直接使用ordinal比较大小。

public enum State {

DESTROYED,

INITIALIZED,

CREATED,

STARTED,

RESUMED;

public boolean isAtLeast(@NonNull State state) {

return compareTo(state) >= 0;

}

}

构造LifecycleCoroutineScopeImpl的时候判断一波,如果当前是DESTROYED直接取消协程。

剩下就两个函数,register()和onStateChanged(source: LifecycleOwner, event: Lifecycle.Event)。

**register()**在创建LifecycleCoroutineScopeImpl的时候调用了一下(可以看上面的源码)。简单来说将当前注册进了生命周期观察者当中。

onStateChanged():每次生命周期变化的时候被调用(不了解的可以自行了解一波lifecycle)。每次都判断如果当前生命周期 在DESTROYED之后就取消掉协程,同时移除观察者! 破案了!!!

所以 lifecycleScope 完美解决了 上面说的GlobalScope的问题。不仅方便还安全!!!

lifecycleScope剩余问题分析(感兴趣的可以继续看)


lifecycleScope虽然比较方便且不用担心内存泄漏的问题!但是是有使用限制的。网上大部分都说lifecycleScope 只能在Activity、Fragment中使用其实是不太准确的。上面分析了一下lifecycleScope是LifecycleOwner的扩展函数,receiver是LifecycleOwner。因为Activity、Fragment和默认绑定了LifecycleOwner所以可以直接使用。但是理论上来说 只要是能获取到LifecycleOwner的地方都是可以使用lifecycleScope的

LifecycleCoroutineScope

public abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {

internal abstract val lifecycle: Lifecycle

public fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {

lifecycle.whenCreated(block)

}

public fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {

lifecycle.whenStarted(block)

}

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

lifecycle.whenResumed(block)

}

}

比如说LifecycleCoroutineScope内部就提供了几个方法,当你不是在Activity、Fragment内部调用的时候,可以调用使用这几个方法,准确的在对应的声明周期内部执行。内部其实是一个DispatchQueue封装了ArrayDeque队列。判断生命周期如果小于当前可执行的生命周期则加入队列,等到对应生命周期来到在取出执行。

但是有一个小问题。不知道大家发现没有,使用这几个方法的时候没有办法设置异常处理器!直接启用了一个默认的launch还没有给传入上下文的入口😲。

所以如果你想使用这几个方法还想传入异常处理器的话,可以这么写:自己写个launch。

val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->

throwable.printStackTrace()

}

lifecycleScope.launch(coroutineExceptionHandler) {

lifecycle.whenCreated {

// TODO: 2021/8/29 do

}

}

immediate

在分析一波immediate吧,这玩意是啥,按照本来的理解Main已经是主线程了,immediate是个什么操作。

先简单介绍一下CoroutineDispatcher 吧,调度器较为上层的基类吧。immediate必然也是调度器,所以肯定实现了CoroutineDispatcher 。而CoroutineDispatcher 里面有两个方法。isDispatchNeeded和dispatch。

isDispatchNeeded:如果协程的执行应该使用 [dispatch] 方法执行,则返回 true。大多数调度程序的默认行为是返回 true

dispatch:将可运行的 [block] 的执行分派到给定 [context] 中的另一个线程。

如果isDispatchNeeded返回true则执行dispatch进行调度。

其实就是isDispatchNeeded返回true则执行dispatch进行调度

调度器是协程的拦截器(在挂起函数恢复时调用),然后使用continuation包装调用然后达到在另一个线程调度的效果。上面说的在DispatchedContinuation类的resumeWith的方法有体现,源码如下。先判断了isDispatchNeeded然后进行dispatch调度

override fun resumeWith(result: Result) {

val context = continuation.context

val state = result.toState()

if (dispatcher.isDispatchNeeded(context)) {

_state = state

resumeMode = MODE_ATOMIC

dispatcher.dispatch(context, this)

} else {

executeUnconfined(state, MODE_ATOMIC) {

withCoroutineContext(this.context, countOrElement) {

continuation.resumeWith(result)

}

}

}

}

上面知识知道后,在看一下immediate的实现。

internal class HandlerContext private constructor(

private val handler: Handler,

private val name: String?,

private val invokeImmediately: Boolean

) : HandlerDispatcher(), Delay {

//省略。。

override val immediate: HandlerContext = _immediate ?:

HandlerContext(handler, name, true).also { _immediate = it }

override fun isDispatchNeeded(context: CoroutineContext): Boolean {

return !invokeImmediately || Looper.myLooper() != handler.looper

}

override fun dispatch(context: CoroutineContext, block: Runnable) {

handler.post(block)

}

//省略。。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结

最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

**

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:
[外链图片转存中…(img-ijQ3E09l-1713390866427)]

[外链图片转存中…(img-FXnZsnEB-1713390866428)]

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 0
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值