“3”
}
val job4 = async {
// 请求4
delay(5000)
“4”
}
val job5 = async {
// 请求5
delay(5000)
“5”
}
// 代码执行到此处时 5个请求已经同时在执行了
// 等待各job执行完 将结果合并
Log.d(
“TAG”,
“asyncTest2: ${job1.await()} ${job2.await()} ${job3.await()} ${job4.await()} ${job5.await()}”
)
// 因为我们设置的模拟时间都是5000毫秒 所以当job1执行完时 其他job也均执行完成
}
}
上面的代码就是一个简单的并发示例,是不是感觉十分的简单,协程的优势立马凸显出来了。
这就是最基本的协程使用,关于作用域,更推荐的是在UI组件中使用LifecycleOwner.lifecycleScope,在ViewModel中使用ViewModel.viewModelScope
Coroutine的深入
其实简单的使用,就已经满足大部分日常开发需求,但是我们有必要全面了解一下
Coroutine,以便能够排查问题及自定义场景,下面我们从一个最基本的函数来切入,这个函数就是launch{}:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext, // 协程上下文
start: CoroutineStart = CoroutineStart.DEFAULT, // 协程启动模式
block: suspend CoroutineScope.() -> Unit // 运行在协程的逻辑
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
上面是launch函数的定义,它以CoroutineScope的扩展函数的形成出现,函数参数分别是:协程上下文CoroutineContext、协程启动模式CoroutineStart、协程体,返回值是协程实例Job,其中CoroutineContext又包括了Job、CoroutineDispatcher、CoroutineName。下面我们就一一介绍这些内容:CoroutineContext、Job、CoroutineDispatcher、CoroutineStart、CoroutineScope。
CoroutineContext - 协程上下文
CoroutineContext即协程的上下文,是 Kotlin 协程的一个基本结构单元。巧妙的运用协程上下文是至关重要的,以此来实现正确的线程行为、生命周期、异常以及调试。它包含用户定义的一些数据集合,这些数据与协程密切相关。它是一个有索引的Element实例集合。这个有索引的集合类似于一个介于set和 map之间的数据结构。每个element在这个集合有一个唯一的 Key 。当多个element的 key 的引用相同,则代表属于集合里同一个element。它由如下几项构成:
- Job: 控制协程的生命周期;
- CoroutineDispatcher: 向合适的线程分发任务;
- CoroutineName: 协程的名称,调试的时候很有用;
- CoroutineExceptionHandler: 处理未被捕捉的异常。
CoroutineContext有两个非常重要的元素 —Job和Dispatcher,Job是当前的Coroutine实例而Dispatcher决定了当前Coroutine执行的线程,还可以添加CoroutineName,用于调试,添加CoroutineExceptionHandler用于捕获异常,它们都实现了Element接口。看一个例子:
fun main() {
val coroutineContext = Job() + Dispatchers.Default + CoroutineName(“myContext”)
println(“
c
o
r
o
u
t
i
n
e
C
o
n
t
e
x
t
,
coroutineContext,
coroutineContext,{coroutineContext[CoroutineName]}”)
val newCoroutineContext = coroutineContext.minusKey(CoroutineName)
println(“$newCoroutineContext”)
}
输出结果如下:
[JobImpl{Active}@7eda2dbb, CoroutineName(myContext), Dispatchers.Default],CoroutineName(myContext)
[JobImpl{Active}@7eda2dbb, Dispatchers.Default]
CoroutineContext接口的定义如下:
public interface CoroutineContext {
public operator fun get(key: Key): E?
public fun fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext{…}
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key
public interface Element : CoroutineContext {…}
}
CoroutineContext 定义了四个核心的操作:
- 操作符get
可以通过 key 来获取这个 Element。由于这是一个 get 操作符,所以可以像访问 map 中的元素一样使用 context[key] 这种中括号的形式来访问。
- 操作符 plus
和 Set.plus 扩展函数类似,返回一个新的 context 对象,新的对象里面包含了两个里面的所有 Element,如果遇到重复的(Key 一样的),那么用+号右边的 Element 替代左边的。+ 运算符可以很容易的用于结合上下文,但是有一个很重要的事情需要小心 —— 要注意它们结合的次序,因为这个 + 运算符是不对称的。
- fun fold(initial: R, operation: (R, Element) -> R): R
和 Collection.fold 扩展函数类似,提供遍历当前 context 中所有 Element 的能力。
- fun minusKey(key: Key<*>): CoroutineContext
返回一个上下文,其中包含该上下文中的元素,但不包含具有指定key的元素。
某些情况需要一个上下文不持有任何元素,此时就可以使用 EmptyCoroutineContext 对象。可以预见,添加这个对象到另一个上下文不会对其有任何影响。
在任务层级中,每个协程都会有一个父级对象,要么是
CoroutineScope或者另外一个coroutine。然而,实际上协程的父级CoroutineContext和父级协程的CoroutineContext是不一样的,因为有如下的公式:
父级上下文 = 默认值 + 继承的 CoroutineContext + 参数
其中:
- 一些元素包含默认值: Dispatchers.Default 是默认的 CoroutineDispatcher,以及 “coroutine” 作为默认的 CoroutineName;
- 继承的 CoroutineContext 是 CoroutineScope 或者其父协程的 CoroutineContext;
- 传入协程 builder 的参数的优先级高于继承的上下文参数,因此会覆盖对应的参数值。
请注意: CoroutineContext 可以使用 " + " 运算符进行合并。由于 CoroutineContext 是由一组元素组成的,所以加号右侧的元素会覆盖加号左侧的元素,进而组成新创建的 CoroutineContext。比如,(Dispatchers.Main, "name") + (Dispatchers.IO) = (Dispatchers.IO, "name")。
Job & Deferred - 任务
Job用于处理协程。对于每一个所创建的协程 (通过launch或者 async),它会返回一个Job实例,该实例是协程的唯一标识,并且负责管理协程的生命周期
CoroutineScope.launch函数返回的是一个Job对象,代表一个异步的任务。Job具有生命周期并且可以取消。Job还可以有层级关系,一个Job可以包含多个子Job,当父Job被取消后,所有的子Job也会被自动取消;当子Job被取消或者出现异常后父Job也会被取消。除了通过
CoroutineScope.launch来创建Job对象之外,还可以通过Job()工厂方法来创建该对象。默认情况下,子Job的失败将会导致父Job被取消,这种默认的行为可以通过SupervisorJob来修改。具有多个子
Job的父Job会等待所有子Job完成(或者取消)后,自己才会执行完成
Job 的状态
一个任务可以包含一系列状态: 新创建 (New)、活跃 (Active)、完成中 (Completing)、已完成 (Completed)、取消中 (Cancelling) 和已取消 (Cancelled)。虽然我们无法直接访问这些状态,但是我们可以访问
Job的属性:isActive、isCancelled和isCompleted。如果协程处于活跃状态,协程运行出错或者调用
job.cancel()都会将当前任务置为取消中 (Cancelling) 状态 (isActive = false, isCancelled = true)。当所有的子协程都完成后,协程会进入已取消 (Cancelled) 状态,此时isCompleted = true。
| State | [isActive] | [isCompleted] | [isCancelled] |
|---|---|---|---|
| New (optional initial state) | false | false | false |
| Active (default initial state) | true | false | false |
| Completing (transient state) | true | false | false |
| Cancelling (transient state) | false | false | true |
| Cancelled (final state) | false | true | true |
| Completed (final state) | false | true | false |
wait children
±----+ start ±-------+ complete ±------------+ finish ±----------+
| New | -----> | Active | ---------> | Completing | -------> | Completed |
±----+ ±-------+ ±------------+ ±----------+
| cancel / fail |
| ±---------------+
| |
V V
±-----------+ finish ±----------+
| Cancelling | --------------------------------> | Cancelled |
±-----------+ ±----------+
Job 的常用函数
这些函数都是线程安全的,所以可以直接在其他
Coroutine中调用。
- fun start(): Boolean
调用该函数来启动这个 Coroutine,如果当前 Coroutine 还没有执行调用该函数返回 true,如果当前 Coroutine 已经执行或者已经执行完毕,则调用该函数返回 false
- fun cancel(cause: CancellationException? = null)
通过可选的取消原因取消此作业。 原因可以用于指定错误消息或提供有关取消原因的其他详细信息,以进行调试。
- fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle
通过这个函数可以给 Job 设置一个完成通知,当 Job 执行完成的时候会同步执行这个通知函数。 回调的通知对象类型为:typealias CompletionHandler = (cause: Throwable?) -> Unit.
CompletionHandler 参数代表了 Job 是如何执行完成的。 cause 有下面三种情况: – 如果 Job 是正常执行完成的,则 cause 参数为 null – 如果 Job 是正常取消的,则 cause 参数为 CancellationException 对象。这种情况不应该当做错误处理,这是任务正常取消的情形。所以一般不需要在错误日志中记录这种情况。 – 其他情况表示 Job 执行失败了。
这个函数的返回值为 DisposableHandle 对象,如果不再需要监控 Job 的完成情况了, 则可以调用 DisposableHandle.dispose 函数来取消监听。如果 Job 已经执行完了, 则无需调用 dispose 函数了,会自动取消监听。
- suspend fun join()
join 函数和前面三个函数不同,这是一个 suspend 函数。所以只能在 Coroutine 内调用。
这个函数会暂停当前所处的 Coroutine直到该Coroutine执行完成。所以 Job 函数一般用来在另外一个 Coroutine 中等待 job 执行完成后继续执行。当 Job 执行完成后, job.join 函数恢复,这个时候 job 这个任务已经处于完成状态了,而调用 job.join 的Coroutine还继续处于 activie 状态。
请注意,只有在其所有子级都完成后,作业才能完成
该函数的挂起是可以被取消的,并且始终检查调用的Coroutine的Job是否取消。如果在调用此挂起函数或将其挂起时,调用Coroutine的Job被取消或完成,则此函数将引发 CancellationException。
Deferred
public interface Deferred : Job {
public val onAwait: SelectClause1
public suspend fun await(): T
@ExperimentalCoroutinesApi
public fun getCompleted(): T
@ExperimentalCoroutinesApi
public fun getCompletionExceptionOrNull(): Throwable?
}
通过使用
async创建协程可以得到一个有返回值Deferred,Deferred接口继承自Job接口,额外提供了获取Coroutine返回结果的方法。由于Deferred继承自 Job 接口,所以Job相关的内容在Deferred上也是适用的。 Deferred 提供了额外三个函数来处理和Coroutine执行结果相关的操作。
- suspend fun await(): T
用来等待这个Coroutine执行完毕并返回结果。
- fun getCompleted(): T
用来获取Coroutine执行的结果。如果Coroutine还没有执行完成则会抛出 IllegalStateException ,如果任务被取消了也会抛出对应的异常。所以在执行这个函数之前,可以通过 isCompleted 来判断一下当前任务是否执行完毕了。
- fun getCompletionExceptionOrNull(): Throwable?
获取已完成状态的Coroutine异常信息,如果任务正常执行完成了,则不存在异常信息,返回null。如果还没有处于已完成状态,则调用该函数同样会抛出 IllegalStateException,可以通过 isCompleted 来判断一下当前任务是否执行完毕了。
SupervisorJob
SupervisorJob 是一个顶层函数,定义如下:
/**
- Creates a supervisor job object in an active state.
- Children of a supervisor job can fail independently of each other.
- A failure or cancellation of a child does not cause the supervisor job to fail and does not affect its other children,
- so a supervisor can implement a custom policy for handling failures of its children:
-
- A failure of a child job that was created using [launch][CoroutineScope.launch] can be handled via [CoroutineExceptionHandler] in the context.
-
- A failure of a child job that was created using [async][CoroutineScope.async] can be handled via [Deferred.await] on the resulting deferred value.
- If [parent] job is specified, then this supervisor job becomes a child job of its parent and is cancelled when its
- parent fails or is cancelled. All this supervisor’s children are cancelled in this case, too. The invocation of
- [cancel][Job.cancel] with exception (other than [CancellationException]) on this supervisor job also cancels parent.
- @param parent an optional parent job.
*/
@Suppress(“FunctionName”)
public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)
该函数创建了一个处于 active 状态的supervisor job。如前所述, Job 是有父子关系的,如果子Job 失败了父Job会自动失败,这种默认的行为可能不是我们期望的。比如在 Activity 中有两个子Job分别获取一篇文章的评论内容和作者信息。如果其中一个失败了,我们并不希望父Job自动取消,这样会导致另外一个子Job也被取消。而SupervisorJob就是这么一个特殊的 Job,里面的子Job不相互影响,一个子Job失败了,不影响其他子Job的执行。SupervisorJob(parent:Job?) 具有一个parent参数,如果指定了这个参数,则所返回的 Job 就是参数 parent 的子Job。如果 Parent Job 失败了或者取消了,则这个 Supervisor Job 也会被取消。当 Supervisor Job 被取消后,所有 Supervisor Job 的子Job也会被取消。
MainScope() 的实现就使用了 SupervisorJob 和一个 Main Dispatcher:
/**
-
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是很容易被误解的,它和协程异常处理、子协程所属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:
-
showSomeDatareturns as soon as the data is loaded and displayed in the UI.
-
- If
doSomeWorkthrows an exception, then theasynctask is cancelled andshowSomeDatarethrows that exception.
- If
-
- If the outer scope of
showSomeDatais cancelled, both startedasyncandwithContextblocks are cancelled.
- If the outer scope of
-
- If the
asyncblock fails,withContextwill be cancelled.
- If the
-
The method may throw a [CancellationException] if the current job was cancelled externally
-
or may throw a corresponding unhandled [Throwable] if there is any unhandled exception in this scope
-
(for example, from a crashed coroutine that was started with [launch][CoroutineScope.launch] in this scope).
*/
public suspend fun coroutineScope(block: suspend CoroutineScope.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = ScopeCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
首先这两个函数都是挂起函数,需要运行在协程内或挂起函数内。supervisorScope属于主从作用域,会继承父协程的上下文,它的特点就是子协程的异常不会影响父协程,它的设计应用场景多用于子协程为独立对等的任务实体的时候,比如一个下载器,每一个子协程都是一个下载任务,当一个下载任务异常时,它不应该影响其他的下载任务。coroutineScope和supervisorScope都会返回一个作用域,它俩的差别就是异常传播:coroutineScope 内部的异常会向上传播,子协程未捕获的异常会向上传递给父协程,任何一个子协程异常退出,会导致整体的退出;supervisorScope 内部的异常不会向上传播,一个子协程异常退出,不会影响父协程和兄弟协程的运行。
协程的取消和异常
普通协程如果产生未处理异常会将此异常传播至它的父协程,然后父协程会取消所有的子协程、取消自己、将异常继续向上传递。下面拿一个官方的图来示例这个过程:

这种情况有的时候并不是我们想要的,我们更希望一个协程在产生异常时,不影响其他协程的执行,在上文中我们也提到了一些解决方案,下面我们就在实践一下。
使用SupervisorJob**
在上文中我们也对这个顶层函数做了讲解,那如何使用呢?直接上代码:
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
/**
- 使用官方库的 MainScope()获取一个协程作用域用于创建协程
*/
private val mScope = MainScope()
companion object {
const val TAG = “Kotlin Coroutine”
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mScope.launch(Dispatchers.Default) {
delay(500)
Log.e(TAG, “Child 1”)
}
mScope.launch(Dispatchers.Default) {
delay(1000)
Log.e(TAG, “Child 2”)
throw RuntimeException(“–> RuntimeException <–”)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。





既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
希望大家能有一个好心态,想进什么样的公司要想清楚,并不一定是大公司,我选的也不是特大厂。当然如果你不知道选或是没有规划,那就选大公司!希望我们能先选好想去的公司再投或内推,而不是有一个公司要我我就去!还有就是不要害怕,也不要有压力,平常心对待就行,但准备要充足。最后希望大家都能拿到一份满意的 offer !如果目前有一份工作也请好好珍惜好好努力,找工作其实挺累挺辛苦的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
开发知识点,真正体系化!**
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
希望大家能有一个好心态,想进什么样的公司要想清楚,并不一定是大公司,我选的也不是特大厂。当然如果你不知道选或是没有规划,那就选大公司!希望我们能先选好想去的公司再投或内推,而不是有一个公司要我我就去!还有就是不要害怕,也不要有压力,平常心对待就行,但准备要充足。最后希望大家都能拿到一份满意的 offer !如果目前有一份工作也请好好珍惜好好努力,找工作其实挺累挺辛苦的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
[外链图片转存中…(img-DoEFLSzh-1712340184750)]
本文深入探讨Kotlin协程,介绍了协程上下文、任务、调度器、启动模式和作用域等核心概念。通过示例展示协程并发优势,还讲解了Job状态和常用函数,以及协程取消和异常处理方法,如使用SupervisorJob避免异常传播影响其他协程。

1348

被折叠的 条评论
为什么被折叠?



