Android Kotlin Coroutine原理簡(jiǎn)述

Kotlin

Kotlin已經(jīng)被谷歌指定為Android的第一開發(fā)語(yǔ)言辽剧,現(xiàn)在大多數(shù)團(tuán)隊(duì)都在改用kotlin進(jìn)行開發(fā)送淆。而kotlin的版本發(fā)布也挺快,目前出了一些新的東西可以進(jìn)行嘗試怕轿。

Coroutine

2018年10月的樣子偷崩,Kotlin1.3正式發(fā)布辟拷,其中有一項(xiàng)特性是Android開發(fā)中以前從未有過(guò)的,那就是Coroutine阐斜,而且是正式版衫冻。
其實(shí)Coroutine的概念在1963年就由梅爾文*康威(一個(gè)牛逼的計(jì)算機(jī)科學(xué)家)提出,但是直到近代才逐漸走進(jìn)大多數(shù)開發(fā)者的視野谒出。比如:Python隅俘、Lua、C#笤喳、Go等等語(yǔ)言已經(jīng)支持Coroutine了为居。其中Go更是憑借著Coroutine,成為了目前比較火熱的服務(wù)端開發(fā)語(yǔ)言莉测,在處理高并發(fā)上有著天然的優(yōu)勢(shì)颜骤。
下面就進(jìn)入正題,Coroutine實(shí)現(xiàn)原理及給我們帶來(lái)了什么捣卤。

同步編程

先看看同步編程的代碼忍抽,其中setToken與setUseInfo為主線程設(shè)置TextView:

/**
 * @Author : xialonghua
 * @Date : Create in 2019/1/28
 * @Description : 阻塞式編程
 */
class Demo1(button: Button, infoView: TextView) : Demo(button, infoView) {

    override fun onCreate() {
        //模擬獲取token
        val token = URL("https://www.baidu.com/getToken").readText().md5()
        setToken(token)
        val userInfo = URL("https://www.baidu.com/userInfo?$token").readText().md5()
        setUserInfo(userInfo)
    }
}

異步編程

異步編程的目的是解決:如何防止應(yīng)用因?yàn)閳?zhí)行代碼而陷入阻塞?
Coroutine就是眾多解決方案中的一種董朝。那么除了Coroutine有哪些常用的方法呢鸠项?

  1. Thread
  2. Callback
  3. Future/Promise/Rx

下面我們就先來(lái)介紹一下這幾種方法。

Thread

這是最原始的解決方案子姜,Thread就是為此而生祟绊,他是操作系統(tǒng)級(jí)別的解決方案。如果不想陷入阻塞哥捕,直接起一個(gè)Thread即可牧抽。

/**
 * @Author : xialonghua
 * @Date : Create in 2019/1/28
 * @Description : thread
 */
class Demo2(button: Button, infoView: TextView) : Demo(button, infoView) {

    override fun onCreate(){
        //模擬獲取token
        thread {
            val token = URL("https://www.baidu.com/getToken").readText().md5()
            uiHandler.post {
                setToken(token)
            }
            val userInfo = URL("https://www.baidu.com/userInfo?$token").readText().md5()
            uiHandler.post {
                setUserInfo(userInfo)
            }
        }
    }
}

因?yàn)楝F(xiàn)代應(yīng)用程序都是有一個(gè)主線程,而UI操作必須在主線程中去做遥赚。那么使用thread方式時(shí)扬舒,需要顯示的將結(jié)果拋到主線程中去處理,這其實(shí)是增加了線程切換的復(fù)雜度凫佛。

Callback

再后來(lái)讲坎,發(fā)現(xiàn)基于thread其實(shí)寫的代碼還是比較多的,挺復(fù)雜的愧薛。所以有人又想出了另外一種方法將結(jié)果通過(guò)Callback的方式返回給UI線程晨炕。

/**
 * @Author : xialonghua
 * @Date : Create in 2019/1/28
 * @Description : callback
 */
class Demo3(button: Button, infoView: TextView) : Demo(button, infoView) {

