flutter页面跳转无法触发dispose,万字长文 - Kotlin 协程进阶(1),真香系列
Android学习是一条漫长的道路,我们要学习的东西不仅仅只有表面的 技术,还要深入底层,弄明白下面的 原理,只有这样,我们才能够提高自己的竞争力,在当今这个竞争激烈的世界里立足。人生不可能一帆风顺,有高峰自然有低谷,要相信,那些打不倒我们的,终将使我们更强大,要做自己的摆渡人。资源持续更新中,欢迎大家一起学习和探讨。本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面
- The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
- If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
val scope = MainScope() + CoroutineName("MyActivity").
*/
@Suppress(“FunctionName”)
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
但是SupervisorJob是很容易被误解的,它和协程异常处理、子协程所属Job类型还有域有很多让人混淆的地方,具体异常处理可以看Google的这一篇文章:协程中的取消和异常 | 异常处理详解
CoroutineDispatcher - 调度器
CoroutineDispatcher定义了 Coroutine 执行的线程。CoroutineDispatcher可以限定Coroutine在某一个线程执行、也可以分配到一个线程池来执行、也可以不限制其执行的线程。
CoroutineDispatcher是一个抽象类,所有dispatcher都应该继承这个类来实现对应的功能。Dispatchers是一个标准库中帮我们封装了切换线程的帮助类,可以简单理解为一个线程池。它的实现如下:

