是什么
官方解釋:協(xié)程是輕量級的線程,基于線程池API
通俗的來說:就是官方提供的線程框架
線程主要用于做耗時操作,所以協(xié)程也是用來處理耗時任務(wù)。實現(xiàn)順序編寫異步代碼,自動進行線程切換
優(yōu)點
- 方便,借助kotlin的語言優(yōu)勢,用同步的方式寫出異步的代碼
- 將不同線程的代碼寫在同一個代碼塊里
- 消除了回調(diào)地獄
- 并發(fā)操作更容易,提高了性能
怎么用
啟動協(xié)程
println("start---->${Thread.currentThread().name}")
GlobalScope.launch(Dispatchers.IO){
Thread.sleep(2000)
println("GlobalScope---->${Thread.currentThread().name}")
}
println("GlobalScope end---->${Thread.currentThread().name}")
Thread.sleep(4000)
打印結(jié)果
start---->main
GlobalScope end---->main
GlobalScope---->DefaultDispatcher-worker-1
上面的代碼中可以分為三部分,分別是 GlobalScope、Dispatcher 和 launch,他們分別對應(yīng)著協(xié)程的作用域、調(diào)度器和協(xié)程構(gòu)建器
協(xié)程作用域
是協(xié)程范圍,指的是協(xié)程內(nèi)的代碼運行的時間周期范圍,如果超出了指定的協(xié)程范圍,協(xié)程會被取消執(zhí)行。
協(xié)程的作用域有三種,他們分別是:
- runBlocking:頂層函數(shù),它和 coroutineScope 不一樣,它會阻塞當前線程來等待,所以這個方法在業(yè)務(wù)中并不適用 。
- GlobalScope:指的是與應(yīng)用進程相同的協(xié)程范圍,也就是在進程沒有結(jié)束之前協(xié)程內(nèi)的代碼都可以運行,且不能取消,所以仍不適用于業(yè)務(wù)開發(fā)。
- 自定義作用域:自定義協(xié)程的作用域,不會造成內(nèi)存泄漏。
顯然,我們不能在 Activity 中調(diào)用 GlobalScope,這樣可能會造成內(nèi)存泄漏,看一下如何自定義作用域,具體的步驟我在注釋中已給出:
class MainActivity : AppCompatActivity() {
// 1. 創(chuàng)建一個 MainScope
val scope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 2. 啟動協(xié)程
scope.launch(Dispatchers.Unconfined) {
val one = getResult(20)
val two = getResult(40)
mNumTv.text = (one + two).toString()
}
}
// 3. 銷毀的時候釋放
override fun onDestroy() {
super.onDestroy()
scope.cancel()
}
private suspend fun getResult(num: Int): Int {
delay(5000)
return num * num
}
}
調(diào)度器
調(diào)度器的作用是將協(xié)程限制在特定的線程執(zhí)行。主要的調(diào)度器類型有:
Dispatchers.Main:指定執(zhí)行的線程是主線程,如上面的代碼。
Dispatchers.IO:指定執(zhí)行的線程是 IO 線程。
Dispatchers.Default:默認的調(diào)度器,適合執(zhí)行 CPU 密集性的任務(wù)。
Dispatchers.Unconfined:非限制的調(diào)度器,指定的線程可能會隨著掛起的函數(shù)的發(fā)生變化。
協(xié)程掛起
在開啟協(xié)程中,運行到掛起函數(shù),會阻塞當前協(xié)程并掛起,當掛起函數(shù)執(zhí)行完成后,會自動喚醒掛起的協(xié)程,繼續(xù)執(zhí)行協(xié)程

