AndroidX Coroutine

overview

Kotlin 协程的基本概念:协程是靠编译器实现的,并不需要操作系统和硬件的支持。

  • CoroutineContext 协程上下文,它包含一个默认的协程调度器,所有协程都必须在CoroutineContext中执行。

  • CoroutineScope 协程作用域,它一个接口只包含一个属性CoroutineContext,它定义了一个有生命周期的实体上的实现。

  • GlobalScope 一个全局的作用域,用于启动顶级的协程,这些协程会在整个应用程序的生命周期内运行。

  • CoroutineDispatcher 协程调度器,它用来调度和处理任务,决定了协程应该在哪个或哪些线程中执行。

  • lifecycleScope:生命周期范围,用于activity等有生命周期的组件,在DESTROYED的时候会自动结束。

  • viewModelScope:viewModel范围,用于ViewModel中,在ViewModel被回收时会自动结束。

CoroutineScope的作用域类型:

  • 顶级作用域:没有父协程的协程所在的作用域为顶级作用域。

  • 协同作用域:协程中启动新的协程,新协程为所在协程的子协程,这种情况下子协程抛出的未捕获异常将传递给父协程处理,父协程同时也会被取消。

  • 主从作用域:与协同作用域类似,但子协程的异常不会向上传递给父协程。

CoroutineScope的声明方式:(它们都遵循CoroutineScope interface,所以你可以自定义一个协程)

  • GlobalScope.launch:声明顶级作用域。

  • GlobalScope.async:声明顶级作用域。

  • runBlocking:声明顶级作用域。

  • coroutineScope:声明协同作用域。

  • supervisorScope:声明主从作用域,异常不会向上传递。

Kotlin中包含四种协程调度器,它们的作用分别是:

  • Dispatchers.Default:这是默认调度器,适合处理CPU密集型任务,如大量计算。它是一个CPU密集型任务调度器,通常用于后台计算。

  • Dispatchers.IO:这是IO调度器,适合执行IO相关操作,如文件读写、网络请求等。它是一个IO密集型任务调度器,通常用于处理文件操作和网络IO操作。

  • Dispatchers.Main:这是UI调度器,通常在主线程上执行任务,如更新UI。在Android平台上,这通常用于处理UI相关的操作。

  • Dispatchers.Unconfined:这是非受限制调度器,不会要求协程在哪个线程上执行。它会在启动它的线程上执行,直到第一个挂起点,然后恢复执行。

协程调度器的使用场景和特点:

  • Dispatchers.Default:适用于需要大量计算的任务,如数据排序、解析等。
  • Dispatchers.IO:适用于文件读写、数据库操作和网络请求等IO密集型任务。
  • Dispatchers.Main:适用于需要在主线程上执行的UI更新操作

... 开始代码示例:将会涉及到几乎所有的协程

  • launch && async
val job1 = GlobalScope.launch(start = CoroutineStart.LAZY) {
        delay(1000)
        println("hello launch")
        // async
        val result1 = async(start = CoroutineStart.DEFAULT) {

            delay(1000)
            println("hello async")
        }

        // invakeOnCompletion
        result1.invokeOnCompletion {
            if (it != null) {
                // result1.cancelAndJoin() 添加之后执行这里
                println("exception: ${it.message}")
            }
            else {
                println("result is complete")
            }
        }
        result1.cancelAndJoin()

    }
    // 默认是 CoroutineStart.DEFAULT,不需要调用job1.start()
    // CoroutineStart.LAZY时需要手动调用job1.start(),相当于懒加载
    // 使用 start 方法来启动协程,不等待协程完成
    val what1 = job1.start()
  • runBlocking
 val what2 = runBlocking {
        delay(1000)
        println("hello runBlocking")
        launch {
            delay(1000)
            println("hello launch")
        }
    }
  • runBlocking + yield

yield 用于挂起当前协程,将当前协程分发到CoroutineDispatcher队列,等其他协程完成/挂起之后,在继续执行先前的协程。

