Kotlin—Coroutine(協(xié)程)的基本使用
什么是協(xié)程
在java中異步都會使用到線程,在kotlin中引入了協(xié)程的概念。與線程類似,協(xié)程也是用于處理異步的,不過與線程相比更加輕巧。協(xié)程完全通過編譯技術(shù)實現(xiàn),使用掛起機制來實現(xiàn)異步,而不會阻塞線程。協(xié)程是一種避免線程阻塞、開銷更小且更加可控的異步操作。
協(xié)程的基礎(chǔ)使用
創(chuàng)建協(xié)程,有三種方式runBlocking、launch、async。
-
runBlocking創(chuàng)建一個阻塞的協(xié)程,當(dāng)協(xié)程內(nèi)部代碼執(zhí)行完畢后才會執(zhí)行后面的代碼。fun main() { println("Hello") runBlocking { delay(1000) println("World") } println("---end---") }在
runBlocking創(chuàng)建的協(xié)程里面,調(diào)用delay讓此協(xié)程掛起1秒。執(zhí)行代碼,首先打印Hello,1秒后打印World,最后打印—end—。注意delay掛起函數(shù)只有在協(xié)程內(nèi)部才能調(diào)用。 -
launch在當(dāng)前協(xié)程作用域下面創(chuàng)建一個非阻塞子協(xié)程,同時返回個Job對象,用來控制當(dāng)前協(xié)程。fun main() = runBlocking { println("hello") launch { delay(1000) println("world") } println("---end---") }使用
runBlocking來包裹main函數(shù),函數(shù)整體都處于runBlocking創(chuàng)建的協(xié)程作用域下面,然后通過launch創(chuàng)建一個子協(xié)程,掛起1秒之后打印輸出。執(zhí)行代碼發(fā)現(xiàn)首先輸出hello和—end—,等待一秒之后打印world,如何讓他按順序執(zhí)行。fun main() = runBlocking { println("hello") val job = launch { delay(1000) println("world") } job.join() println("---end---") }修改代碼,拿到
launch返回的job對象,調(diào)用join()函數(shù),此時會等待job協(xié)程內(nèi)部的代碼執(zhí)行完畢后才會往后執(zhí)行。執(zhí)行代碼,先打印hello,然后1秒后打印world和—end—。 -
Job常用方法- isActive
- isCompleted
- isCanceledstart
- cancel
- join
-
GlobalScope.launch創(chuàng)建一個全局非阻塞協(xié)程,fun main() { println("hello") GlobalScope.launch { delay(1000) println("world") } Thread.sleep(1500) println("---end---") }使用
GlobalScope.launch在任何地方都能創(chuàng)建一個全局的協(xié)程,由于launch創(chuàng)建的協(xié)程是非阻塞的,所以讓當(dāng)前線程睡眠1.5秒等待協(xié)程執(zhí)行完畢。與上面代碼打印結(jié)果一樣。由于使用GlobalScope.launch時,會創(chuàng)建一個頂層協(xié)程。雖然很輕量,但它運行時仍會消耗一些內(nèi)存資源,所以通常是在協(xié)程內(nèi)部作用域下使用launch創(chuàng)建協(xié)程,而不是使用GlobalScope來創(chuàng)建全局協(xié)程。 -
async創(chuàng)建一個非阻塞協(xié)程,同時返回Deferred,可通過await()函數(shù)獲取協(xié)程返回的具體值;fun main() = runBlocking { println("hello") val def = async { delay(1000) "world" } println(def.await()) println("---end---") } -
掛起函數(shù)
接著上面例子,將
launch協(xié)程內(nèi)部的代碼封裝為一個函數(shù),使用suspend關(guān)鍵,這個函數(shù)就叫掛起函數(shù),上面的delay就是一個封裝好的掛起函數(shù)。fun main() = runBlocking { println("hello") val job = launch { delay1000() } job.join() println("---end---") } suspend fun delay1000(){ delay(1000) println("world") } -
調(diào)度器
上面提到三種創(chuàng)建協(xié)程的方式都有一個選填參數(shù)
CoroutineContext,協(xié)程的上下文環(huán)境。也就是協(xié)程調(diào)度器,調(diào)度器確定了協(xié)程執(zhí)行的線程環(huán)境。-
Dispatchers.Default默認后臺線程池里的線程 ; -
Dispatchers.MainAndroid主線程; -
Dispatchers.IO后臺線程池里的IO線程 ; -
Dispatchers.Unconfined不限制,使用父協(xié)程所屬的線程; -
newSingleThreadContext使用新的線程。
如需要在協(xié)程中切換調(diào)度器可使用
withContext,withContext返回最后一行代碼的返回值。 -
-
Android中的使用
簡單了介紹了協(xié)程的基本使用,寫個小demo,模擬網(wǎng)絡(luò)請求然后再界面展示數(shù)據(jù)。在activity中首先要實現(xiàn)
CoroutineScope接口,然后通過by關(guān)鍵字將接口的具體實現(xiàn)委托給MainScope,這樣當(dāng)前activity就是一個協(xié)程作用域了。class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button.setOnClickListener { showData() } } // 創(chuàng)建協(xié)程,默認在當(dāng)前線程,也就是UI線程 private fun showData() = launch { progress.visibility = View.VISIBLE textView.text = loadData() progress.visibility = View.GONE } //網(wǎng)絡(luò)請求 切換為IO線程 private suspend fun loadData()= withContext(Dispatchers.IO) { delay(2000)//掛起2秒 模擬網(wǎng)絡(luò)請求 "假裝這是網(wǎng)絡(luò)請求到的數(shù)據(jù)" //返回請求到的數(shù)據(jù) } }
基本介紹完了協(xié)程的最最最基礎(chǔ)的使用,相比于線程除了內(nèi)存開銷更小、不會造成線程阻塞的優(yōu)點之外之外,個人覺得在沒有用RxJava的情況下可以少寫很多線程的創(chuàng)建、切換、異步接口的回調(diào)。讓代碼更加干凈清爽。
個人學(xué)習(xí)筆記,如有錯誤不對的地方歡迎各位大佬指出。