AndroidX Coroutine

overview

Kotlin 協(xié)程的基本概念:協(xié)程是靠編譯器實(shí)現(xiàn)的,并不需要操作系統(tǒng)和硬件的支持。

  • CoroutineContext 協(xié)程上下文,它包含一個(gè)默認(rèn)的協(xié)程調(diào)度器,所有協(xié)程都必須在CoroutineContext中執(zhí)行。

  • CoroutineScope 協(xié)程作用域,它一個(gè)接口只包含一個(gè)屬性CoroutineContext,它定義了一個(gè)有生命周期的實(shí)體上的實(shí)現(xiàn)。

  • GlobalScope 一個(gè)全局的作用域,用于啟動(dòng)頂級(jí)的協(xié)程,這些協(xié)程會(huì)在整個(gè)應(yīng)用程序的生命周期內(nèi)運(yùn)行。

  • CoroutineDispatcher 協(xié)程調(diào)度器,它用來調(diào)度和處理任務(wù),決定了協(xié)程應(yīng)該在哪個(gè)或哪些線程中執(zhí)行。

  • lifecycleScope:生命周期范圍,用于activity等有生命周期的組件,在DESTROYED的時(shí)候會(huì)自動(dòng)結(jié)束。

  • viewModelScope:viewModel范圍,用于ViewModel中,在ViewModel被回收時(shí)會(huì)自動(dòng)結(jié)束。

CoroutineScope的作用域類型:

  • 頂級(jí)作用域:沒有父協(xié)程的協(xié)程所在的作用域?yàn)轫敿?jí)作用域。

  • 協(xié)同作用域:協(xié)程中啟動(dòng)新的協(xié)程,新協(xié)程為所在協(xié)程的子協(xié)程,這種情況下子協(xié)程拋出的未捕獲異常將傳遞給父協(xié)程處理,父協(xié)程同時(shí)也會(huì)被取消。

  • 主從作用域:與協(xié)同作用域類似,但子協(xié)程的異常不會(huì)向上傳遞給父協(xié)程。

CoroutineScope的聲明方式:(它們都遵循CoroutineScope interface,所以你可以自定義一個(gè)協(xié)程)

  • GlobalScope.launch:聲明頂級(jí)作用域。

  • GlobalScope.async:聲明頂級(jí)作用域。

  • runBlocking:聲明頂級(jí)作用域。

  • coroutineScope:聲明協(xié)同作用域。

  • supervisorScope:聲明主從作用域,異常不會(huì)向上傳遞。

Kotlin中包含四種協(xié)程調(diào)度器,它們的作用分別是:

  • Dispatchers.Default:這是默認(rèn)調(diào)度器,適合處理CPU密集型任務(wù),如大量計(jì)算。它是一個(gè)CPU密集型任務(wù)調(diào)度器,通常用于后臺(tái)計(jì)算。

  • Dispatchers.IO:這是IO調(diào)度器,適合執(zhí)行IO相關(guān)操作,如文件讀寫、網(wǎng)絡(luò)請(qǐng)求等。它是一個(gè)IO密集型任務(wù)調(diào)度器,通常用于處理文件操作和網(wǎng)絡(luò)IO操作。

  • Dispatchers.Main:這是UI調(diào)度器,通常在主線程上執(zhí)行任務(wù),如更新UI。在Android平臺(tái)上,這通常用于處理UI相關(guān)的操作。

  • Dispatchers.Unconfined:這是非受限制調(diào)度器,不會(huì)要求協(xié)程在哪個(gè)線程上執(zhí)行。它會(huì)在啟動(dòng)它的線程上執(zhí)行,直到第一個(gè)掛起點(diǎn),然后恢復(fù)執(zhí)行。

協(xié)程調(diào)度器的使用場(chǎng)景和特點(diǎn):

  • Dispatchers.Default:適用于需要大量計(jì)算的任務(wù),如數(shù)據(jù)排序、解析等。
  • Dispatchers.IO:適用于文件讀寫、數(shù)據(jù)庫(kù)操作和網(wǎng)絡(luò)請(qǐng)求等IO密集型任務(wù)。
  • Dispatchers.Main:適用于需要在主線程上執(zhí)行的UI更新操作

... 開始代碼示例:將會(huì)涉及到幾乎所有的協(xié)程

  • 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() 添加之后執(zhí)行這里
                println("exception: ${it.message}")
            }
            else {
                println("result is complete")
            }
        }
        result1.cancelAndJoin()

    }
    // 默認(rèn)是 CoroutineStart.DEFAULT,不需要調(diào)用job1.start()
    // CoroutineStart.LAZY時(shí)需要手動(dòng)調(diào)用job1.start(),相當(dāng)于懶加載
    // 使用 start 方法來啟動(dòng)協(xié)程,不等待協(xié)程完成
    val what1 = job1.start()
  • runBlocking
 val what2 = runBlocking {
        delay(1000)
        println("hello runBlocking")
        launch {
            delay(1000)
            println("hello launch")
        }
    }
  • runBlocking + yield

yield 用于掛起當(dāng)前協(xié)程,將當(dāng)前協(xié)程分發(fā)到CoroutineDispatcher隊(duì)列,等其他協(xié)程完成/掛起之后,在繼續(xù)執(zhí)行先前的協(xié)程。

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')

        // 無論是否調(diào)用以下兩句,上面兩個(gè)協(xié)程都會(huì)執(zhí)行
        job1.join()
        job2.join()
        // 執(zhí)行結(jié)果為: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會(huì)正常執(zhí)行
    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){
            // 使用父級(jí)上下文,也就是runBlocking的上下文
            println("coroutineContext : I am is working in thread ${Thread.currentThread()}")
        }

        isAdd = jobs.add(job2)

        val job3 = this.launch (context = Dispatchers.Default){
            // 使用父級(jí)上下文,也就是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()
            }
        }

        // 執(zhí)行結(jié)果如下:(每次執(zhí)行順序不同)
        /*
        * 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
        * */
    }
  • 父子協(xié)程