val what3 = runBlocking {
        val job1 = launch {
            println('1')
            yield()
            println('3')
            yield()
            println('5')
        }

        val job2 = launch {
            println('2')
            yield()
            println('4')
            yield()
            println('6')
        }

        println('0')

        // 无论是否调用以下两句,上面两个协程都会执行
        job1.join()
        job2.join()
        // 执行结果为:0 1 2 3 4 5 6
    }
  • withContext + CoroutineScope
val what4 = CoroutineScope(context = Dispatchers.Default).launch {
        val result1 = withContext(this.coroutineContext){
            delay(2000)
            1
        }
        val result2 = withContext(Dispatchers.IO) {
            delay(1000)
            2
        }

        val result3 = result1 + result2
        println("${result3}")
    }

  • withContext + CoroutineScope + NonCancellable
val what5 = CoroutineScope(Dispatchers.Default).launch {
        withContext(NonCancellable){
            delay(2000)
            println("this code can not cancel")
        }
    }
    // 取消也没用,what5会正常执行
    val CanNot = what5.cancel()
  • CoroutineScope + coroutineScope
val what6 = CoroutineScope(Dispatchers.Default).launch {

        val result1 = withContext(this.coroutineContext){
            delay(2000)
            1
        }

        // coroutineScope
        val result2 = coroutineScope {
            delay(2000)
            2
        }

        val result3 = result1 + result2
        println("${result3}")
    }
  • CoroutineScope + jobs
var jobs = ArrayList<Job>()
    var isAdd = false
    val what7 = CoroutineScope(Dispatchers.Default).launch {
        val job1 = this.launch(context = Dispatchers.Unconfined) {
            println("Unconfined : I am is working in thread ${Thread.currentThread()}")
        }

        isAdd = jobs.add(job1)


        val job2 = this.launch (context = this.coroutineContext){
            // 使用父级上下文,也就是runBlocking的上下文
            println("coroutineContext : I am is working in thread ${Thread.currentThread()}")
        }

        isAdd = jobs.add(job2)

        val job3 = this.launch (context = Dispatchers.Default){
            // 使用父级上下文,也就是runBlocking的上下文
            println("'Dispatchers.Default' : I am is working in thread ${Thread.currentThread()}")
        }

        isAdd = jobs.add(job3)

        val job4 = this.launch {
            println("'default' : I am is working in thread ${Thread.currentThread()}")
        }

        isAdd = jobs.add(job4)

        val job5 = this.launch(context = newSingleThreadContext("ThreadForHong")) {
            println("'TreadForHong': I am working in thread ${Thread.currentThread()}")
        }

        isAdd = jobs.add(job5)

        if(isAdd) {
            jobs.forEach {
                it.join()
            }
        }

        // 执行结果如下:(每次执行顺序不同)
        /*
        * Unconfined : I am is working in thread Main
        * 'Dispatchers.Default' : I am is working in thread DefaultDispatcher-worker-1
        * 'TreadForHong': I am working in thread ThreadForHong
        * coroutineContext : I am is working in thread Main
        * 'default' : I am is working in thread Main
        * */
    }
  • 父子协程
val what8 = GlobalScope.launch {

        // 第一个使用不同的上下文
        val job1 = GlobalScope.launch {
            println("job1")
            delay(1000)
            println("job1 context ...")
        }
        // 不用写this.coroutineContext,默认也是 this.coroutineContext
        val job2 = this.launch(context = this.coroutineContext) {
            println("job2")
            delay(1000)
            println("job2 context ...")
        }

        job1.join()
        job2.join()
    }

    val what8in = what8.cancel()

    // 执行结果:
    /*
    * job1
    * job2
    * job1 context ...
    *
    * job2 context ...它没有被打印,原因:父级Job取消执行,what8.cancel()
    * 如果job2 context = Dispatchers.Default,则不会跟随父级Job取消而取消,依然执行
    * */
  • 父子协程完善执行代码:
