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有哪些常用的方法呢鸠项?
- Thread
- Callback
- 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)入客。
- GlobalScope
- CoroutineContext
- launch/async
- Job
- cancel
- Dispatchers
- 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ī)律:
- Coroutine之間是父子關(guān)系,默認(rèn)繼承父Coroutine的context
- 父Coroutine會(huì)等待所有子Coroutine完成或取消才會(huì)結(jié)束
- 父Coroutine如果取消或者異常退出則會(huì)取消所有子Coroutine
- 子Coroutine異常退出則會(huì)取消父Coroutine
- 取消可以被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)換匣距。
- 將函數(shù)返回值去掉
- 添加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í)行泌神。


非編譯期處理
異步
前面講的這些其實(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)題摘刑,滿足如下需求,用其他異步編程方法需要多少代碼:
- 點(diǎn)擊按鈕開始計(jì)數(shù)刻坊,并把按鈕disable
- 使用retrofit請(qǐng)求百度并將返回結(jié)果轉(zhuǎn)成MD5(算法UUID代替)枷恕,每隔2秒toast輸出一次
- 取消計(jì)時(shí),并設(shè)置textview文本為”Hello World Finish”
- enable按鈕
- activity destroy的時(shí)候停止計(jì)時(shí)谭胚、請(qǐng)求網(wǎng)絡(luò)徐块、定時(shí)彈toast

我再貼出使用了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)鍵的類灾而,看源碼可以從這些地方入手:
- CoroutineContext胡控、CombinedContext context的具體實(shí)現(xiàn)
- Continuation、BaseContinuationImpl旁趟、ContinuationImpl昼激、SuspendLambda 編譯處理以及流程控制(掛起恢復(fù))
- ContinuationInterceptor、CoroutineDispatcher 攔截器與dispatcher轻庆,實(shí)現(xiàn)了異步
- AbstractCoroutine builder的實(shí)現(xiàn)抽象父類
- CoroutineScope Coroutine的scope
總結(jié)
前面寫了這么多,其實(shí)大多都是描述Coroutine的本質(zhì)敛劝。文字比較多有可能沒(méi)有描述清楚余爆,歡迎拍磚。下面我自己總結(jié)兩點(diǎn):
- Coroutine的性能消耗對(duì)比Thread微乎其微
- 更重要的是它帶來(lái)了一種新的編程方式夸盟,讓異步編程不再?gòu)?fù)雜
demo源碼:https://github.com/xialonghua/AndroidCoroutineRetrofit