還記得第一次聽到 Coroutines
的時候,納悶了一下皱坛,口瑞停编曼,這是什么新的番號招式(誤),之后其實也沒有多在意了剩辟,好一段時間掐场,因為一個檔案的 I/O 會把 UI Thread 卡住,必須要用異步程序去處理贩猎,寫 Handler Thread 可以避免熊户,這也是最基礎的方式,缺點也很明顯某些時候還是避不掉融欧,寫 RX 又總覺得微妙感覺有點殺雞用牛刀的感覺敏弃,后來看了一下決定用 Coroutines
,于是有了本篇文章噪馏。
是什么問題要解決麦到?
functionA()
functionB()
functionC()
普通情況下,執(zhí)行的順序會是很直白的 functionA()
-> functionB()
-> functionC()
欠肾。
如果只有一個 thread 瓶颠,這樣很順暢沒問題。
但假如這是一個跑在 main thread 上刺桃,而 ·function A· 是需要另一個 thread 的處理結果粹淋,而該結果是需要該 thread 耗費長時間作業(yè)才可以獲得的。這邊姑且稱為 IO thread 好了。那不就意味著 function A
得等到 IO thread 處理結束并告知結果才能繼續(xù)執(zhí)行 function A
乃至 function B
之后才是 function C
呢桃移?
那在等待 function A
的時候 main thread 啥事都不能做屋匕,只能 idle 在那邊動也不動。
這如果是在 Android main thread 上借杰,這樣的行為會讓畫面 freeze 过吻,時間稍微長一點就會 ANR
被 OS 當作壞掉進行異常排除了。
其實這個異步問題解決方案很多蔗衡,諸如自己寫一個 callback 纤虽,或者自干 Handler thread 去控管或者是用 RX ,去訂閱之類绞惦。某些時候顯得不直觀逼纸,或者使用起來麻煩,總有種殺雞何需使用牛刀的感覺济蝉。
那有沒有可能杰刽?我就寫成上面那樣,但是當 function A
在等待 IO thread 時堆生,讓 main thread 去做其他的事情专缠,等到 IO thread 結束耗時處理后,再回來繼續(xù)執(zhí)行 function A
淑仆,function B
、 function C
呢哥力?
是的蔗怠,可以,這個解決方案便是 Coroutine
吩跋。
Coroutines
到底是什么寞射?
Coroutines
,這個單字會被標成錯字锌钮,理由是他其實是兩個單字合并而成的桥温,分別是 cooperation + routine, cooperation 意指合作梁丘,routine 意指例行作業(yè)侵浸、慣例,照這里直接翻譯就會是合作式例行作業(yè)氛谜。
想到輝夜姬讓人想告白提到了慣例行為掏觉,也是念作 routine
那我們看到的翻譯多半會是協程、協作程序…這樣講沒啥前后感值漫,誰協助程序澳腹?協助啥程序? 總之就是滿頭的問號。
這里 routine 指得是程序中被呼叫的 function酱塔、method 沥邻,也就是說,我們將 function 羊娃、method 協同其他更多的 function谋国、method 共同作業(yè)這件事情稱為 Coroutines
。
協同作業(yè)聽起來還是很抽象迁沫,具體協同什么呢芦瘾?
這便是 Coroutines
最典型的特色,允許 method 被暫停( suspended)執(zhí)行之后再回復(resumed)執(zhí)行集畅,而暫停執(zhí)行的 method 狀態(tài)允許被保留近弟,復原后再以暫停時的狀態(tài)繼續(xù)執(zhí)行。
換句話說挺智,就是我在 main thread 執(zhí)行到 function A
需要等 IO thread 耗時處理的結果祷愉,那我先暫停 function A
, 協調讓出 main thread 讓 main thread 去執(zhí)行其他的事情赦颇,等到 IO thread 的耗時處理結束后得到結果后再回復 function A
繼續(xù)執(zhí)行二鳄,已得到我要的結果,這便是 Coroutines
的概念媒怯,這聽起來不是很簡單了呢订讼?
事實上這個概念早在 1964 年就已經被提出了,而很多語言也都有這樣的概念扇苞,只是 Android 上頭類似的東西一直沒有被積極推廣欺殿,直到 Kotlin 成為官方語言后,Coroutines
以 Support Library 的形式被推廣才又在 Android 業(yè)界流行起來鳖敷。
Kotlin Coroutines
首先脖苏,因為 Kotlin 的 Coroutine
并沒有包含在原有包裝中,而是以 Support Library 的形式提供開發(fā)者使用定踱,所以需要另外導入該 Library棍潘。
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
這里選用這個版本進行演示,實際中可以根據自己的需要修改版本崖媚。
那因為是在 Android 上使用的亦歉, Android 上頭的 main thread 跟普通 java 有點不一樣,所以還需要另一個 implementation至扰,不然會報錯鳍徽。
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
導入之后就可以開始使用了。
一個簡單的開始
這邊我想做的是畫面上有一個會倒數的 Text 敢课,用 Coroutines
可以簡單地做到
class CoroutineActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_coroutine)
GlobalScope.launch(Dispatchers.Main) {
for (i in 10 downTo 1) {
textView.text = "count down $i ..." // update text
delay(1000)
}
textView.text = "Done!"
}
}
}
那跑起來結果就像這樣:
這樣如果要 Thread 有相同的結果可以寫成這樣:
Thread {
for (i in 10 downTo 1) {
Thread.sleep(1000)
runOnUiThread {
textView.text = "count down $i ..."
}
}
runOnUiThread {
textView.text = "Done!"
}
}.start()
這樣會有什么問題就是另一個故事了阶祭,至少現在這樣不會馬上出現 Exception (最常見的就是使用者離開畫面沒多久就出現一個 Exception)绷杜,不過也并不是說用 Coroutines
就不會發(fā)生這些問題,記得這些做法沒有什么優(yōu)劣濒募,差別在都選擇就是了鞭盟。
說回 Coroutines
,那跟 Thread 一樣瑰剃,某些時候我們會想要臨時把它停住齿诉,那 GlobalScope.launch
會回傳一個 Job class 的玩意
val job: Job = GlobalScope.launch(Dispatchers.Main) {
// launch coroutine in the main thread
for (i in 10 downTo 1) { // countdown from 10 to 1
textView.text = "count down $i ..." // update text
delay(1000) // wait half a second
}
textView.text = "Done!"
}
想要把它停住的話就用 cancel
即可
job.cancel()
Scope
GlobalScope 是什么玩意?
Scope
指得是范圍晌姚,Coroutines
可以作用的范圍粤剧。在 Main thread 上或是 IO thread 上,又或者希望開更多的 Worker thread挥唠,然后是可以在某個控制流(e.g Activity 的生命周期)中可被控制的抵恋。
原則上,在 Kotlin 里頭使用任何標記 suspend
的 method(后面會提)都會在 Scope
里面宝磨,這樣才可以控制 Coroutines
的行進與存活與否弧关。
那這邊舉的例子, GlobalScope
繼承自 CoroutineScope
唤锉。它是 CoroutineScope
的一個實作世囊,它的概念就是最高層級的 Coroutines
,它的作用的范圍伴隨著 Application 的生命周期窿祥,那其實他的概念與 Dispatch.Unconfined
相同(待會會提到)株憾,用他其實可以避免 Coroutines
被過早結束,但也要記得是壁肋,這個用法類似直接呼叫靜態(tài)函數号胚,需要注意。
那如果直接實作 CoroutineScope
呢?
class CoroutineActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = TODO("not implemented")
//...ignore
}
那會要求實作一個 CoroutineContext
浸遗,這是什么玩意?指的就是 Coroutines
作用的情景箱亿,這邊可以指定他是在 Main thread 上或者就直接弄一個 Job 給他:
class CoroutineActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = job
private val job = Job()
//...ignore
}
這樣 launch 的時候就會使用這個 Job 來操作了跛锌,如果沒有特別定義,那這個 Job 就是跑在 Worker thread 上了届惋,用它更新 UI 會出現 Exception
髓帽,這方面可以依據自己的需求去做調整。
不過更多時候我會希望他能夠跑在 Main Thread 上脑豹,Koltinx Coroutine
有提供 CoroutineScope
的實作 - MainScrope