    override fun onCreate() {
        getToken { token ->
            setToken(token)
            getUserInfo(token){ userInfo ->
                setUserInfo(userInfo)
            }
        }
    }
    private fun getToken(callback: (token: String) -> Unit){
        thread {
            val token = URL("https://www.baidu.com/getToken").readText().md5()
            uiHandler.post {
                callback(token)
            }
        }
    }
    private fun getUserInfo(token: String, callback: (userinfo: String) -> Unit){
        thread {
            val userInfo = URL("https://www.baidu.com/userInfo?$token").readText().md5()
            uiHandler.post {
                callback(userInfo)
            }
        }
    }
}

我們可以看到這種方式將線程切換、計(jì)算封裝到了一個(gè)方法內(nèi)部毫炉,對(duì)外通過(guò)一個(gè)Callback接口給出計(jì)算結(jié)果瓮栗。那么在使用的過(guò)程中簡(jiǎn)化了操作,無(wú)需關(guān)系具體細(xì)節(jié)。一個(gè)Callback嵌套另一個(gè)Callback费奸,只有1~2層嵌套看上去還挺美好鲸郊。但是,我們實(shí)際使用中發(fā)現(xiàn)當(dāng)邏輯比較復(fù)雜時(shí)货邓,會(huì)出現(xiàn)N層嵌套的情況,可想而知會(huì)有多少層縮進(jìn)四濒』豢觯可讀性與復(fù)雜程度成指數(shù)級(jí)下降。這就是可怕的:回調(diào)地獄

Future/Promise/Rx

好了盗蟆,好在時(shí)代在進(jìn)步戈二。出現(xiàn)了Future/Promise/Rx這幾種方法。之所以把這幾種放在一起喳资,是因?yàn)樗麄冇邢嗨浦幘蹩浴T僖粋€(gè)Promise/Rx我不太熟就不多講了,參照Future即可(如有不對(duì)歡迎指正)鲜滩。Future在java 1.8中提供了一個(gè)基于數(shù)據(jù)流向的封裝,把所有計(jì)算都看做數(shù)據(jù)從第一步處理到下一步處理再到下一步节值。這樣就把Callback嵌套給拍平了徙硅。只有一層邏輯。

/**
 * @Author : xialonghua
 * @Date : Create in 2019/1/28
 * @Description : future
 */
class Demo4(button: Button, infoView: TextView) : Demo(button, infoView) {

    override fun onCreate() {
        CompletableFuture.supplyAsync {
            URL("https://www.baidu.com/getToken").readText().md5()
        }.thenApply {
            setToken(it)
            it
        }.thenApplyAsync {
            URL("https://www.baidu.com/userInfo?$it").readText().md5()
        }.thenAccept {
           setUserInfo(it)
        }
    }
}

我們可以看到搞疗,就一層嗓蘑,數(shù)據(jù)一路向下傳遞直到最后。具體的異步還是同步細(xì)節(jié)匿乃,都被封裝到了Futrue內(nèi)部桩皿。讓開發(fā)者更加關(guān)注業(yè)務(wù)邏輯。其實(shí)要說(shuō)也有缺點(diǎn)幢炸,我感覺API太復(fù)雜了泄隔,上手比較難,容易被不會(huì)用的人玩出翔來(lái)阳懂。梅尤。

Coroutine

前面說(shuō)了那么多方法,對(duì)比下前面說(shuō)的同步編程方法岩调,我認(rèn)為把異步化為同步最簡(jiǎn)單巷燥。異步編程除了異步不阻塞UI這個(gè)天大的好處,比起同步編程的順著寫邏輯更復(fù)雜号枕,從Future/Promise/Rx來(lái)看缰揪,他們也是通過(guò)封裝盡量將異步復(fù)雜的切換扁平化,來(lái)達(dá)到簡(jiǎn)化的目的。但是從寫代碼來(lái)看钝腺,還是太復(fù)雜了抛姑。沒(méi)有純粹的同步代碼好寫。
這時(shí)候Coroutine出現(xiàn)了艳狐,他以語(yǔ)言\編譯器級(jí)別的支持將異步編程變成了同步編程定硝。

/**
 * @Author : xialonghua
 * @Date : Create in 2019/1/28
 * @Description : coroutines suspend
 */
class Demo6(button: Button, infoView: TextView) : Demo(button, infoView) {

