Kotlin開發利器之協程
Kotlin開發利器之協程
協程的定義
協程的開發人員 Roman Elizarov 是這樣描述協程的:協程就像非常輕量級的執行緒。執行緒是由系統排程的,執行緒切換或執行緒阻塞的開銷都比較大。而協程依賴於執行緒,但是協程掛起時不需要阻塞執行緒,幾乎是無代價的,協程是由開發者控制的。所以協程也像使用者態的執行緒,非常輕量級,一個執行緒中可以建立任意個協程。
專案中引入Kotlin的協程
新增依賴:coroutines庫在Kotlin1.3版本的時候已經升級為正式版,命名為1.0.0。
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0' // 可以新增Android的依賴 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'
1.協程方法的開啟
GlobalScope.launch
fun main(args: Array<String>) { GlobalScope.launch { // 在後臺啟動一個新的協程並繼續 delay(1000L) // 非阻塞執行緒1s println("World!") // 在延遲後列印輸出 println("This is a coroutines ${TimeUtil.getTimeDetail()}") } println("Hello,") // 主執行緒的協程將會繼續等待 Thread.sleep(2000L) // 阻塞執行緒2s,保證JVM存活,協程可正常執行完 println("main end ${TimeUtil.getTimeDetail()}") } // 輸出 // Hello, // World! // This is a coroutines 10:49:58 // main end 10:49:59
線上程環境中可直接使用CoroutineScope.launch啟動一個新的協程,它的引數有如下三個,分別為:
- context: CoroutineContext = EmptyCoroutineContext:協程上下文;
- start: CoroutineStart = CoroutineStart.DEFAULT:啟動模式,預設是DEAFAULT,也就是建立就啟動;還有一個是LAZY,意思是等你需要它的時候,再呼叫啟動。在Kotlin 1.3版本中,還有ATOMIC和UNDISPATCHED兩個額外的模式,但是現在還是實驗版,這裡不多介紹;
- block: suspend CoroutineScope.() -> Unit:閉包引數,定義協程內需要執行的操作。
返回值為Job物件。Job有如下幾個重要的方法,分別為:
job.start()可配合LAZY啟動一個協程
fun main(args: Array<String>){
val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
println("this is a job")
}
job.start()
Thread.sleep(1000L)
}
// 輸出
// this is a job
job.join()等待協程執行完畢
suspend fun main(args: Array<String>){
val job = GlobalScope.launch {
println("this is a job")
}
job.join()
}
// 輸出
// this is a job
注意:join()函式是一個掛起函式,所有main必須被suspend修飾。
job.cancel()取消一個協程
suspend fun main(args: Array<String>){
val job = GlobalScope.launch {
println("this is a job")
}
job.cancel()
job.join()
}
// 無輸出,協程被取消了
job.cancelAndJoin()等待協程執行完畢然後再取消
suspend fun main(args: Array<String>){
val job = GlobalScope.launch {
println("this is a job")
}
job.cancelAndJoin()
}
// 輸出
// this is a job
job.cancelAndJoin()也是一個掛起函式。
從上面的程式碼和輸出可以看到,協程中的輸出和main中的輸出只相差了1s,也就說明了為什麼delay(1000L)是非阻塞的。delay()函式類似於Thread.sleep(),但是它不阻塞(non-blocking)執行緒,它是一個被suspend修飾的掛起函式,掛起函式只能被掛起函式呼叫或協程中呼叫。
2.GlobalScope.async
suspend fun main(args: Array<String>) {
val deferred = GlobalScope.async {
delay(1000L)
println("This is async ${TimeUtil.getTimeDetail()}")
[email protected] "taonce"
}
println("main start ${TimeUtil.getTimeDetail()}")
val result = deferred.await()
println("async result is $result")
println("main end ${TimeUtil.getTimeDetail()}")
}
// 輸出
// main start 15:27:19:668
// This is async 15:27:20:652
// async result is taonce
// main end 15:27:20:657
async和launch引數是一模一樣的,不同的是async返回的是Deferred物件,它繼承了Job介面,所以說Job有的它都有,並且還額外增加了一個方法:
public suspend fun await(): T這個方法接收的是async閉包中返回的值。如果閉包中需要返回一個值那麼我們就需要考慮用async了。
3.runBlocking
runBlocking的最大特點就是它的delay()可以阻塞當前的執行緒,和Thread.sleep()有著相同的效果。一般用runBlocking來橋接普通阻塞程式碼和掛起風格的非阻塞程式碼,在runBlocking閉包裡面啟動另外的協程。
協程實際運用
以最常用的網路請求為例,如果不用Kotlin的協程來實現,我們可以用Thread、RxJava等等很多種方法。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
coroutine.setOnClickListener { click() }
}
private fun click() = runBlocking {
GlobalScope.launch(Dispatchers.Main) {
coroutine.text = GlobalScope.async(Dispatchers.IO) {
// 比如進行了網路請求
// 放回了請求後的結構
[email protected] "main"
}.await()
}
}
}
上面程式碼的原理是:用async()在IO執行緒中去執行網路請求,然後通過await()返回請求結果,最後用launch(Dispatchers.Main)在主執行緒中更新UI。
其中用到了Dispatchers來指定協程所在的執行緒,目前Dispatchers有三種:
- Default:如果沒有指定具體的Dispatchers都會使用預設的,它使用的是最大的執行緒數;
- IO:用來排程阻塞的協程;
- Main:就是常說的UI執行緒了,是Android特有的。
總結
- 本質上,協程是輕量級的執行緒;
- 掛起函式(被suspend修飾的函式)不能在普通函式中被呼叫;
- 可以使用一些協程操作來替換一些執行緒操作,比如: 用 GlobalScope.launch { …… } 替換 thread { …… } 用 delay(……) 替換 Thread.sleep(……);
- 協程是輕量級的,比開啟執行緒節約資源;
- 在 GlobalScope 中啟動的活動中的協程就像守護執行緒一樣,不能使它們所在的程序保活。