- 在UI组件中使用LifecycleOwner.lifecycleScope,在ViewModel中使用ViewModel.viewModelScope
CoroutineScope.async
- async函数实现返回值处理或者并发处理
返回值处理
private fun asyncReturn() {
mScope.launch(Dispatchers.Main) {
//新开一个协程去执行协程体,父协程的代码会接着往下走
var deferred = async(Dispatchers.IO) {
delay(1000)
“Coroutine-Async”
}
//等待async执行完成获取返回值,并不会阻塞线程,而是挂起,将线程的执行权交出去
//直到async的协程体执行完毕后,会恢复协程继续执行
val data = deferred.await()
Alerter.create(this@LearnCoroutineActivity).setTitle(“Result”).setText(data).show()
}
}
并发处理
private fun asyncConcurrent() {
//coroutineContext的创建下文会有分析
var coroutineContext = Job() +
Dispatchers.Main +
CoroutineExceptionHandler { coroutineContext, throwable ->
Log.e(
“CoroutineException”,
“CoroutineExceptionHandler: KaTeX parse error: Expected 'EOF', got '}' at position 14: throwable" ) }̲ + CoroutineNam…{job1.await()},job2:
j
o
b
2.
a
w
a
i
t
(
)
,
j
o
b
3
:
{job2.await()},job3:
job2.await(),job3:{job3.await()}”).show()
}
}
可以看到,这就是同步代码风格编写异步代码的强大之处,下面开始源码解析
3、coroutineContext解析
-
CoroutineContext是一个特殊的集合,同时包含了Map和Set的特点
-
集合内部的元素Element是根据key去对应(Map特点),但是不允许重复(Set特点)
-
Element之间可以通过+号进行组合
-
Element有如下四类,共同组成了CoroutineContext
-
Job:协程的唯一标识,用来控制协程的生命周期(new、active、completing、completed、cancelling、cancelled)
-
CoroutineDispatcher:指定协程运行的线程(IO、Default、Main、Unconfined)
-
CoroutineName: 指定协程的名称,默认为coroutine
-
CoroutineExceptionHandler: 指定协程的异常处理器,用来处理未捕获的异常
3.1、Job Element
- 每一个所创建的协程 (通过
launch
或者 async),会返回一个Job
实例,该实例是协程的唯一标识,并且负责管理协程的生命周期
Job状态
Job在执行的过程中,包含了一系列状态,虽然开发者没办法直接获取所有状态,但是Job之中有如下三个属性
- isActive(是否活动)
- isCompleted(是否已完成)
- isCancelled(是否已取消)
根据属性就可以推断出Job的所处状态,状态如下
- 新创建 (New)
- 当一个协程创建后就处于新建(New)状态
- 活跃 (Active)
- 当调用Job的start/join方法后协程就处于活跃(Active)状态
- 完成中 (Completing)
- 当协程执行完成后或者调用CompletableJob(CompletableJob是Job的一个子接口)的complete方法都会让当前协程进入完成中(Completing)状态
- 已完成 (Completed)
- 处于完成中状态的协程会等所有子协程都完成后才进入完成(Completed)状态
- 取消中 (Cancelling)
- 当运行出错或者调用Job的cancel方法都会将当前协程置为取消中(Cancelling)状态
- 已取消 (Cancelled)
- 处于取消中状态的协程会等所有子协程都完成后才进入取消 (Cancelled)状态
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方法
fun start(): Boolean
- 调用该函数来启动这个
Coroutine
,如果当前Coroutine
还没有执行调用该函数返回true
,如果当前Coroutine
已经执行或者已经执行完毕,则调用该函数返回false
fun cancel(cause: CancellationException? = null)
- 通过可选的取消原因取消Job
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()(suspend函数)
- 用来在另外一个
Coroutine
中等待 job 执行完成后继续执行。
Job异常传播
- 协程是有父子级的概念,如果子Job在运行过程之中发生异常,那么父Job就会感知到并抛出异常。如果要抑制这种行为就需要使用SupervisorJob
除了CancellationException以外的异常
SupervisorJob
fun main(){
val parentJob = GlobalScope.launch {
//childJob是一个SupervisorJob
val childJob = launch(SupervisorJob()){
throw NullPointerException()
}
childJob.join()
println(“parent complete”)
}
Thread.sleep(1000)
}
此时childJob抛出异常并不会影响parentJob的运行,parentJob会继续运行并输出parent complete。
3.2、CoroutineDispatcher Element
- 用于指定协程的运行线程
- kotlin已经内置了CoroutineDispatcher的4个实现,可以通过Dispatchers的Default、IO、Main、Unconfined字段分别返回使用
public actual object Dispatchers {
@JvmStatic
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
@JvmStatic
public val IO: CoroutineDispatcher = DefaultScheduler.IO
@JvmStatic
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
@JvmStatic
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
}
Default,IO
Default,IO其实内部用的是一个线程池,下面逐个解析,看实现原理
default
- Default会根据useCoroutinesScheduler属性(默认为true)去获取对应的线程池
- DefaultScheduler(useCoroutinesScheduler=ture):kotlin自己实现的线程池逻辑
- CommonPool(useCoroutinesScheduler=false):java类库中的Executor实现线程池逻辑
internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
if (useCoroutinesScheduler) DefaultScheduler else CommonPool
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
…
}
//委托类
public open class ExperimentalCoroutineDispatcher(
private val corePoolSize: Int,
private val maxPoolSize: Int,
private val idleWorkerKeepAliveNs: Long,
private val schedulerName: String = “CoroutineScheduler”
) : ExecutorCoroutineDispatcher() {
}
//java类库中的Executor实现线程池逻辑
internal object CommonPool : ExecutorCoroutineDispatcher() {}
//共同父类,定义行为
public abstract class ExecutorCoroutineDispatcher: CoroutineDispatcher(), Closeable {}
ExperimentalCoroutineDispatcher
- DefaultScheduler的主要实现都在它的父类ExperimentalCoroutineDispatcher中
public open class ExperimentalCoroutineDispatcher(
private val corePoolSize: Int,
private val maxPoolSize: Int,
private val idleWorkerKeepAliveNs: Long,
private val schedulerName: String = “CoroutineScheduler”
) : ExecutorCoroutineDispatcher() {
public constructor(
corePoolSize: Int = CORE_POOL_SIZE,
maxPoolSize: Int = MAX_POOL_SIZE,
schedulerName: String = DEFAULT_SCHEDULER_NAME
) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS, schedulerName)
…//省略构造
//创建CoroutineScheduler实例
private fun createScheduler() = CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)
override val executor: Executorget() = coroutineScheduler
override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
try {
//dispatch方法委托到CoroutineScheduler的dispatch方法
coroutineScheduler.dispatch(block)
} catch (e: RejectedExecutionException) {
…
}
override fun dispatchYield(context: CoroutineContext, block: Runnable): Unit =
try {
//dispatchYield方法委托到CoroutineScheduler的dispatchYield方法
coroutineScheduler.dispatch(block, tailDispatch = true)
} catch (e: RejectedExecutionException) {
…
}
internal fun dispatchWithContext(block: Runnable, context: TaskContext, tailDispatch: Boolean) {
try {
//dispatchWithContext方法委托到CoroutineScheduler的dispatchWithContext方法
coroutineScheduler.dispatch(block, context, tailDispatch)
} catch (e: RejectedExecutionException) {
…
}
}
override fun close(): Unit = coroutineScheduler.close()
//实现请求阻塞
public fun blocking(parallelism: Int = BLOCKING_DEFAULT_PARALLELISM): CoroutineDispatcher {
require(parallelism > 0) { “Expected positive parallelism level, but have $parallelism” }
return LimitingDispatcher(this, parallelism, null, TASK_PROBABLY_BLOCKING)
}
//实现请求数量限制
public fun limited(parallelism: Int): CoroutineDispatcher {
require(parallelism > 0) { “Expected positive parallelism level, but have KaTeX parse error: Expected 'EOF', got '}' at position 14: parallelism" }̲ require(parall…corePoolSize), but have $parallelism” }
return LimitingDispatcher(this, parallelism, null, TASK_NON_BLOCKING)
}
…//省略一些供测试的方法,更好的跟踪同步状态
}
IO
- IO的实现其实是LimitingDispatcher
val IO: CoroutineDispatcher = LimitingDispatcher(
this,
systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)),
“Dispatchers.IO”,
TASK_PROBABLY_BLOCKING
)
LimitingDispatcher
- IO的实现类会有一些最大请求限制,以及队列处理
private class LimitingDispatcher(
private val dispatcher: ExperimentalCoroutineDispatcher,
private val parallelism: Int,
private val name: String?,
override val taskMode: Int
) : ExecutorCoroutineDispatcher(), TaskContext, Executor {
//同步阻塞队列
private val queue = ConcurrentLinkedQueue()
//cas计数
private val inFlightTasks = atomic(0)
override fun dispatch(context: CoroutineContext, block: Runnable) = dispatch(block, false)
private fun dispatch(block: Runnable, tailDispatch: Boolean) {
var taskToSchedule = block
while (true) {
if (inFlight <= parallelism) {
//LimitingDispatcher的dispatch方法委托给了DefaultScheduler的dispatchWithContext方法
dispatcher.dispatchWithContext(taskToSchedule, this, tailDispatch)
return
}
…//省略了一些队列处理逻辑
}
}
}
CoroutineScheduler
- Default、IO其实都是共享CoroutineScheduler线程池,Kotlin实现了一套线程池两种调度策略
- 通过内部的mode区分
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
…
if (task.mode == TASK_NON_BLOCKING) {
if (skipUnpark) return
signalCpuWork()
} else {
signalBlockingWork(skipUnpark = skipUnpark)
}
}
Mode
Type | Mode |
---|---|
Default | TASK_NON_BLOCKING |
IO | TASK_PROBABLY_BLOCKING |
处理策略
Type | Mode |
---|---|
Default | CoroutineScheduler最多有corePoolSize个线程被创建,corePoolSize它的取值为max(2, CPU核心数), |
即它会尽量的等于CPU核心数 | |
IO | 创建比corePoolSize更多的线程来运行IO型任务,但不能大于maxPoolSize |
1.公式:max(corePoolSize, min(CPU核心数 * 128, 2^21 - 2)),即大于corePoolSize,小于2^21 - 2 | |
2.2^21 - 2是一个很大的数约为2M,但是CoroutineScheduler是不可能创建这么多线程的,所以就需要外部限制提交的任务数 | |
3.Dispatchers.IO构造时就通过LimitingDispatcher默认限制了最大线程并发数parallelism为max(64, CPU核心数),即最多只能提交parallelism个任务到CoroutineScheduler中执行,剩余的任务被放进队列中等待。 |
适合场景
Type | Mode |
---|---|
Default | 1.CPU密集型任务的特点是执行任务时CPU会处于忙碌状态,任务会消耗大量的CPU资源 |
2.复杂计算、视频解码等,如果此时线程数太多,超过了CPU核心数,那么这些超出来的线程是得不到CPU的执行的,只会浪费内存资源 | |
3.因为线程本身也有栈等空间,同时线程过多,频繁的线程切换带来的消耗也会影响线程池的性能 | |
4.对于CPU密集型任务,线程池并发线程数等于CPU核心数才能让CPU的执行效率最大化 | |
IO | 1.IO密集型任务的特点是执行任务时CPU会处于闲置状态,任务不会消耗大量的CPU资源 |
2.网络请求、IO操作等,线程执行IO密集型任务时大多数处于阻塞状态,处于阻塞状态的线程是不占用CPU的执行时间 | |
3.此时CPU就处于闲置状态,为了让CPU忙起来,执行IO密集型任务时理应让线程的创建数量更多一点,理想情况下线程数应该等于提交的任务数,对于这些多创建出来的线程,当它们闲置时,线程池一般会有一个超时回收策略,所以大部分情况下并不会占用大量的内存资源 | |
4.但也会有极端情况,所以对于IO密集型任务,线程池并发线程数应尽可能地多才能提高CPU的吞吐量,这个尽可能地多的程度并不是无限大,而是根据业务情况设定,但肯定要大于CPU核心数。 |
Unconfined
- 任务执行在默认的启动线程。之后由调用
resume
的线程决定恢复协程的线程。
internal object Unconfined : CoroutineDispatcher() {
//为false为不需要dispatch
override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
override fun dispatch(context: CoroutineContext, block: Runnable) {
// 只有当调用yield方法时,Unconfined的dispatch方法才会被调用
// yield() 表示当前协程让出自己所在的线程给其他协程运行
val yieldContext = context[YieldContext]
if (yieldContext != null) {
yieldContext.dispatcherWasUnconfined = true
return
}
throw UnsupportedOperationException("Dispatchers.Unconfined.dispatch function can only be used by the yield function. " +
"If you wrap Unconfined dispatcher in your code, make sure you properly delegate " +
“isDispatchNeeded and dispatch calls.”)
}
}
- 每一个协程都有对应的Continuation实例,其中的resumeWith用于协程的恢复,存在于DispatchedContinuation
DispatchedContinuation
- 我们重点看resumeWith的实现以及类委托
internal class DispatchedContinuation(
@JvmField val dispatcher: CoroutineDispatcher,
@JvmField val continuation: Continuation
) : DispatchedTask(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation by continuation {
…
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)
}
}
}
}
…
}
解析如下:
-
DispatchedContinuation通过类委托实现了在resumeWith()方法之前的代码逻辑添加
-
通过isDispatchNeeded(是否需要dispatch,Unconfined=false,default,IO=true)判断做不同处理
-
true:调用协程的CoroutineDispatcher的dispatch方法(上文有解析)
-
false:调用executeUnconfined方法
private inline fun DispatchedContinuation<*>.executeUnconfined(
contState: Any?, mode: Int, doYield: Boolean = false,
block: () -> Unit
): Boolean {
assert { mode != MODE_UNINITIALIZED }
val eventLoop = ThreadLocalEventLoop.eventLoop
if (doYield && eventLoop.isUnconfinedQueueEmpty) return false
return if (eventLoop.isUnconfinedLoopActive) {
_state = contState
resumeMode = mode
eventLoop.dispatchUnconfined(this)
true
} else {
runUnconfinedEventLoop(eventLoop, block = block)
false
}
}
- 从threadlocal中取出eventLoop(eventLoop和当前线程相关的),判断是否在执行Unconfined任务
- 如果在执行则调用EventLoop的dispatchUnconfined方法把Unconfined任务放进EventLoop中
- 如果没有在执行则直接执行
internal inline fun DispatchedTask<*>.runUnconfinedEventLoop(
eventLoop: EventLoop,
block: () -> Unit
) {
eventLoop.incrementUseCount(unconfined = true)
try {
block()
while (true) {
if (!eventLoop.processUnconfinedEvent()) break
}
} catch (e: Throwable) {
handleFatalException(e, null)
} finally {
eventLoop.decrementUseCount(unconfined = true)
}
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
感谢您的阅读,在文末给大家准备一个福利。本人从事Android开发已经有十余年,算是一名资深的移动开发架构师了吧。根据我的观察发现,对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。
所以在此将我十年载,从萌新小白一步步成长为Android移动开发架构师的学习笔记,从Android四大组件到手写实现一个架构设计,我都有一一的对应笔记为你讲解。
当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。
最后,赠与大家一句诗,共勉!
不驰于空想,不骛于虚声。不忘初心,方得始终。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
。
所以在此将我十年载,从萌新小白一步步成长为Android移动开发架构师的学习笔记,从Android四大组件到手写实现一个架构设计,我都有一一的对应笔记为你讲解。
当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。
[外链图片转存中…(img-R6DiTjnO-1712798404870)]
最后,赠与大家一句诗,共勉!
不驰于空想,不骛于虚声。不忘初心,方得始终。
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-n3CSQ56J-1712798404871)]