val what9 = runBlocking {
        val job1 = this.launch(context = this.coroutineContext) {
            println("job1 is run")
            delay(1000)
            println("job1 done")
        }
        val job2 = this.launch(context = this.coroutineContext) {
            println("job2 is run")
            delay(1000)
            println("job2 done")
        }

        val job3 = this.launch(context = this.coroutineContext) {
            println("job3 is run")
            delay(1000)
            println("job3 done")
        }
        val job4 = this.launch(context = this.coroutineContext) {
            println("job4 is run")
            delay(1000)
            println("job4 done")
        }
        job1.join()
        job2.join()
        job3.join()
        job4.join()
    }

    // 执行结果:
    /*
    * job1 is run
    * job2 is run
    * job3 is run
    * job4 is run
    * job1 done
    * job2 done
    * job3 done
    * job4 done
    * */
  • CoroutineContext "+" 操作
val what10 = CoroutineScope(Dispatchers.Unconfined).launch {
        val childJob = CoroutineScope(Dispatchers.Unconfined + coroutineContext).launch {
            println("childJob is run")
            delay(1000)
            println("childJob done")
        }
    }
    val what10in = what10.cancel()

    // 执行结果:childJob is run, 纸打印一句,因为变成了父子协程,父级取消,suspend的一切跟着取消
    // 这里的delay(1000)可以理解成suspend,或者说就是suspend
  • CoroutineContext "+Job" 操作
val what11 = runBlocking {
        val whatJob1 = Job()
        this.launch(context = coroutineContext + whatJob1) {
            delay(500)
            println("job1 dene")
        }
        this.launch(context = coroutineContext + whatJob1) {
            delay(1000)
            println("job2 dene")
        }
        this.launch(context = Dispatchers.Default + whatJob1) {
            delay(1500)
            println("job3 done")
        }
        this.launch(context = Dispatchers.Default + whatJob1) {
            delay(2000)
            println("job4 done")
        }
        this.launch(context = Dispatchers.Default + whatJob1) {
            delay(4000)
            println("job5 done")
        }

        delay(1888)
        whatJob1.cancel()// 取消任务
    }

    // 执行结果
    /*
    * job1 dene
    * job2 dene
    * job3 dene
    * */
  • 安全的使用 CoroutineScope,自定义的都比较安全,尽量少用GlobalScope(作用域越小越安全)

比方说:你家里有个老婆叫红旗,我外面有一群女人叫彩旗,彩旗越少就越安全。但是不刺激啊...

val what12 = SafeCoroutineScope(Dispatchers.Unconfined).launch {
        val whatJob1 = Job()
        this.launch(context = coroutineContext + whatJob1) {
            delay(500)
            println("job1 dene")
        }
        this.launch(context = coroutineContext + whatJob1) {
            delay(1000)
            println("job2 dene")
        }
        delay(800)
        whatJob1.cancel()
    }
  • viewModelScope

viewModelScope 会在对象销毁时,自动销毁,也可手动提前销毁优化内存

internal class MyViewModel : ViewModel() {

        val whatScope = viewModelScope

        fun fetchData() {
            // 在 viewModelScope 中启动一个新的协程
            whatScope.launch {
                // 这里执行异步操作,例如网络请求或数据库查询
                val data = performNetworkOperation() // 假设这是一个挂起函数
                // 使用数据
                processData(data)
            }
        }

        private suspend fun performNetworkOperation(): String {
            // 这里执行网络请求或其他耗时操作
            return "new value"
        }

        private fun processData(data: String) {
            // 处理数据
            whatScope.cancel()
        }
    }
  • Channel 协程之间的数据通讯 Channel

Channel相当于之前博客《kotlin多线程》篇章中的BlockingQueue

val what13 = runBlocking {
        val channel = Channel<Int>() // 相当于BlockingQueue
        this.launch(Dispatchers.Default) {
            repeat(5){
                delay(200)
                channel.send(it)
                if (it == 4) channel.close()
            }
        }

        this.launch(Dispatchers.Default) {
            repeat(5){
                try {
                    val element = channel.receive()
                    println(element)
                }
                catch (e:ClosedReceiveChannelException) {
                    // 必须捕获这个异常否则程序会崩
                    println("error: ${e.message}")
                }
            }
        }

    }
  • Channel 管道
