目錄
kotlin之協(xié)程(一),線程,進(jìn)程,協(xié)程,協(xié)程可以替換線程嗎?
kotlin之協(xié)程(二),Kotlin協(xié)程是什么、掛起是什么、掛起的非阻塞式
kotlin之協(xié)程(三),開始創(chuàng)建協(xié)程,launch,withContext
kotlin之協(xié)程(四),協(xié)程的核心關(guān)鍵字suspend
kotlin之協(xié)程(五),launch 函數(shù)以及協(xié)程的取消與超時
kotlin之協(xié)程(六),協(xié)程中的 async和launch的區(qū)別以及runBlocking
kotlin之協(xié)程(七),協(xié)程中relay、yield 區(qū)別
前言
在開始做安卓之前都是學(xué)習(xí)如何用框架,用一個庫,但是在用的過程中不了解核心本質(zhì),無法理解其含義.
后來就換了學(xué)習(xí)的方法
在用一個東西之前,都是先了解這個事物的本質(zhì),再去學(xué)習(xí)如何用,這樣的形式可能在用的過程中有更深的體會.在使用的過程中再去更深入的了解其如何實現(xiàn).
我們想在kotlin使用協(xié)程
項目中配置對 Kotlin 協(xié)程的支持
在使用協(xié)程之前,我們需要在 build.gradle 文件中增加對 Kotlin 協(xié)程的依賴:
- 項目根目錄下的 build.gradle :
buildscript {
ext.kotlin_coroutines = '1.4.0'
}
- Module 下的 build.gradle
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0'
}
創(chuàng)建協(xié)程
kotlin 中 GlobalScope 類提供了幾個攜程構(gòu)造函數(shù):
- launch - 創(chuàng)建協(xié)程
- async - 創(chuàng)建帶返回值的協(xié)程,返回的是 Deferred 類
- withContext - 不創(chuàng)建新的協(xié)程,指定協(xié)程上運(yùn)行代碼塊
- runBlocking - 不是 GlobalScope 的 API,可以獨(dú)立使用,區(qū)別是 runBlocking 里面的 delay 會阻塞線程,而 launch 創(chuàng)建的不會
先跑起來一個簡單的例子:
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 在后臺啟動一個新的協(xié)程并繼續(xù)
delay(1000L) // 非阻塞的等待 1 秒鐘(默認(rèn)時間單位是毫秒)
println("World!") // 在延遲后打印輸出
}
println("Hello,") // 協(xié)程已在等待時主線程還在繼續(xù)
Thread.sleep(2000L) // 阻塞主線程 2 秒鐘來保證 JVM 存活
}
協(xié)程最簡單的使用方法,其實在前面章節(jié)就已經(jīng)看到了。我們可以通過一個 launch 函數(shù)實現(xiàn)線程切換的功能
coroutineScope.launch(Dispatchers.IO) {
...
}
所以,什么時候用協(xié)程?當(dāng)你需要切線程或者指定線程的時候。你要在后臺執(zhí)行任務(wù)?切!
launch(Dispatchers.IO) {
val image = getImage(imageId)
}
然后需要在前臺更新界面?再切!
coroutineScope.launch(Dispatchers.IO) {
val image = getImage(imageId)
launch(Dispatchers.Main) {
avatarIv.setImageBitmap(image)
}
}
好像有點不對勁?這不還是有嵌套嘛。
如果只是使用 launch 函數(shù),協(xié)程并不能比線程做更多的事。不過協(xié)程中卻有一個很實用的函數(shù):withContext 。這個函數(shù)可以切換到指定的線程,并在閉包內(nèi)的邏輯執(zhí)行結(jié)束之后,自動把線程切回去繼續(xù)執(zhí)行。那么可以將上面的代碼寫成這樣:
coroutineScope.launch(Dispatchers.Main) { // 在 UI 線程開始
val image = withContext(Dispatchers.IO) { // 切換到 IO 線程,并在執(zhí)行完成后切回 UI 線程
getImage(imageId) // 將會運(yùn)行在 IO 線程
}
avatarIv.setImageBitmap(image) // 回到 UI 線程更新 UI
}
我們甚至可以把 withContext 放進(jìn)一個單獨(dú)的函數(shù)里面:
launch(Dispatchers.Main) { // 在 UI 線程開始
val image = getImage(imageId)
avatarIv.setImageBitmap(image) // 執(zhí)行結(jié)束后,自動切換回 UI 線程
}
//
fun getImage(imageId: Int) = withContext(Dispatchers.IO) {
...
}
這就是之前說的「用同步的方式寫異步的代碼」了。
不過如果只是這樣寫,編譯器是會報錯的:
fun getImage(imageId: Int) = withContext(Dispatchers.IO) {
// IDE 報錯 Suspend function'withContext' should be called only from a coroutine or another suspend funcion
}
意思是說,withContext 是一個 suspend 函數(shù),它需要在協(xié)程或者是另一個 suspend 函數(shù)中調(diào)用。
what?
suspend函數(shù)我們在kotlin之協(xié)程(二),Kotlin協(xié)程是什么、掛起是什么、掛起的非阻塞式有提到,下一章我們專門講解kotlin協(xié)程中的suspend