    override fun onCreate() {
        GlobalScope.launch(Dispatchers.Main) {
            val a = async { computeA() }
            val b = async { computeB() }
            delay(2000)
            setUserInfo("sum : ${a.await() + b.await()}")
        }
    }
    private suspend fun computeA() : Int{
        repeat(3){
            delay(1000)
        }
        return 125
    }
    private suspend fun computeB() : Int{
        repeat(3){
            delay(1000)
        }
        return 100
    }
}

稍微改變下需求更加直觀,同時(shí)求computeA/computeB的值并顯示到UI毫目。我們可以看到代碼很簡(jiǎn)單蔬啡,如果不要同時(shí)計(jì)算AB,可以去掉async镀虐。這不就是同步的寫法么箱蟆?運(yùn)行起來(lái)會(huì)發(fā)現(xiàn)并沒(méi)有阻塞UI。對(duì)刮便,就是這么神奇空猜。

基礎(chǔ)概念

那么接下來(lái)我們就簡(jiǎn)單介紹下如何使用協(xié)程。還是回到getToken的吧恨旱。辈毯。雖然寫的不太合適但是能夠直觀的了解協(xié)程的相關(guān)概念。

/**
 * @Author : xialonghua
 * @Date : Create in 2019/1/28
 * @Description : coroutines
 */
class Demo5(button: Button, infoView: TextView) : Demo(button, infoView) {

    lateinit var job : Job //1. 每個(gè)協(xié)程都是一個(gè)job窖杀,可以取消
    override fun onCreate() {
        job = GlobalScope /*2. 所有的協(xié)程都在一個(gè)作用域下執(zhí)行*/.launch {//3. launch 表示啟動(dòng)一個(gè)協(xié)程
            val token = getToken() //掛起函數(shù)執(zhí)行完后協(xié)程會(huì)被掛起漓摩,等待被恢復(fù)的時(shí)機(jī)
            launch(
                this.coroutineContext + Dispatchers.Main //4. 每個(gè)協(xié)程都有個(gè)一個(gè)context
            ){
                setToken(token)
            }
            val userInfo = async {
                getUserInfo(token)
            }
            launch(Dispatchers.Main){
                delay(3000)
                setUserInfo(userInfo.await() /*可以等待數(shù)據(jù)返回,與launch的區(qū)別*/)
            }
        }
//        job.cancel()
//        setText("job is canceled")
    }

    private suspend fun getToken() : String{
        delay(100)
        return URL("https://www.baidu.com/getToken").readText().md5()
    }

    private suspend fun getUserInfo(token: String): String{
        delay(100)
        return URL("https://www.baidu.com/userInfo?$token").readText().md5()
    }

}

從上面代碼可以看到幾個(gè)關(guān)鍵的點(diǎn)入客。

  1. GlobalScope
  2. CoroutineContext
  3. launch/async
  4. Job
  5. cancel
  6. Dispatchers
  7. suspend

這里不具體的去說(shuō)如何使用管毙,而是把幾個(gè)關(guān)鍵的概念拎出來(lái)描述清楚,那么以后就能很好理解了如何使用了桌硫。

GlobalScope

如其名Scope夭咬、Global。兩層含義铆隘。Global表示是一個(gè)全局的作用域卓舵。還有其他的Scope,也可以自己實(shí)現(xiàn)接口CoroutineScope定義作用域膀钠。所有的協(xié)程都是在作用域下運(yùn)行掏湾。

CoroutineContext

看到Context,我們很容易想到Android里的Context肿嘲。對(duì)融击,每個(gè)協(xié)程都對(duì)應(yīng)有一個(gè)context,Context的作用就是用來(lái)保存協(xié)程相關(guān)的一些信息雳窟。比如Dispatchers尊浪、Job、名字、等等拇涤。他的數(shù)據(jù)結(jié)構(gòu)其實(shí)挺妖捣作,我看了半天才看懂。
最終的實(shí)現(xiàn)是一個(gè)叫CombinedContext的類鹅士,其實(shí)就是一個(gè)鏈表券躁,每個(gè)節(jié)點(diǎn)保存了一個(gè)Key。

launch/async

