自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Android Kotlin 協(xié)程初探

移動(dòng)開發(fā)
Kotlin協(xié)程之所以被認(rèn)為是假協(xié)程,是因?yàn)樗⒉辉谕粋€(gè)線程運(yùn)行,而是真的會(huì)創(chuàng)建多個(gè)線程。

一、 它是什么(協(xié)程 和 Kotlin協(xié)程)

1.1 協(xié)程是什么

維基百科:協(xié)程,英文Coroutine \[k?ru’tin\] (可入廳),是計(jì)算機(jī)程序的一類組件,推廣了協(xié)作式多任務(wù)的子程序,允許執(zhí)行被掛起與被恢復(fù)。

作為Google欽定的Android開發(fā)首選語言Kotlin,協(xié)程并不是 Kotlin 提出來的新概念,目前有協(xié)程概念的編程語言有Lua語言、Python語言、Go語言、C語言等,它只是一種編程思想,不局限于特定的語言。

而每一種編程語言中的協(xié)程的概念及實(shí)現(xiàn)又不完全一樣,本次分享主要講Kotlin協(xié)程。

1.2 Kotlin協(xié)程是什么

Kotlin官網(wǎng):協(xié)程是輕量級(jí)線程

可簡(jiǎn)單理解:一個(gè)線程框架,是全新的處理并發(fā)的方式,也是Android上方便簡(jiǎn)化異步執(zhí)行代碼的方式

類似于 Java:線程池 Android:Handler和AsyncTask,RxJava的Schedulers

注:Kotlin不僅僅是面向JVM平臺(tái)的,還有JS/Native,如果用kotlin來寫前端,那Koltin的協(xié)程就是JS意義上的協(xié)程。如果僅僅JVM 平臺(tái),那確實(shí)應(yīng)該是線程框架。

1.3 進(jìn)程、線程、協(xié)程比較

可通過以下兩張圖理解三者的不同和關(guān)系

Android Kotlin 協(xié)程初探 | 京東物流技術(shù)團(tuán)隊(duì)_UserAndroid Kotlin 協(xié)程初探 | 京東物流技術(shù)團(tuán)隊(duì)_User

Android Kotlin 協(xié)程初探 | 京東物流技術(shù)團(tuán)隊(duì)_User_02Android Kotlin 協(xié)程初探 | 京東物流技術(shù)團(tuán)隊(duì)_User_02

二、 為什么選擇它(協(xié)程解決什么問題)

異步場(chǎng)景舉例:

  1. 第一步:接口獲取當(dāng)前用戶token及用戶信息
  2. 第二步:將用戶的昵稱展示界面上
  3. 第三步:然后再通過這個(gè)token獲取當(dāng)前用戶的消息未讀數(shù)
  4. 第四步:并展示在界面上

2.1 現(xiàn)有方案實(shí)現(xiàn)

apiService.getUserInfo().enqueue(object :Callback<User>{
    override fun onResponse(call: Call<User>, response: Response<User>) {
        val user = response.body()
        tvNickName.text = user?.nickName
        apiService.getUnReadMsgCount(user?.token).enqueue(object :Callback<Int>{
            override fun onResponse(call: Call<Int>, response: Response<Int>) {
                val tvUnReadMsgCount = response.body()
                tvMsgCount.text = tvUnReadMsgCount.toString()
            }
        })
    }
})

現(xiàn)有方案如何拿到異步任務(wù)的數(shù)據(jù),得不到就毀掉哈哈哈,就是通過回調(diào)函數(shù)來解決。
若嵌套多了,這種畫風(fēng)是不是有點(diǎn)回調(diào)地獄的感覺,俗稱的「callback hell」

2.2 協(xié)程實(shí)現(xiàn)

mainScope.launch {
    val user = apiService.getUserInfoSuspend() //IO線程請(qǐng)求數(shù)據(jù)
    tvNickName.text = user?.nickName //UI線程更新界面
    val unReadMsgCount = apiService.getUnReadMsgCountSuspend(user?.token) //IO線程請(qǐng)求數(shù)據(jù)
    tvMsgCount.text = unReadMsgCount.toString() //UI線程更新界面
}
suspend fun getUserInfoSuspend() :User? {
    return withContext(Dispatchers.IO){
        //模擬網(wǎng)絡(luò)請(qǐng)求耗時(shí)操作
        delay(10)
        User("asd123", "userName", "nickName")
    }
}
suspend fun getUnReadMsgCountSuspend(token:String?) :Int{
    return withContext(Dispatchers.IO){
        //模擬網(wǎng)絡(luò)請(qǐng)求耗時(shí)操作
        delay(10)
        10
    }
}

