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
main
functions 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
runBlocking
invocation 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 - 推荐使用
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
针对于上面的问题,我总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
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、安卓逆向、云计算