一篇文章带你彻底搞懂Kotlin的协程

取消功能

下面执行了两个job,第一个是原始的,第二个是在1秒后取消第一个job,这会导致tvText的文本并不会改变

val job = lifecycleScope.launch {

try {

val text=getText()

tvTest.text = text

} catch (e:Exception){

e.printStackTrace()

}

}

lifecycleScope.launch {

delay(1000)

job.cancel()

}

设置超时

这个相当于系统封装了自动取消功能,对应函数withTimeout

lifecycleScope.launch {

try {

withTimeout(1000) {

val text = getText()

tvTest.text = text

}

} catch (e:Exception){

e.printStackTrace()

}

}

带返回值的Job

与launch类似的还有一个async方法,它会返回一个Deferred对象,属于Job的扩展类,Deferred可以获取返回的结果,具体使用如下

lifecycleScope.launch {

val one= async {

delay(1000)

return@async 1

}

val two= async {

delay(2000)

return@async 2

}

Log.i(“scope test”,(one.await()+two.await()).toString())

}

高级进阶


自定义CoroutineScope

先看CoroutineScope源码

public interface CoroutineScope {

public val coroutineContext: CoroutineContext

}

CoroutineScope中主要包含一个coroutineContext对象,我们要自定义只需实现coroutineContext的get方法

class TestScope() : CoroutineScope {

override val coroutineContext: CoroutineContext

get() = TODO(“Not yet implemented”)

}

要创建coroutineContext,得要先知道CoroutineContext是什么,我们再看CoroutineContext源码

/**

  • Persistent context for the coroutine. It is an indexed set of [Element] instances.

  • An indexed set is a mix between a set and a map.

  • Every element in this set has a unique [Key].

*/

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 {

}

}

通过注释说明,我们知道它本质就是一个包含Element的集合,只是不像set和map集合一样,它自己实现了获取(get),折叠(fold,添加和替换的组合),相减(minusKey,移除),对象组合(plus,如val coroutineContext=coroutineContext1+coroutineContext2)

它的主要内容是Element,而Element的实现有

  • Job 任务

  • ContinuationInterceptor 拦截器

  • AbstractCoroutineContextElement

  • CoroutineExceptionHandler

  • ThreadContextElement

  • DownstreamExceptionElement

可以看到很多地方都有实现Element,它主要目的是限制范围以及异常的处理。这里我们先了解两个重要的Element,一个是Job,一个是CoroutineDispatcher

Job
  • Job:子Job取消,会导致父job和其他子job被取消;父job取消,所有子job被取消

  • SupervisorJob:父job取消,所有子job被取消

CoroutineDispatcher
  • Dispatchers.Main:主线程执行

  • Dispatchers.IO:IO线程执行

我们模拟一个类似lifecycleScope的自定义TestScope

class TestScope() : CoroutineScope {

override val coroutineContext: CoroutineContext

get() = SupervisorJob() +Dispatchers.Main

}

这里我们定义了一个总流程线SupervisorJob()以及具体执行环境Dispatchers.Main(Android主线程),假如我们想替换掉activity的lifecycleScope,就需要在activity中创建实例

val testScope=TestScope()

然后在activity销毁的时候取消掉所有job

override fun onDestroy() {

testScope.cancel()

super.onDestroy()

}

其他使用方式同lifecycleScope,如

testScope.launch{

val text = getText()

tvTest.text = text

}

深入理解Job

CoroutineScope中包含一个主Job,之后调用的launch或其他方法创建的job都属于CoroutineScope的子Job,每个job都有属于自己的状态,其中包括isActive、isCompleted、isCancelled,以及一些基础操作start()、cancel()、join(),具体的转换流程如下

job状态图.png

我们先从创建job开始,当调用launch的时候默认有三个参数CoroutineContext、CoroutineStart以及代码块参数。

  • context:CoroutineContext的对象,默认为CoroutineStart.DEFAULT,会与CoroutineScope的context进行折叠

  • start:CoroutineStart的对象,默认为CoroutineStart.DEFAULT,代表立即执行,同时还有CoroutineStart.LAZY,代表非立即执行,必须调用job的start()才会开始执行

val job2= lifecycleScope.launch(start = CoroutineStart.LAZY) {

delay(2000)

Log.i(“scope test”,“lazy”)

}

job2.start()

当使用这种模式创建时默认就是new状态,此时isActive,isCompleted,isCancelled都为false,当调用start后,转换为active状态,其中只有isActive为true,如果它的任务完成了则会进入Completing状态,此时为等待子job完成,这种状态下还是只有isActive为true,如果所有子job也完成了则会进入Completed状态,只有isCompleted为true。如果在active或Completing状态下出现取消或异常,则会进入Cancelling状态,如果需要取消父job和其他子job则会等待它们取消完成,此时只有isCancelled为true,取消完成后最终进入Cancelled状态,isCancelled和isCompleted都为true

| State | isActive | isCompleted | isCancelled |

| — | — | — | — |

| | | | |

| New | FALSE | FALSE | FALSE |

| Active | TRUE | FALSE | FALSE |

| Completing | TRUE | FALSE | FALSE |

| Cancelling | FALSE | FALSE | TRUE |

| Cancelled | FALSE | TRUE | TRUE |