Android Kotlin 協(xié)程初探 | 京東物流技術(shù)團(tuán)隊(duì)_Kotlin_03Android Kotlin 協(xié)程初探 | 京東物流技術(shù)團(tuán)隊(duì)_Kotlin_03

紅色框框內(nèi)的就是一個(gè)協(xié)程代碼塊。

可以看得出在協(xié)程實(shí)現(xiàn)中告別了callback,所以再也不會(huì)出現(xiàn)回調(diào)地獄這種情況了,協(xié)程解決了回調(diào)地獄

Android Kotlin 協(xié)程初探 | 京東物流技術(shù)團(tuán)隊(duì)_Kotlin_04Android Kotlin 協(xié)程初探 | 京東物流技術(shù)團(tuán)隊(duì)_Kotlin_04

協(xié)程可以讓我們用同步的代碼寫出異步的效果,這也是協(xié)程最大的優(yōu)勢(shì),異步代碼同步去寫。

小結(jié):協(xié)程可以異步代碼同步去寫,解決回調(diào)地獄,讓程序員更方便地處理異步業(yè)務(wù),更方便地切線程,保證主線程安全。

它是怎么做到的?

三、 它是怎么工作的(協(xié)程的原理淺析)

3.1 協(xié)程的掛起和恢復(fù)

掛起(非阻塞式掛起)

suspend 關(guān)鍵字,它是協(xié)程中核心的關(guān)鍵字,是掛起的標(biāo)識(shí)。

下面看一下上述示例代碼切換線程的過程:

Android Kotlin 協(xié)程初探 | 京東物流技術(shù)團(tuán)隊(duì)_狀態(tài)機(jī)_05Android Kotlin 協(xié)程初探 | 京東物流技術(shù)團(tuán)隊(duì)_狀態(tài)機(jī)_05

每一次從主線程切到IO線程都是一次協(xié)程的掛起操作;

每一次從IO線程切換主線程都是一次協(xié)程的恢復(fù)操作;

掛起和恢復(fù)是suspend函數(shù)特有的能力,其他函數(shù)不具備,掛起的內(nèi)容是協(xié)程,不是掛起線程,也不是掛起函數(shù),當(dāng)線程執(zhí)行到suspend函數(shù)的地方,不會(huì)繼續(xù)執(zhí)行當(dāng)前協(xié)程的代碼了,所以它不會(huì)阻塞線程,是非阻塞式掛起。

有掛起必然有恢復(fù)流程, 恢復(fù)是指將已經(jīng)被掛起的目標(biāo)協(xié)程從掛起之處開始恢復(fù)執(zhí)行。在協(xié)程中,掛起和恢復(fù)都不需要我們手動(dòng)處理,這些都是kotlin協(xié)程幫我們自動(dòng)完成的。

那Kotlin協(xié)程是如何幫我們自動(dòng)實(shí)現(xiàn)掛起和恢復(fù)操作的呢?

它是通過Continuation來實(shí)現(xiàn)的。 \[k?n?t?nju?e??(?)n\] (繼續(xù);延續(xù);連續(xù)性;后續(xù)部分)

3.2 協(xié)程的掛起和恢復(fù)的工作原理(Continuation)

CPS + 狀態(tài)機(jī)

Java中沒有suspend函數(shù),suspend是Kotlin中特有的關(guān)鍵字,當(dāng)編譯時(shí),Kotlin編譯器會(huì)將含有suspend關(guān)鍵字的函數(shù)進(jìn)行一次轉(zhuǎn)換。

這種被編譯器轉(zhuǎn)換在kotlin中叫CPS轉(zhuǎn)換(cotinuation-passing-style)。

轉(zhuǎn)換流程如下所示

程序員寫的掛起函數(shù)代碼:

suspend fun getUserInfo() : User {
    val user = User("asd123", "userName", "nickName")
    return user
}

假想的一種中間態(tài)代碼(便于理解):

fun getUserInfo(callback: Callback<User>): Any? {
    val user = User("asd123", "userName", "nickName")
    callback.onSuccess(user)
    return Unit
}

轉(zhuǎn)換后的代碼:

fun getUserInfo(cont: Continuation<User>): Any? {
    val user = User("asd123", "userName", "nickName")
    cont.resume(user)
    return Unit
}

我們通過Kotlin生成字節(jié)碼工具查看字節(jié)碼,然后將其反編譯成Java代碼:

@Nullable
public final Object getUserInfo(@NotNull Continuation $completion) {
   User user = new User("asd123", "userName", "nickName");
   return user;
}

這也驗(yàn)證了確實(shí)是會(huì)通過引入一個(gè)Continuation對(duì)象來實(shí)現(xiàn)恢復(fù)的流程,這里的這個(gè)Continuation對(duì)象中包含了Callback的形態(tài)。