- Dispatchers.Default
默认的调度器,适合处理后台计算,是一个CPU密集型任务调度器。如果创建 Coroutine 的时候没有指定 dispatcher,则一般默认使用这个作为默认值。Default dispatcher 使用一个共享的后台线程池来运行里面的任务。注意它和IO共享线程池,只不过限制了最大并发数不同。
- Dispatchers.IO
顾名思义这是用来执行阻塞 IO 操作的,是和Default共用一个共享的线程池来执行里面的任务。根据同时运行的任务数量,在需要的时候会创建额外的线程,当任务执行完毕后会释放不需要的线程。
- Dispatchers.Unconfined
由于Dispatchers.Unconfined未定义线程池,所以执行的时候默认在启动线程。遇到第一个挂起点,之后由调用resume的线程决定恢复协程的线程。
- Dispatchers.Main:
指定执行的线程是主线程,在Android上就是UI线程·
由于子Coroutine 会继承父Coroutine 的 context,所以为了方便使用,我们一般会在 父Coroutine 上设定一个 Dispatcher,然后所有 子Coroutine 自动使用这个 Dispatcher。
CoroutineStart - 协程启动模式
- CoroutineStart.DEFAULT:
协程创建后立即开始调度,在调度前如果协程被取消,其将直接进入取消响应的状态
虽然是立即调度,但也有可能在执行前被取消
- CoroutineStart.ATOMIC:
协程创建后立即开始调度,协程执行到第一个挂起点之前不响应取消
虽然是立即调度,但其将调度和执行两个步骤合二为一了,就像它的名字一样,其保证调度和执行是原子操作,因此协程也一定会执行
- CoroutineStart.LAZY:
只要协程被需要时,包括主动调用该协程的start、join或者await等函数时才会开始调度,如果调度前就被取消,协程将直接进入异常结束状态
- CoroutineStart.UNDISPATCHED:
协程创建后立即在当前函数调用栈中执行,直到遇到第一个真正挂起的点
是立即执行,因此协程一定会执行
这些启动模式的设计主要是为了应对某些特殊的场景。业务开发实践中通常使用DEFAULT和LAZY这两个启动模式就够了
CoroutineScope - 协程作用域
定义协程必须指定其
CoroutineScope。CoroutineScope可以对协程进行追踪,即使协程被挂起也是如此。同调度程序 (Dispatcher) 不同,CoroutineScope并不运行协程,它只是确保您不会失去对协程的追踪。为了确保所有的协程都会被追踪,Kotlin不允许在没有使用CoroutineScope的情况下启动新的协程。CoroutineScope可被看作是一个具有超能力的ExecutorService的轻量级版本。CoroutineScope会跟踪所有协程,同样它还可以取消由它所启动的所有协程。这在Android开发中非常有用,比如它能够在用户离开界面时停止执行协程。
Coroutine是轻量级的线程,并不意味着就不消耗系统资源。 当异步操作比较耗时的时候,或者当异步操作出现错误的时候,需要把这个Coroutine取消掉来释放系统资源。在Android环境中,通常每个界面(Activity、Fragment等)启动的Coroutine只在该界面有意义,如果用户在等待Coroutine执行的时候退出了这个界面,则再继续执行这个Coroutine可能是没必要的。另外Coroutine也需要在适当的context中执行,否则会出现错误,比如在非UI线程去访问View。 所以Coroutine在设计的时候,要求在一个范围(Scope)内执行,这样当这个Scope取消的时候,里面所有的子 Coroutine也自动取消。所以要使用Coroutine必须要先创建一个对应的CoroutineScope。
CoroutineScope 接口
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
CoroutineScope 只是定义了一个新 Coroutine 的执行 Scope。每个 coroutine builder 都是 CoroutineScope 的扩展函数,并且自动的继承了当前 Scope 的 coroutineContext 。
分类及行为规则
官方框架在实现复合协程的过程中也提供了作用域,主要用以明确写成之间的父子关系,以及对于取消或者异常处理等方面的传播行为。该作用域包括以下三种:
- 顶级作用域
没有父协程的协程所在的作用域为顶级作用域。
- 协同作用域
协程中启动新的协程,新协程为所在协程的子协程,这种情况下,子协程所在的作用域默认为协同作用域。此时子协程抛出的未捕获异常,都将传递给父协程处理,父协程同时也会被取消。
- 主从作用域
与协同作用域在协程的父子关系上一致,区别在于,处于该作用域下的协程出现未捕获的异常时,不会将异常向上传递给父协程。
除了三种作用域中提到的行为以外,父子协程之间还存在以下规则:
- 父协程被取消,则所有子协程均被取消。由于协同作用域和主从作用域中都存在父子协程关系,因此此条规则都适用。
- 父协程需要等待子协程执行完毕之后才会最终进入完成状态,不管父协程自身的协程体是否已经执行完。
- 子协程会继承父协程的协程上下文中的元素,如果自身有相同
key的成员,则覆盖对应的key,覆盖的效果仅限自身范围内有效。
常用作用域
官方库给我们提供了一些作用域可以直接来使用,并且 Android 的Lifecycle Ktx库也封装了更好用的作用域,下面看一下各种作用域
GlobalScope - 不推荐使用
public object GlobalScope : CoroutineScope {
/**
- Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
GlobalScope是一个单例实现,源码十分简单,上下文是EmptyCoroutineContext,是一个空的上下文,切不包含任何Job,该作用域常被拿来做示例代码,由于 GlobalScope 对象没有和应用生命周期组件相关联,需要自己管理 GlobalScope 所创建的 Coroutine,且GlobalScope的生命周期是 process 级别的,所以一般而言我们不推荐使用 GlobalScope 来创建 Coroutine。
runBlocking{} - 主要用于测试
/**
- Runs a new coroutine and blocks the current thread interruptibly until its completion.
- This function should not be used from a coroutine. It is designed to bridge regular blocking code
- to libraries that are written in suspending style, to be used in
mainfunctions and in tests. - The default [CoroutineDispatcher] for this builder is an internal implementation of event loop that processes continuations
- in this blocked thread until the completion of this coroutine.
- See [CoroutineDispatcher] for the other implementations that are provided by
kotlinx.coroutines. - When [CoroutineDispatcher] is explicitly specified in the [context], then the new coroutine runs in the context of
- the specified dispatcher while the current thread is blocked. If the specified dispatcher is an event loop of another
runBlocking, - then this invocation uses the outer event loop.
- If this blocked thread is interrupted (see [Thread.interrupt]), then the coroutine job is cancelled and
- this
runBlockinginvocation throws [InterruptedException]. - See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging facilities that are available
- for a newly created coroutine.
- @param context the context of the coroutine. The default value is an event loop on the current thread.
- @param block the coroutine code.
*/
@Throws(InterruptedException::class)
public fun runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
val currentThread = Thread.currentThread()
val contextInterceptor = context[ContinuationInterceptor]
val eventLoop: EventLoop?
val newContext: CoroutineContext
if (contextInterceptor == null) {
// create or use private event loop if no dispatcher is specified
eventLoop = ThreadLocalEventLoop.eventLoop
newContext = GlobalScope.newCoroutineContext(context + eventLoop)
} else {
// See if context’s interceptor is an event loop that we shall use (to support TestContext)
// or take an existing thread-local event loop if present to avoid blocking it (but don’t create one)
eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
?: ThreadLocalEventLoop.currentOrNull()
newContext = GlobalScope.newCoroutineContext(context)
}
val coroutine = BlockingCoroutine(newContext, currentThread, eventLoop)
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
return coroutine.joinBlocking()
}
这是一个顶层函数,从源码的注释中我们可以得到一些信息,运行一个新的协程并且阻塞当前可中断的线程直至协程执行完成,该函数不应从一个协程中使用,该函数被设计用于桥接普通阻塞代码到以挂起风格(suspending style)编写的库,以用于主函数与测试。该函数主要用于测试,不适用于日常开发,该协程会阻塞当前线程直到协程体执行完成。
MainScope() - 可用于开发
/**
-
Creates the main [CoroutineScope] for UI components.
-
Example of use:
-
class MyAndroidActivity {
-
private val scope = MainScope() -
override fun onDestroy() { -
super.onDestroy() -
scope.cancel() -
} -
}
-
The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
-
If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
-
val scope = MainScope() + CoroutineName("MyActivity").
*/
@Suppress(“FunctionName”)
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
该函数是一个顶层函数,用于返回一个上下文是SupervisorJob() + Dispatchers.Main的作用域,该作用域常被使用在Activity/Fragment,并且在界面销毁时要调用fun CoroutineScope.cancel(cause: CancellationException? = null)对协程进行取消,这是官方库中可以在开发中使用的一个用于获取作用域的顶层函数,使用示例在官方库的代码注释中已经给出,上面的源码中也有,使用起来也是十分的方便。
LifecycleOwner.lifecycleScope - 推荐使用
/**
- [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
- This scope will be cancelled when the [Lifecycle] is destroyed.
- This scope is bound to
- [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
*/
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
该扩展属性是 Android 的Lifecycle Ktx库提供的具有生命周期感知的协程作用域,它与LifecycleOwner的Lifecycle绑定,Lifecycle被销毁时,此作用域将被取消。这是在Activity/Fragment中推荐使用的作用域,因为它会与当前的UI组件绑定生命周期,界面销毁时该协程作用域将被取消,不会造成协程泄漏,相同作用的还有下文提到的ViewModel.viewModelScope。
ViewModel.viewModelScope - 推荐使用
/**
- [CoroutineScope] tied to this [ViewModel].
- This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called
- This scope is bound to
- [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
*/
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
}
该扩展属性和上文中提到的LifecycleOwner.lifecycleScope基本一致,它是ViewModel的扩展属性,也是来自Android 的Lifecycle Ktx库,它能够在此ViewModel销毁时自动取消,同样不会造成协程泄漏。该扩展属性返回的作用域的上下文同样是SupervisorJob() + Dispatchers.Main.immediate
coroutineScope & supervisorScope
/**
- Creates a [CoroutineScope] with [SupervisorJob] and calls the specified suspend block with this scope.
- The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides
- context’s [Job] with [SupervisorJob].
- A failure of a child does not cause this scope to fail and does not affect its other children,
- so a custom policy for handling failures of its children can be implemented. See [SupervisorJob] for details.
- A failure of the scope itself (exception thrown in the [block] or cancellation) fails the scope with all its children,
- but does not cancel parent job.
*/
public suspend fun supervisorScope(block: suspend CoroutineScope.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = SupervisorCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
/**
-
Creates a [CoroutineScope] and calls the specified suspend block with this scope.
-
The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides
-
the context’s [Job].
-
This function is designed for parallel decomposition of work. When any child coroutine in this scope fails,
-
this scope fails and all the rest of the children are cancelled (for a different behavior see [supervisorScope]).
-
This function returns as soon as the given block and all its children coroutines are completed.
-
A usage example of a scope looks like this:
-
suspend fun showSomeData() = coroutineScope {
-
val data = async(Dispatchers.IO) { // <- extension on current scope -
... load some UI data for the Main thread ... -
} -
withContext(Dispatchers.Main) { -
doSomeWork() -
val result = data.await() -
display(result) -
} -
}
-
The scope in this example has the following semantics:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。






既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
Android学习是一条漫长的道路,我们要学习的东西不仅仅只有表面的 技术,还要深入底层,弄明白下面的 原理,只有这样,我们才能够提高自己的竞争力,在当今这个竞争激烈的世界里立足。
人生不可能一帆风顺,有高峰自然有低谷,要相信,那些打不倒我们的,终将使我们更强大,要做自己的摆渡人。
资源持续更新中,欢迎大家一起学习和探讨。
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
**
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
更多推荐

所有评论(0)