| Completed | FALSE | TRUE | FALSE |

不同job交互需使用join()与cancelAndJoin()

  • join():将当前job添加到其他协程任务里面

  • cancelAndJoin():取消操作,只是添加进去后再取消

val job1= GlobleScope.launch(start = CoroutineStart.LAZY) {

delay(2000)

Log.i(“scope test”,“job1”)

}

lifecycleScope.launch {

job1.join()

delay(2000)

Log.i(“scope test”,“job2”)

}

深入理解suspend

suspend作为kotlin新增的方法修饰词,最终实现还是java,我们先看它们的差异性

suspend fun test1(){}

fun test2(){}

对应java代码

public final Object test1(@NotNull Continuation $completion) {

return Unit.INSTANCE;

}

public final void test2() {

}

对应字节码

public final test1(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;

L0

LINENUMBER 6 L0

GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;

ARETURN

L1

LOCALVARIABLE this Lcom/lieni/android_c/ui/test/TestActivity; L0 L1 0

LOCALVARIABLE $completion Lkotlin/coroutines/Continuation; L0 L1 1

MAXSTACK = 1

MAXLOCALS = 2

public final test2()V

L0

LINENUMBER 9 L0

RETURN

L1

LOCALVARIABLE this Lcom/lieni/android_c/ui/test/TestActivity; L0 L1 0

MAXSTACK = 0

MAXLOCALS = 1

可以看到,加了suspend的方法其实和普通方法一样,只是传入时多了个Continuation对象,并返回了Unit.INSTANCE对象

Continuation是一个接口,包含了context对象和resumeWith方法

public interface Continuation {

public val context: CoroutineContext

public fun resumeWith(result: Result)

}

而Continuation的具体实现在BaseContinuationImpl中

internal abstract class BaseContinuationImpl(…) : Continuation<Any?>, CoroutineStackFrame, Serializable {

public final override fun resumeWith(result: Result<Any?>) {

while (true) {

with(current) {

val outcome = invokeSuspend(param)

releaseIntercepted()

if (completion is BaseContinuationImpl) {

} else {

return

}

}

}

}

}

当我们调用resumeWith时,它会一直执行一个循环,调用invokeSuspend(param)和releaseIntercepted() ,直到最顶层completion执行完成后返回,并且释放协程的interceptor

最终的释放在ContinuationImpl中实现

internal abstract class ContinuationImpl(…) : BaseContinuationImpl(completion) {

protected override fun releaseIntercepted() {

val intercepted = intercepted

if (intercepted != null && intercepted !== this) {

context[ContinuationInterceptor]!!.releaseInterceptedContinuation(intercepted)

}

this.intercepted = CompletedContinuation

}

}

通过这里知释放最终通过CoroutineContext中为ContinuationInterceptor的Element来实现

而暂停也是同理,继续看suspendCoroutine

public suspend inline fun suspendCoroutine(crossinline block: (Continuation) -> Unit): T =

suspendCoroutineUninterceptedOrReturn { c: Continuation ->

val safe = SafeContinuation(c.intercepted())

}

默认会调用Continuation的intercepted()方法

internal abstract class ContinuationImpl(…) : BaseContinuationImpl(completion) {

public fun intercepted(): Continuation<Any?> =intercepted

?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)

.also { intercepted = it }

}

可以看到暂停最终也是通过CoroutineContext中为ContinuationInterceptor的Element来实现

流程总结(线程切换)

  • 创建新的Continuation

  • 调用CoroutineScope中的context的ContinuationInterceptor的interceptContinuation方法暂停父任务

  • 执行子任务(如果指定了线程,则在新线程执行,并传入Continuation对象)

  • 执行完毕后用户调用Continuation的resume或者resumeWith返回结果

  • 调用CoroutineScope中的context的ContinuationInterceptor的releaseInterceptedContinuation方法恢复父任务

阻塞与非阻塞

CoroutineScope默认是不会阻塞当前线程的,如果需要阻塞可以使用runBlocking,如果在主线程执行下面代码,会出现2s白屏

runBlocking {

delay(2000)

Log.i(“scope test”,“runBlocking is completed”)

}

阻塞原理:执行runBlocking默认会创建BlockingCoroutine,而BlockingCoroutine中会一直执行一个循环,直到当前Job为isCompleted状态才会跳出循环

public fun runBlocking(…): T {

val coroutine = BlockingCoroutine(newContext, currentThread, eventLoop)

coroutine.start(CoroutineStart.DEFAULT, coroutine, block)

return coroutine.joinBlocking()

}

private class BlockingCoroutine(…) : AbstractCoroutine(parentContext, true) {

fun joinBlocking(): T {

while (true) {

if (isCompleted) break

}

}

}

下面分享给大家一份谷歌大佬编写高级Kotlin强化实战手册(附Demo)。
第一章 Kotlin入门教程
  • Kotlin 概述

  • Kotlin 与 Java 比较

  • 巧用 Android Studio

  • 认识 Kotlin 基本类型

  • 走进 Kotlin 的数组
    自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?

改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

-FpqIHvSo-1713496785731)]

[外链图片转存中…(img-PSAAZVuO-1713496785732)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?

改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-aZoI1LHH-1713496785733)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值