launch
launch 的作用從它的名稱就可以看的出來,啟動一個新的協(xié)程,它返回的是一個 Job對象,我們可以調(diào)用 Job#cancel() 取消這個協(xié)程。
除了 launch,還有一個方法跟它很像,就是 async,
用async方法包裹了suspend方法來執(zhí)行并發(fā)請求,并發(fā)結(jié)果都返回之后,切換到主線程,接著再用await方法來獲取并發(fā)請求結(jié)果。
它的作用是創(chuàng)建一個協(xié)程,之后返回一個 Deferred<T>對象,我們可以調(diào)用 Deferred#await()去獲取返回的值,有點類似于 Java 中的 Future,稍微改一下上面的代碼:
class MainActivity : AppCompatActivity() {
// 1. 創(chuàng)建一個 MainScope
val scope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 2. 啟動協(xié)程
scope.launch(Dispatchers.Unconfined) {
//并發(fā)執(zhí)行 相當于兩個線程同時執(zhí)行
val one = async { getResult(20) }
val two = async { getResult(40) }
//等帶全部執(zhí)行完成才有返回值
mNumTv.text = (one.await() + two.await()).toString()
}
}
// 3. 銷毀的時候釋放
override fun onDestroy() {
super.onDestroy()
scope.cancel()
}
private suspend fun getResult(num: Int): Int {
delay(5000)
return num * num
}
}
與修改前的代碼相比,async 能夠并發(fā)執(zhí)行任務(wù),執(zhí)行任務(wù)的時間也因此縮短了一半。
除了上述的并發(fā)執(zhí)行任務(wù),async 還可以對它的 start 入?yún)⒃O(shè)置成懶加載
val one = async(start = CoroutineStart.LAZY) { getResult(20) }
這樣系統(tǒng)就可以在調(diào)用它的時候再為它分配資源了。
suspend
suspend關(guān)鍵字只起到了標志這個函數(shù)是一個耗時操作,必須放在協(xié)程中執(zhí)行的作用,而withContext方法則進行了線程的切換工作。
協(xié)程中的代碼自動地切換到其他線程之后又自動地切換回了主線程!順序編寫保證了邏輯上的直觀性,協(xié)程的自動線程切換又保證了代碼的非阻塞性。掛起函數(shù)必須在協(xié)程或者其他掛起函數(shù)中被調(diào)用,也就是掛起函數(shù)必須直接或者間接地在協(xié)程中執(zhí)行
那為什么協(xié)程中的代碼沒有在主線程中執(zhí)行呢?而且執(zhí)行完畢為什么還會自動地切回主線程呢?
協(xié)程的掛起可以理解為協(xié)程中的代碼離開協(xié)程所在線程的過程,協(xié)程的恢復(fù)可以理解為協(xié)程中的代碼重新進入?yún)f(xié)程所在線程的過程。協(xié)程就是通過的這個掛起恢復(fù)機制進行線程的切換。
那我們什么時候需要使用掛起函數(shù)呢?
常見的場景有:
耗時操作:使用 withContext 切換到指定的 IO
線程去進行網(wǎng)絡(luò)或者數(shù)據(jù)庫請求。等待操作:使用delay方法去等待某個事件。
withContext 的代碼:
private suspend fun getResult(num: Int): Int {
return withContext(Dispatchers.IO) {
num * num
}
}
delay操作
private suspend fun getResult(num: Int): Int {
delay(5000)
return num * num
}
結(jié)合 Android Jetpack
ViewModelScope,為應(yīng)用中的每個 ViewModel 定義了 ViewModelScope。如果 ViewModel 已清除,則在此范圍內(nèi)啟動的協(xié)程都會自動取消。
LifecycleScope,為每個 Lifecycle 對象定義了 LifecycleScope。在此范圍內(nèi)啟動的協(xié)程會在 Lifecycle 被銷毀時取消。
在介紹自定義協(xié)程作用域的時候,我們需要主動在 Activity 或者 Fragment 中的 onDestroy 方法中調(diào)用 job.cancel(),忘記處理可能是程序員經(jīng)常會犯的錯誤,如何避免呢?
lifecycleScope.launch {
// 代表當前生命周期處于 Resumed 的時候才會執(zhí)行(選擇性使用)
whenResumed {
// ... 具體的協(xié)程代碼
}
}