scope和context都具備了掉盅,那么如何啟動(dòng)Coroutine呢嘱朽?也很簡(jiǎn)單launch或者async就可以了,像啟動(dòng)一個(gè)線程一樣簡(jiǎn)單怔接。我們把這種叫做Builder∠」欤可以啟動(dòng)各式各樣的協(xié)程扼脐。
其中l(wèi)aunch和async的區(qū)別只有一個(gè)async返回的對(duì)象可以調(diào)用await方法掛起Coroutine直到async執(zhí)行完畢。

Job

job也好理解奋刽,每次啟動(dòng)一個(gè)Coroutine會(huì)返回一個(gè)job對(duì)象瓦侮。job對(duì)象可以對(duì)Coroutine進(jìn)行取消操作,async返回的job還能掛起當(dāng)前Coroutine直到Coroutine的job執(zhí)行完畢佣谐。

cancel

前面說(shuō)到Coroutine是可以取消的肚吏。直接使用Job的cancel方法即可。

取消需要其他配合

但是需要注意的是狭魂,如果Coroutine中執(zhí)行的代碼是無(wú)法退出的罚攀,比如while(true)。那么調(diào)用了cancel是不起作用的雌澄。只有在suspend方法結(jié)束的時(shí)候才會(huì)去生效斋泄。但是我們可以做一點(diǎn)改進(jìn):while(isActive)。isActive是Coroutine的狀態(tài)镐牺,如果調(diào)用了cancel炫掐,isActive會(huì)變成false。

父子Coroutine

我們很容易想到睬涧,Coroutine中啟動(dòng)Coroutine的情況募胃。在Kotlin中Coroutine是有父子關(guān)系的,那么父子關(guān)系默認(rèn)遵守以下幾條規(guī)律:

  1. Coroutine之間是父子關(guān)系,默認(rèn)繼承父Coroutine的context
  2. 父Coroutine會(huì)等待所有子Coroutine完成或取消才會(huì)結(jié)束
  3. 父Coroutine如果取消或者異常退出則會(huì)取消所有子Coroutine
  4. 子Coroutine異常退出則會(huì)取消父Coroutine
  5. 取消可以被try…finally捕獲畦浓,如果已經(jīng)取消會(huì)拋出異常

Dispatchers

這個(gè)也比較好理解痹束,我們知道Coroutine本質(zhì)上還是得依附于thread去執(zhí)行。因此我們需要一個(gè)調(diào)度器來(lái)指定Coroutine具體執(zhí)行在哪一個(gè)thread宅粥。

suspend

suspend關(guān)鍵字可以說(shuō)是實(shí)現(xiàn)Coroutine的關(guān)鍵参袱。它表示這個(gè)函數(shù)是可以被掛起的,只能在suspend修飾的方法中調(diào)用suspend方法。
也就是說(shuō)代碼執(zhí)行到suspend方法或者suspend方法結(jié)束抹蚀,會(huì)切換到其他Coroutine的其他suspend方法執(zhí)行剿牺。這也很好的解釋了前面的demo中,computeA和computeB是如何并行執(zhí)行的环壤。launch啟動(dòng)的Coroutine里的代碼為什么沒(méi)有阻塞UI晒来。因?yàn)閟uspend方法遇到delay或者其他suspend方法,會(huì)被掛起而不是像Thread.sleep那樣阻塞住線程郑现,等到合適的時(shí)機(jī)suspend方法會(huì)被恢復(fù)執(zhí)行湃崩。
至于中間是如何掛起并且如何恢復(fù),后續(xù)會(huì)講解接箫。

原理解析

下面從源碼的方面來(lái)簡(jiǎn)述攒读,Coroutine到底是如何實(shí)現(xiàn)函數(shù)掛起的。我們分幾部來(lái)講辛友。這里炒一個(gè)代碼薄扁。。自己弄實(shí)在是麻煩废累。

suspend fun postItem(item: Item): PostResult {
  val token = requestToken()
  val post = createPost(token, item)
  val postResult = processPost(post)
  return postResult
}

編譯期處理

suspend方法用起來(lái)挺簡(jiǎn)單邓梅,但實(shí)際上背后Kotlin做了很多不為人知的事情。
首先邑滨,被suspend關(guān)鍵字修飾后日缨,在編譯期間,我們看看編譯器做了哪些事情掖看。

CPS(Continuation Passing Style)