// Channel 管道
    fun produce1() = SafeCoroutineScope(Dispatchers.Default).produce<Int> {
        repeat(5){
            send(it) // 发送0~4
        }
    }

    fun produce2(numbers:ReceiveChannel<Int>) = SafeCoroutineScope(Dispatchers.Default).produce<Int> {
        for (x in numbers) {
            send(x * x)
        }
    }

    fun produce3(numbers:ReceiveChannel<Int>) = SafeCoroutineScope(Dispatchers.Default).produce<Int> {
        for (x in numbers) {
            send(x+1)
        }
    }

    fun HongMain() = runBlocking<Unit> {
        val numbers1 = produce1()
        val numbers2 = produce2(numbers1)
        val numbers3 = produce3(numbers2)
        numbers3.consumeEach {
            println("consume: $it")
        }
        println("receive done")
        // 接收完成之后,关闭所有produce
        numbers3.cancel()
        numbers2.cancel()
        numbers1.cancel()
    }
  • channel 缓冲
fun HongCache() = runBlocking {
        val cacheSize = 3
        val channel = Channel<Int>(cacheSize)// 创建带有缓冲区的channel
        this.launch(this.coroutineContext) {
            repeat(6){
                delay(50)
                println("send $it")
                channel.send(it)
            }
        }

        this.launch {
            delay(1000)
            repeat(6){
                println("receive: ${channel.receive()}")
            }
        }
    }

    /*
    * 第一个协程先发送两个消息,需要等到第二个协程时间到了,第二个协程才开始消费信息
    * 等到缓冲区的信息消费完毕后,第一个协程继续发送信息,第二个协程继续消费信息,
    * 以此类推直到结束。
    * */
  • actor

actor 本身就是一个协程,内部包含一个channel,通过channel与其他协程通讯,这个内部channel只做接收信息

fun HongActor() = runBlocking {
        val summer = actor<Int>(context = this.coroutineContext) {
            var sm = 0
            for (x in channel) {
                sm += x
                println("sim : $sm")
            }
        }
        repeat(10){
            summer.send(it)
        }
    }
  • select
fun produceSelect1(context:CoroutineContext) = SafeCoroutineScope(context).produce<String> {
        while (true) {
            delay(400)
            send("hong")
        }
    }

    fun produceSelect2(context:CoroutineContext) = SafeCoroutineScope(context).produce<String> {
        while (true) {
            delay(600)
            send("lina")
        }
    }

    suspend fun selectProduces(channel1:ReceiveChannel<String>,channel2:ReceiveChannel<String>) {
        select<Unit> {
            channel1.onReceive {
                println(it)
            }
            channel2.onReceive {
                println(it)
            }
        }
    }

    fun HongSelectMain() = runBlocking {
        val hong = produceSelect1(this.coroutineContext)
        val lina = produceSelect2(this.coroutineContext)
        repeat(10) {
            selectProduces(hong,lina)
        }
        this.coroutineContext.cancelChildren()
        // ok
    }
  • lifecycleScope + Activity
class MiLifecycleCoroutineScope:AppCompatActivity(){

    override fun onStart() {
        super.onStart()
    }

    fun loadData(){
       lifecycleScope.launch {
           val data = fetchDataFromNetwork()
           println(data)
       }
    }

    suspend fun fetchDataFromNetwork():Any {
        delay(2000)
        return "data"
    }
}
// Activity销毁,lifecycleScope自动销毁,和viewmodelScope类似,属于绑定关系的CoroutineScope
  • rememberCoroutineScope + @Composable
@Composable
fun MyComposable() {
    val coroutineScope = rememberCoroutineScope()
 
    // 在这里,你可以使用 coroutineScope.launch 来启动一个新的协程
    // 这个协程会在组件销毁时自动取消,并且会在每次组件重组时保持相同的实例
    coroutineScope.launch {
        // 在这里执行异步操作
    }
}
  • 自定义的 SafeCoroutineScope
class SafeCoroutineScope(context:CoroutineContext):CoroutineScope,Closeable {

    val handler = CoroutineExceptionHandler { context, exception ->
        println("异常捕获并处理: $exception")
    }

    override val coroutineContext: CoroutineContext = SupervisorJob() + context + handler

    override fun close() {
        coroutineContext.cancelChildren()
    }
}

以上实乃androidx和kotlinx的内功心法,持有它Android在你面前就像褪去衣服的美女,发挥你的原始兽性去攻略它吧.

thank..

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容