它有兩個(gè)作用:1\. 暫停并記住執(zhí)行點(diǎn)位;2. 記住函數(shù)暫停時(shí)刻的局部變量上下文。

所以為什么我們可以用同步的方式寫異步代碼,是因?yàn)镃ontinuation幫我們做了回調(diào)的流程。

下面看一下這個(gè)Continuation 的源碼部分

Android Kotlin 協(xié)程初探 | 京東物流技術(shù)團(tuán)隊(duì)_User_06Android Kotlin 協(xié)程初探 | 京東物流技術(shù)團(tuán)隊(duì)_User_06

可以看到這個(gè)Continuation中封裝了一個(gè)resumeWith的方法,這個(gè)方法就是恢復(fù)用的。

internal abstract class BaseContinuationImpl() : Continuation<Any?> {
    public final override fun resumeWith(result: Result<Any?>) {
        //省略好多代碼
        invokeSuspend()
        //省略好多代碼
    }
    protected abstract fun invokeSuspend(result: Result<Any?>): Any?
}
internal abstract class ContinuationImpl(
    completion: Continuation<Any?>?,
    private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
protected abstract fun invokeSuspend(result: Result<Any?>): Any?

//invokeSuspend() 這個(gè)方法是恢復(fù)的關(guān)鍵一步

繼續(xù)看上述例子:

這是一個(gè)CPS之前的代碼:

suspend fun testCoroutine() {
    val user = apiService.getUserInfoSuspend() //掛起函數(shù)  IO線程
    tvNickName.text = user?.nickName //UI線程更新界面
    val unReadMsgCount = apiService.getUnReadMsgCountSuspend(user?.token) //掛起函數(shù)  IO線程
    tvMsgCount.text = unReadMsgCount.toString() //UI線程更新界面
}

當(dāng)前掛起函數(shù)里有兩個(gè)掛起函數(shù)

通過kotlin編譯器編譯后:

fun testCoroutine(completion: Continuation<Any?>): Any? {
    // TestContinuation本質(zhì)上是匿名內(nèi)部類
    class TestContinuation(completion: Continuation<Any?>?) : ContinuationImpl(completion) {
        // 表示協(xié)程狀態(tài)機(jī)當(dāng)前的狀態(tài)
        var label: Int = 0
        // 兩個(gè)變量,對(duì)應(yīng)原函數(shù)的2個(gè)變量
        lateinit var user: Any
        lateinit var unReadMsgCount: Int
        // result 接收協(xié)程的運(yùn)行結(jié)果
        var result = continuation.result
        // suspendReturn 接收掛起函數(shù)的返回值
        var suspendReturn: Any? = null
        // CoroutineSingletons 是個(gè)枚舉類
        // COROUTINE_SUSPENDED 代表當(dāng)前函數(shù)被掛起了
        val sFlag = CoroutineSingletons.COROUTINE_SUSPENDED
        // invokeSuspend 是協(xié)程的關(guān)鍵
        // 它最終會(huì)調(diào)用 testCoroutine(this) 開啟協(xié)程狀態(tài)機(jī)
        // 狀態(tài)機(jī)相關(guān)代碼就是后面的 when 語句
        // 協(xié)程的本質(zhì),可以說就是 CPS + 狀態(tài)機(jī)
        override fun invokeSuspend(_result: Result<Any?>): Any? {
            result = _result
            label = label or Int.Companion.MIN_VALUE
            return testCoroutine(this)
        }
    }
    // ...
    val continuation = if (completion is TestContinuation) {
        completion
    } else {
        //                作為參數(shù)
        //                   ↓
        TestContinuation(completion)
loop = true
while(loop) {
when (continuation.label) {
    0 -> {
        // 檢測(cè)異常
        throwOnFailure(result)
        // 將 label 置為 1,準(zhǔn)備進(jìn)入下一次狀態(tài)
        continuation.label = 1
        // 執(zhí)行 getUserInfoSuspend(第一個(gè)掛起函數(shù))
        suspendReturn = getUserInfoSuspend(continuation)
        // 判斷是否掛起
        if (suspendReturn == sFlag) {
            return suspendReturn
        } else {
            result = suspendReturn
            //go to next state
        }
    }
    1 -> {
        throwOnFailure(result)
        // 獲取 user 值
        user = result as Any
        // 準(zhǔn)備進(jìn)入下一個(gè)狀態(tài)
        continuation.label = 2
        // 執(zhí)行 getUnReadMsgCountSuspend
        suspendReturn = getUnReadMsgCountSuspend(user.token, continuation)


        // 判斷是否掛起
        if (suspendReturn == sFlag) {
            return suspendReturn
        } else {
            result = suspendReturn
            //go to next state
        }
    }
    2 -> {
        throwOnFailure(result)
        user = continuation.mUser as Any
        unReadMsgCount = continuation.unReadMsgCount as Int
        loop = false
}
}

通過一個(gè)label標(biāo)簽控制分支代碼執(zhí)行,label為0,首先會(huì)進(jìn)入第一個(gè)分支,首先將label設(shè)置為下一個(gè)分支的數(shù)值,然后執(zhí)行第一個(gè)suspend方法并傳遞當(dāng)前Continuation,得到返回值,如果是COROUTINE SUSPENDED,協(xié)程框架就直接return,協(xié)程掛起,當(dāng)?shù)谝粋€(gè)suspend方法執(zhí)行完成,會(huì)回調(diào)Continuation的invokeSuspend方法,進(jìn)入第二個(gè)分支執(zhí)行,以此類推執(zhí)行完所有suspend方法。

每一個(gè)掛起點(diǎn)和初始掛起點(diǎn)對(duì)應(yīng)的 Continuation 都會(huì)轉(zhuǎn)化為一種狀態(tài),協(xié)程恢復(fù)只是跳轉(zhuǎn)到下一種狀態(tài)中。掛起函數(shù)將執(zhí)行過程分為多個(gè) Continuation 片段,并且利用狀態(tài)機(jī)的方式保證各個(gè)片段是順序執(zhí)行的。

小結(jié):協(xié)程的掛起和恢復(fù)的本質(zhì)是CPS + 狀態(tài)機(jī)

四、 總結(jié)

總結(jié)幾個(gè)不用協(xié)程實(shí)現(xiàn)起來很麻煩的騷操作:

  1. 如果有一個(gè)函數(shù),它的返回值需要等到多個(gè)耗時(shí)的異步任務(wù)都執(zhí)行完畢返回之后,組合所有任務(wù)的返回值作為 最終返回值
  2. 如果有一個(gè)函數(shù),需要順序執(zhí)行多個(gè)網(wǎng)絡(luò)請(qǐng)求,并且后一個(gè)請(qǐng)求依賴前一個(gè)請(qǐng)求的執(zhí)行結(jié)果
  3. 當(dāng)前正在執(zhí)行一項(xiàng)異步任務(wù),但是你突然不想要它執(zhí)行了,隨時(shí)可以取消
  4. 如果你想讓一個(gè)任務(wù)最多執(zhí)行3秒,超過3秒則自動(dòng)取消

Kotlin協(xié)程之所以被認(rèn)為是假協(xié)程,是因?yàn)樗⒉辉谕粋€(gè)線程運(yùn)行,而是真的會(huì)創(chuàng)建多個(gè)線程。

Kotlin協(xié)程在Android上只是一個(gè)類似線程池的封裝,真就是一個(gè)線程框架。但是它卻可以讓我們用同步的代碼風(fēng)格寫出異步的效果,至于怎么做的,這個(gè)不需要我們操心,這些都是kotlin幫我們處理好了,我們需要關(guān)心的是怎么用好它

它就是一個(gè)線程框架。

責(zé)任編輯:龐桂玉 來源: 51CTO博客
相關(guān)推薦

2021-05-20 09:14:09

Kotlin協(xié)程掛起和恢復(fù)

2019-10-23 14:34:15

KotlinAndroid協(xié)程

2023-09-03 19:13:29

AndroidKotlin

2020-06-19 08:01:48

Kotlin 協(xié)程編程

2021-09-16 09:59:13

PythonJavaScript代碼

2020-02-19 14:16:23

kotlin協(xié)程代碼

2021-04-28 09:08:23

Kotlin協(xié)程代碼

2023-11-17 11:36:59

協(xié)程纖程操作系統(tǒng)

2021-08-04 16:19:55

AndroidKotin協(xié)程Coroutines

2025-02-08 09:13:40

2021-12-09 06:41:56

Python協(xié)程多并發(fā)

2020-07-07 09:19:28

Android 協(xié)程開發(fā)

2020-04-08 09:06:34

Android 協(xié)程開發(fā)

2020-12-04 14:32:33

AndroidJetpackKotlin

2020-04-23 09:33:32

Android 協(xié)程開發(fā)

2022-09-06 20:30:48

協(xié)程Context主線程

2021-04-25 09:36:20

Go協(xié)程線程

2024-06-27 07:56:49

2016-10-28 17:39:47

phpgolangcoroutine

2017-05-02 11:38:00

PHP協(xié)程實(shí)現(xiàn)過程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)