編譯器做的第一件事就是CPS轉(zhuǎn)換匣距。

  1. 將函數(shù)返回值去掉
  2. 添加cont: Continuation參數(shù),將結(jié)果放入resumeWith回調(diào)中哎壳。
//轉(zhuǎn)換后的偽代碼
fun postItem(item: Item, cont: Continuation): Any?{
}

我們?cè)倏纯碈ontinuation里有什么墨礁。

@SinceKotlin("1.3")
public interface Continuation<in T> {
    public val context: CoroutineContext
    public fun resumeWith(result: Result<T>)
}

看到resumeWith沒(méi)有,想到能做什么了嗎耳峦?
對(duì)恩静,異步與流程控制。做了CPS變換后蹲坷,中間就有了無(wú)限的可能驶乾,比如可以不直接執(zhí)行postItem里的代碼,而是通過(guò)Continuation決定何時(shí)再去執(zhí)行具體的代碼循签。這不就可以實(shí)現(xiàn)了函數(shù)的掛起與恢復(fù)嗎级乐?
說(shuō)白了其實(shí)kotlin中的Coroutine本質(zhì)上還是基于回掉去實(shí)現(xiàn),只是它幫我們將細(xì)節(jié)封裝在了編譯期間县匠。在外在看來(lái)风科,與阻塞編程沒(méi)有區(qū)別撒轮。
那么具體的函數(shù)實(shí)現(xiàn)放哪去了呢?

題外話:尾遞歸與CPS

說(shuō)到CPS大家都不清楚贼穆。說(shuō)到遞歸题山,應(yīng)該再熟悉不過(guò)」嗜可是這幾個(gè)詞擺在一起是為什么呢顶瞳?
遞歸
自己調(diào)用自己。愕秫。但是有個(gè)問(wèn)題慨菱。遞歸的性能眾所周知枫振,而且如果太多會(huì)出現(xiàn)棧溢出蝙寨。有什么優(yōu)化方案呢硬萍?答案:循環(huán)胯究。問(wèn)題又來(lái)了,有的語(yǔ)言壓根就沒(méi)有循環(huán)這一說(shuō)途样!那么有什么優(yōu)化方案呢瓢颅?答案:尾遞歸空幻。
尾遞歸
為什么叫尾遞歸课蔬。因?yàn)檫f歸的函數(shù)調(diào)用被放到了最后,所以叫尾遞歸郊尝。沒(méi)那么簡(jiǎn)單二跋。。還必須他的執(zhí)行并不依賴上一次的執(zhí)行結(jié)果流昏,這樣編譯器會(huì)將代碼優(yōu)化成類似循環(huán)的結(jié)構(gòu)扎即。這樣每次調(diào)用不用保存上次的棧,每次執(zhí)行都是重新開始况凉。因此尾遞歸在效率上比遞歸高出不少谚鄙,而且保留了可讀性。
還是上個(gè)代碼對(duì)比下把刁绒,經(jīng)典例子斐波拉切數(shù)列,可以執(zhí)行下對(duì)比下耗時(shí):

/**
 * @Author : xialonghua
 * @Date : Create in 2019/1/28
 * @Description : a new file
 */
//編譯器會(huì)將尾遞歸優(yōu)化成循環(huán)
fun fibonacci_tail(n: Int, acc1: Int, acc2: Int): Int {
    return if (n < 2) {
        acc1
    } else {
        fibonacci_tail(n - 1, acc2, acc1 + acc2)
    }
}

// 遞歸
fun fibonacci(n: Int): Int {
    return if (n <= 2) {
        1
    } else {
        fibonacci(n - 1) + fibonacci(n - 2)
    }
}

那么這和CPS什么關(guān)系呢闷营?
有沒(méi)發(fā)現(xiàn)尾遞歸在遞歸函數(shù)參數(shù)上多了2個(gè),將計(jì)算結(jié)果給直接給到下次遞歸知市。再看CPS轉(zhuǎn)換傻盟,是不是在函數(shù)后面加了個(gè)Continuation,然后把執(zhí)行結(jié)果放入resumeWith回調(diào)中嫂丙,然后繼續(xù)執(zhí)行娘赴。簡(jiǎn)直一毛一樣阿,將結(jié)果直接給到下次計(jì)算跟啤,而不是自上而下又自下而上的調(diào)用棧關(guān)系诽表。

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

