如果你还没有接触过协程,推荐你先阅读这篇入门级文章What? 你还不知道Kotlin Coroutine?
如果你已经接触过协程,相信你都有过以下几个疑问:
- 协程到底是个什么东西?
- 协程的
suspend
有什么作用,工作原理是怎样的? - 协程中的一些关键名称(例如:
Job
、Coroutine
、Dispatcher
、CoroutineContext
与CoroutineScope
)它们之间到底是怎么样的关系? - 协程的所谓非阻塞式挂起与恢复又是什么?
- 协程的内部实现原理是怎么样的?
- …
接下来的一些文章试着来分析一下这些疑问,也欢迎大家一起加入来讨论。
协程是什么
这个疑问很简单,只要你不是野路子接触协程的,都应该能够知道。因为官方文档中已经明确给出了定义。
下面来看下官方的原话(也是这篇文章最具有底气的一段话)。
协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码。
敲黑板划重点:协程是一种并发的设计模式。
所以并不是一些人所说的什么线程的另一种表现。虽然协程的内部也使用到了线程。但它更大的作用是它的设计思想。将我们传统的Callback
回调方式进行消除。将异步编程趋近于同步对齐。
解释了这么多,最后我们还是直接点,来看下它的优点
- 轻量:您可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
- 内存泄露更少:使用结构化并发机制在一个作用域内执行多个操作。
- 内置取消支持:取消功能会自动通过正在运行的协程层次结构传播。
- Jetpack集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。
suspend
suspend
是协程的关键字,每一个被suspend
修饰的方法都必须在另一个suspend
函数或者Coroutine
协程程序中进行调用。
第一次看到这个定义不知道你们是否有疑问,反正小憩我是很疑惑,为什么suspend
修饰的方法需要有这个限制呢?不加为什么就不可以,它的作用到底是什么?
当然,如果你有关注我之前的文章,应该就会有所了解,因为在重温Retrofit源码,笑看协程实现这篇文章中我已经有简单的提及。
这里涉及到一种机制俗称CPS(Continuation-Passing-Style)
。每一个suspend
修饰的方法或者lambda
表达式都会在代码调用的时候为其额外添加Continuation
类型的参数。
@GET(“/v2/news”)
suspend fun newsGet(@QueryMap params: Map<String, String>): NewsResponse
上面这段代码经过CPS
转换之后真正的面目是这样的
@GET(“/v2/news”)
fun newsGet(@QueryMap params: Map<String, String>, c: Continuation): Any?
经过转换之后,原有的返回类型NewsResponse
被添加到新增的Continutation
参数中,同时返回了Any?
类型。这里可能会有所疑问?返回类型都变了,结果不就出错了吗?
其实不是,Any?
在Kotlin
中比较特殊,它可以代表任意类型。
当suspend
函数被协程挂起时,它会返回一个特殊的标识COROUTINE_SUSPENDED
,而它本质就是一个Any
;当协程不挂起进行执行时,它将返回执行的结果或者引发的异常。这样为了让这两种情况的返回都支持,所以使用了Kotlin
独有的Any?
类型。
返回值搞明白了,现在来说说这个Continutation
参数。
首先来看下Continutation
的源码
public interface Continuation {
/**
- The context of the coroutine that corresponds to this continuation.
*/
public val context: CoroutineContext
/**
- Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
- return value of the last suspension point.
*/
public fun resumeWith(result: Result)
}
context
是协程的上下文,它更多时候是CombinedContext
类型,类似于协程的集合,这个后续会详情说明。
resumeWith
是用来唤醒挂起的协程。前面已经说过协程在执行的过程中,为了防止阻塞使用了挂起的特性,一旦协程内部的逻辑执行完毕之后,就是通过该方法来唤起协程。让它在之前挂起的位置继续执行下去。
所以每一个被suspend
修饰的函数都会获取上层的Continutation
,并将其作为参数传递给自己。既然是从上层传递过来的,那么Continutation
是由谁创建的呢?
其实也不难猜到,Continutation
就是与协程创建的时候一起被创建的。
GlobalScope.launch {
}
launch
的时候就已经创建了Continutation
对象,并且启动了协程。所以在它里面进行挂起的协程传递的参数都是这个对象。
简单的理解就是协程使用resumeWith
替换传统的callback
,每一个协程程序的创建都会伴随Continutation
的存在,同时协程创建的时候都会自动回调一次Continutation
的resumeWith
方法,以便让协程开始执行。
CoroutineContext
协程的上下文,它包含用户定义的一些数据集合,这些数据与协程密切相关。它类似于map
集合,可以通过key
来获取不同类型的数据。同时CoroutineContext
的灵活性很强,如果其需要改变只需使用当前的CoroutineContext
来创建一个新的CoroutineContext
即可。
来看下CoroutineContext
的定义
public interface CoroutineContext {
/**
- Returns the element with the given [key] from this context or
null
.
*/
public operator fun get(key: Key): E?
/**
- Accumulates entries of this context starting with [initial] value and applying [operation]
- from left to right to current accumulator value and each element of this context.
*/
public fun fold(initial: R, operation: (R, Element) -> R): R
/**
- Returns a context containing elements from this context and elements from other [context].
- The elements from this context with the same key as in the other one are dropped.
*/
public operator fun plus(context: CoroutineContext): CoroutineContext = …
/**
- Returns a context containing elements from this context, but without an element with
- the specified [key].
/
public fun minusKey(key: Key<>): CoroutineContext
/**
- Key for the elements of [CoroutineContext]. [E] is a type of element with this key.
*/
public interface Key
/**
- An element of the [CoroutineContext]. An element of the coroutine context is a singleton context by itself.
*/
public interface Element : CoroutineContext {…}
}
每一个CoroutineContext
都有它唯一的一个Key
其中的类型是Element
,我们可以通过对应的Key
来获取对应的具体对象。说的有点抽象我们直接通过例子来了解。
var context = Job() + Dispatchers.IO + CoroutineName(“aa”)
LogUtils.d(“$context,
c
o
n
t
e
x
t
[
C
o
r
o
u
t
i
n
e
N
a
m
e
]
"
)
c
o
n
t
e
x
t
=
c
o
n
t
e
x
t
.
m
i
n
u
s
K
e
y
(
J
o
b
)
L
o
g
U
t
i
l
s
.
d
(
"
{context[CoroutineName]}") context = context.minusKey(Job) LogUtils.d("
context[CoroutineName]")context=context.minusKey(Job)LogUtils.d("context”)
// 输出
[JobImpl{Active}@158b42c, CoroutineName(aa), LimitingDispatcher@aeb0f27[dispatcher = DefaultDispatcher]], CoroutineName(aa)
[CoroutineName(aa), LimitingDispatcher@aeb0f27[dispatcher = DefaultDispatcher]]
Job
、Dispatchers
与CoroutineName
都实现了Element
接口。
如果需要结合不同的CoroutineContext
可以直接通过+
拼接,本质就是使用了plus
方法。
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else // fast path – avoid lambda creation
context.fold(this) { acc, element ->
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext) element else {
// make sure interceptor is always last in the context (and thus is fast to get when present)
val interceptor = removed[ContinuationInterceptor]
if (interceptor == null) CombinedContext(removed, element) else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
plus
的实现逻辑是将两个拼接的CoroutineContext
封装到CombinedContext
中组成一个拼接链,同时每次都将ContinuationInterceptor
添加到拼接链的最尾部.
那么CombinedContext
又是什么呢?
internal class CombinedContext(
private val left: CoroutineContext,
private val element: Element
) : CoroutineContext, Serializable {
override fun get(key: Key): E? {
var cur = this
while (true) {
cur.element[key]?.let { return it }
val next = cur.left
if (next is CombinedContext) {
cur = next
} else {
return next[key]
}
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。
最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
【算法合集】
【延伸Android必备知识点】
【Android部分高级架构视频学习资源】
**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!