val what8 = GlobalScope.launch {

        // 第一個(gè)使用不同的上下文
        val job1 = GlobalScope.launch {
            println("job1")
            delay(1000)
            println("job1 context ...")
        }
        // 不用寫this.coroutineContext,默認(rèn)也是 this.coroutineContext
        val job2 = this.launch(context = this.coroutineContext) {
            println("job2")
            delay(1000)
            println("job2 context ...")
        }

        job1.join()
        job2.join()
    }

    val what8in = what8.cancel()

    // 執(zhí)行結(jié)果:
    /*
    * job1
    * job2
    * job1 context ...
    *
    * job2 context ...它沒有被打印,原因:父級(jí)Job取消執(zhí)行,what8.cancel()
    * 如果job2 context = Dispatchers.Default,則不會(huì)跟隨父級(jí)Job取消而取消,依然執(zhí)行
    * */
  • 父子協(xié)程完善執(zhí)行代碼:
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()
    }

    // 執(zhí)行結(jié)果:
    /*
    * 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()

    // 執(zhí)行結(jié)果:childJob is run, 紙打印一句,因?yàn)樽兂闪烁缸訁f(xié)程,父級(jí)取消,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()// 取消任務(wù)
    }

    // 執(zhí)行結(jié)果
    /*
    * job1 dene
    * job2 dene
    * job3 dene
    * */
  • 安全的使用 CoroutineScope,自定義的都比較安全,盡量少用GlobalScope(作用域越小越安全)

比方說:你家里有個(gè)老婆叫紅旗,我外面有一群女人叫彩旗,彩旗越少就越安全。但是不刺激啊...

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 會(huì)在對(duì)象銷毀時(shí),自動(dòng)銷毀,也可手動(dòng)提前銷毀優(yōu)化內(nèi)存

internal class MyViewModel : ViewModel() {

        val whatScope = viewModelScope

        fun fetchData() {
            // 在 viewModelScope 中啟動(dòng)一個(gè)新的協(xié)程
            whatScope.launch {
                // 這里執(zhí)行異步操作,例如網(wǎng)絡(luò)請(qǐng)求或數(shù)據(jù)庫(kù)查詢
                val data = performNetworkOperation() // 假設(shè)這是一個(gè)掛起函數(shù)
                // 使用數(shù)據(jù)
                processData(data)
            }
        }

        private suspend fun performNetworkOperation(): String {
            // 這里執(zhí)行網(wǎng)絡(luò)請(qǐng)求或其他耗時(shí)操作
            return "new value"
        }

        private fun processData(data: String) {
            // 處理數(shù)據(jù)
            whatScope.cancel()
        }
    }
  • Channel 協(xié)程之間的數(shù)據(jù)通訊 Channel

Channel相當(dāng)于之前博客《kotlin多線程》篇章中的BlockingQueue

val what13 = runBlocking {
        val channel = Channel<Int>() // 相當(dāng)于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) {
                    // 必須捕獲這個(gè)異常否則程序會(huì)崩
                    println("error: ${e.message}")
                }
            }
        }

    }
  • Channel 管道
// Channel 管道
    fun produce1() = SafeCoroutineScope(Dispatchers.Default).produce<Int> {
        repeat(5){
            send(it) // 發(fā)送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")
        // 接收完成之后,關(guān)閉所有produce
        numbers3.cancel()
        numbers2.cancel()
        numbers1.cancel()
    }
  • channel 緩沖
fun HongCache() = runBlocking {
        val cacheSize = 3
        val channel = Channel<Int>(cacheSize)// 創(chuàng)建帶有緩沖區(qū)的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()}")
            }
        }
    }

    /*
    * 第一個(gè)協(xié)程先發(fā)送兩個(gè)消息,需要等到第二個(gè)協(xié)程時(shí)間到了,第二個(gè)協(xié)程才開始消費(fèi)信息
    * 等到緩沖區(qū)的信息消費(fèi)完畢后,第一個(gè)協(xié)程繼續(xù)發(fā)送信息,第二個(gè)協(xié)程繼續(xù)消費(fèi)信息,
    * 以此類推直到結(jié)束。
    * */
  • actor

actor 本身就是一個(gè)協(xié)程,內(nèi)部包含一個(gè)channel,通過channel與其他協(xié)程通訊,這個(gè)內(nèi)部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自動(dòng)銷毀,和viewmodelScope類似,屬于綁定關(guān)系的CoroutineScope
  • rememberCoroutineScope + @Composable
@Composable
fun MyComposable() {
    val coroutineScope = rememberCoroutineScope()
 
    // 在這里,你可以使用 coroutineScope.launch 來啟動(dòng)一個(gè)新的協(xié)程
    // 這個(gè)協(xié)程會(huì)在組件銷毀時(shí)自動(dòng)取消,并且會(huì)在每次組件重組時(shí)保持相同的實(shí)例
    coroutineScope.launch {
        // 在這里執(zhí)行異步操作
    }
}
  • 自定義的 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()
    }
}

以上實(shí)乃androidx和kotlinx的內(nèi)功心法,持有它Android在你面前就像褪去衣服的美女,發(fā)揮你的原始獸性去攻略它吧.

thank..

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,708評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,409評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,774評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評(píng)論 1 286
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,650評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容