@SinceKotlin("1.3")
public abstract class BaseContinuationImpl:Continuation<in T> {

    protected abstract fun invokeSuspend(result: Result<Any?>): Any?

}

知道了是如何掛起和恢復(fù)唉锌,那么suspend方法里還有別的suspend方法呢?編譯器還做了點(diǎn)別的事情竿奏。袄简。那就是狀態(tài)機(jī)。首先將具體實(shí)現(xiàn)放到了invokeSuspend中议双。它把每一步suspend方法都用一個(gè)狀態(tài)表示痘番。當(dāng)一個(gè)suspend方法執(zhí)行完后,將狀態(tài)改變平痰,然后交由Continuation的resumeWith來(lái)繼續(xù)執(zhí)行下一個(gè)步驟汞舱。說(shuō)白了就像遞歸一樣不停的調(diào)用resumeWith來(lái)向前推進(jìn),至于是直接返回還是繼續(xù)掛起宗雇,取決于resumeWith的返回值昂芜。值得注意的是子suspend也會(huì)持有父suspend的Continuation實(shí)例,形成一個(gè)鏈表赔蒲,這樣就能在子suspend執(zhí)行完后回到父suspend繼續(xù)執(zhí)行泌神。


圖

3108769-730fe1e81c30a4d0..jpg
3108769-730fe1e81c30a4d0..jpg

非編譯期處理

異步

前面講的這些其實(shí)本質(zhì)上還是在同一個(gè)線程不停的回調(diào)執(zhí)行,并沒(méi)有實(shí)現(xiàn)異步舞虱,并沒(méi)有將Coroutine分布到其他線程欢际。那么是如果做到異步的呢?

Interceptor and Dispatcher

通過(guò)前面可以知道Continuation是持有父Continuation引用的矾兜,是一個(gè)鏈表损趋。那么引入Interceptor的概念,在原本的調(diào)用鏈里加入一個(gè)InterceptorContinuation椅寺,里面包含Dispatcher的引用浑槽。它的resumeWith里不干別的,就把下一個(gè)Continuation的resumeWith通過(guò)Dispatcher丟到其他線程里執(zhí)行返帕。
至于線程的調(diào)度就交給Dispatcher去完成桐玻,Dispatcher可以有多種實(shí)現(xiàn),比如使用線程池荆萤、使用Handler等等镊靴。
是不是很巧妙?

到這里Coroutine的實(shí)現(xiàn)原理就說(shuō)的差不多了链韭。后面再講講其他的邑闲。

與Thread的對(duì)比

這個(gè)其實(shí)沒(méi)什么好比了。創(chuàng)建10000個(gè)線程的話內(nèi)存肯定是吃不消梧油,但是創(chuàng)建10000個(gè)Coroutine肯定是沒(méi)問(wèn)題的苫耸。通過(guò)前面的講解,很容易知道Coroutine只是一個(gè)Continuation鏈表儡陨,它只會(huì)占用鏈表的內(nèi)存空間褪子,比一個(gè)thread消耗不是一個(gè)量級(jí)

用同步的方法編寫異步代碼

這一塊還是值得說(shuō)道的量淌。怎么寫代碼最簡(jiǎn)單,無(wú)疑是同步代碼最簡(jiǎn)單嫌褪。那么Coroutine帶來(lái)的好處顯而易見呀枢,寫代碼只管按照同步去寫,至于何時(shí)掛起何時(shí)恢復(fù)全由Coroutine內(nèi)部處理笼痛。至少外在看起來(lái)就是同步的代碼裙秋。感覺說(shuō)還是說(shuō)不清,還是給一個(gè)例子來(lái)說(shuō)明吧缨伊。
看demo思考一個(gè)問(wèn)題摘刑,滿足如下需求,用其他異步編程方法需要多少代碼:

  1. 點(diǎn)擊按鈕開始計(jì)數(shù)刻坊,并把按鈕disable
  2. 使用retrofit請(qǐng)求百度并將返回結(jié)果轉(zhuǎn)成MD5(算法UUID代替)枷恕,每隔2秒toast輸出一次
  3. 取消計(jì)時(shí),并設(shè)置textview文本為”Hello World Finish”
  4. enable按鈕
  5. activity destroy的時(shí)候停止計(jì)時(shí)谭胚、請(qǐng)求網(wǎng)絡(luò)徐块、定時(shí)彈toast
demo
demo

我再貼出使用了Coroutine的核心代碼來(lái)對(duì)比一下,有沒(méi)感覺簡(jiǎn)單清晰很多:

class MainActivity2 : AppCompatActivity(), CoroutineScope, AnkoLogger {
    private val job = SupervisorJob()
    override val coroutineContext: CoroutineContext = Dispatchers.Main + job

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        helloClick.onClick {
            val tickJob = launch {
                repeat(100000){
                    delay(10)
                    helloClick.text = "Hello World $it"
                    info("====")
                }
            }
            helloClick.isEnabled = false
            try {
                val result = api.getBaidu().await()
                repeat(3){
                    toast(result.md5())
                    delay(2000)
                }
            }catch (e: Exception){
                e.printStackTrace()
                //請(qǐng)求異常處理
                toast("網(wǎng)絡(luò)錯(cuò)誤")
            }

            tickJob.cancel()
            helloClick.isEnabled = true
            helloClick.text = "Hello World Finish"
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }

    private fun View.onClick(handler: (suspend CoroutineScope.(v: android.view.View?)->Unit)){
        setOnClickListener { v ->
            launch {
                handler(v)
            }
        }
    }
}

源碼閱讀關(guān)鍵類幫助

我列了一些關(guān)于Coroutine關(guān)鍵的類灾而,看源碼可以從這些地方入手:

  1. CoroutineContext胡控、CombinedContext context的具體實(shí)現(xiàn)
  2. Continuation、BaseContinuationImpl旁趟、ContinuationImpl昼激、SuspendLambda 編譯處理以及流程控制(掛起恢復(fù))
  3. ContinuationInterceptor、CoroutineDispatcher 攔截器與dispatcher轻庆,實(shí)現(xiàn)了異步
  4. AbstractCoroutine builder的實(shí)現(xiàn)抽象父類
  5. CoroutineScope Coroutine的scope

總結(jié)

前面寫了這么多,其實(shí)大多都是描述Coroutine的本質(zhì)敛劝。文字比較多有可能沒(méi)有描述清楚余爆,歡迎拍磚。下面我自己總結(jié)兩點(diǎn):

  1. Coroutine的性能消耗對(duì)比Thread微乎其微
  2. 更重要的是它帶來(lái)了一種新的編程方式夸盟,讓異步編程不再?gòu)?fù)雜

demo源碼:https://github.com/xialonghua/AndroidCoroutineRetrofit

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛾方,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子上陕,更是在濱河造成了極大的恐慌桩砰,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件释簿,死亡現(xiàn)場(chǎng)離奇詭異亚隅,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)庶溶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門煮纵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)懂鸵,“玉大人,你說(shuō)我怎么就攤上這事行疏〈夜猓” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵酿联,是天一觀的道長(zhǎng)终息。 經(jīng)常有香客問(wèn)我,道長(zhǎng)贞让,這世上最難降的妖魔是什么周崭? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮震桶,結(jié)果婚禮上休傍,老公的妹妹穿的比我還像新娘。我一直安慰自己蹲姐,他們只是感情好磨取,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柴墩,像睡著了一般忙厌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上江咳,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天逢净,我揣著相機(jī)與錄音,去河邊找鬼歼指。 笑死爹土,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的踩身。 我是一名探鬼主播胀茵,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挟阻!你這毒婦竟也來(lái)了琼娘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤附鸽,失蹤者是張志新(化名)和其女友劉穎脱拼,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坷备,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡熄浓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了省撑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玉组。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谎柄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惯雳,到底是詐尸還是另有隱情朝巫,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布石景,位于F島的核電站劈猿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏潮孽。R本人自食惡果不足惜揪荣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望往史。 院中可真熱鬧仗颈,春花似錦、人聲如沸椎例。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)订歪。三九已至脖祈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刷晋,已是汗流浹背盖高。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留眼虱,地道東北人喻奥。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像捏悬,于是被迫代替她去往敵國(guó)和親撞蚕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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