協(xié)程啟動(dòng)簡(jiǎn)單總結(jié)

先來(lái)看看調(diào)度器Dispatchers,里面有4種CoroutineDispatcher。

// kotlinx.coroutines.Dispatchers
public actual object Dispatchers {
    @JvmStatic
    public actual val Default: CoroutineDispatcher = DefaultScheduler
    @JvmStatic
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
    @JvmStatic
    public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
    @JvmStatic
    public val IO: CoroutineDispatcher = DefaultIoScheduler
}

一、協(xié)程啟動(dòng)

1.GlobalScope

來(lái)看看使用 GlobalScope 的情況下,使用不同Dispatchers下,協(xié)程運(yùn)行的情況。

// GlobalScope 使用
private const val TAG = "GlobalScope"

// 不傳入 CoroutineDispatcher
fun globalScopeTest() = GlobalScope.launch {
    //DefaultDispatcher-worker-1
    Log.d(TAG, "inner globalScopeTest, 當(dāng)前線程 = ${Thread.currentThread().name}")
}

// Dispatchers.Main
fun globalScopeTest1() = GlobalScope.launch(Dispatchers.Main) {
    // main
    Log.d(TAG, "inner globalScopeTest1, 當(dāng)前線程 = ${Thread.currentThread().name}")
}

// Dispatchers.IO
fun globalScopeTest2() = GlobalScope.launch(Dispatchers.IO) {
    // DefaultDispatcher-worker-1
    Log.d(TAG, "inner globalScopeTest2, 當(dāng)前線程 = ${Thread.currentThread().name}")
}

// Dispatchers.Default
fun globalScopeTest3() = GlobalScope.launch(Dispatchers.Default) {
    // DefaultDispatcher-worker-2
    Log.d(TAG, "inner globalScopeTest3, 當(dāng)前線程 = ${Thread.currentThread().name}")
}

// Dispatchers.Unconfined
fun globalScopeTest4() = GlobalScope.launch(Dispatchers.Unconfined) {
    delay(1000)
    //如果不加delay, 當(dāng)前線程 = main
    //使用delay后, 當(dāng)前線程 = kotlinx.coroutines.DefaultExecutor
    Log.d(TAG, "inner globalScopeTest4, 當(dāng)前線程 = ${Thread.currentThread().name}")
}

調(diào)用。

class MainActivity : AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        globalScopeTest()
        globalScopeTest1()
        globalScopeTest2()
        globalScopeTest3()
        globalScopeTest4()
    }
}

結(jié)果如下:


image.png

簡(jiǎn)單總結(jié):調(diào)用GlobalScope 啟動(dòng)協(xié)程運(yùn)行的線程名有3種情況

DefaultDispatcher-worker-x (使用Dispatchers.Default和Dispatchers.IO,GlobalScope不傳入 CoroutineDispatcher)
main(使用Dispatchers.Main,使用Dispatchers.Unconfined不調(diào)用delay(1000))
kotlinx.coroutines.DefaultExecutor(使用Dispatchers.Unconfined并調(diào)用delay(1000)后)

傳入Dispatchers.Main則運(yùn)行在main線程,傳入Dispatchers.Default和Dispatchers.IO則運(yùn)行在DefaultDispatcher-worker-x。傳入Dispatchers.Unconfined則是調(diào)用者的線程,但是當(dāng)在coroutine中第一個(gè)掛起之后,后面所在的線程將完全取決于調(diào)用掛起方法的線程。

要盡量避免使用GlobalScope,因?yàn)镚lobalScope是生命周期是process級(jí)別的,即使Activity或Fragment已經(jīng)被銷毀,協(xié)程仍然在執(zhí)行。

2.GlobalScope

// CoroutineScope 使用
private const val TAG = "CoroutineScope"
// Dispatchers.Main
fun coroutineScopeTest() = CoroutineScope(Dispatchers.Main).launch{
    // main
    Log.d(TAG, "inner coroutineScopeTest, 當(dāng)前線程 = ${Thread.currentThread().name}")
}

// Dispatchers.IO
fun coroutineScopeTest1() = CoroutineScope(Dispatchers.IO).launch{
    // DefaultDispatcher-worker-3
    Log.d(TAG, "inner coroutineScopeTest1, 當(dāng)前線程 = ${Thread.currentThread().name}")
}

// Dispatchers.Default
fun coroutineScopeTest2() = CoroutineScope(Dispatchers.Default).launch{
    // DefaultDispatcher-worker-1
    Log.d(TAG, "inner coroutineScopeTest2, 當(dāng)前線程 = ${Thread.currentThread().name}")
}

// Dispatchers.Unconfined
fun coroutineScopeTest3() = CoroutineScope(Dispatchers.Unconfined).launch{
    // main
    Log.d(TAG, "inner coroutineScopeTest3, 當(dāng)前線程 = ${Thread.currentThread().name}")
}

簡(jiǎn)單調(diào)用

class MainActivity : AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        coroutineScopeTest()
        coroutineScopeTest1()
        coroutineScopeTest2()
        coroutineScopeTest3()
    }
}

3.lifecycleScope

添加依賴

    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'

在MainActivity 中使用

private const val TAG = "MainActivity"
class MainActivity : AppCompatActivity(){
    fun lifecycleScopeTest() = lifecycleScope.launch {
        // main
        Log.d(TAG, "inner lifecycleScopeTest, 當(dāng)前線程 = ${Thread.currentThread().name}")
    }

    fun lifecycleScopeTest1() = lifecycleScope.launch(Dispatchers.IO) {
        // DefaultDispatcher-worker-2
        Log.d(TAG, "inner lifecycleScopeTest1, 當(dāng)前線程 = " + Thread.currentThread().name)
        // 通過(guò) withContext 來(lái)切換線程
        withContext(Dispatchers.Main) {
            // 當(dāng)前線程 = main
            Log.d(TAG, "inner withContext, 當(dāng)前線程 = ${Thread.currentThread().name}")
        }
    }

    // whenResumed和launchWhenResumed執(zhí)行時(shí)機(jī)一樣,在生命周期為 onResume 后調(diào)用
    // 區(qū)別在于:whenResumed 可以有返回結(jié)果 , launchWhenResumed 返回的是Job對(duì)象
    fun lifecycleScopeTest2() = lifecycleScope.launch {
        whenResumed {
            // main
            Log.d(TAG, "inner lifecycleScopeTest2 whenResumed, 當(dāng)前線程 = ${Thread.currentThread().name}")
        }
    }

    fun lifecycleScopeTest3() = lifecycleScope.launchWhenResumed  {
        // main
        Log.d(TAG, "inner lifecycleScopeTest3 launchWhenResumed, 當(dāng)前線程 = ${Thread.currentThread().name}")
    }


    override fun onResume() {
        super.onResume()
        Log.d(TAG, "inner onResume")
    }
}

4.viewModelScope

添加依賴

    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
private const val TAG = "ViewModelScopeTest"
class ViewModelScopeTest: ViewModel(){

    // 通過(guò)viewModelScope讓框架管理生命周期,無(wú)需執(zhí)行cancel
    /**
     * 沒(méi)法在主線程完成的繁重操作
     */
    fun launchDataLoad() {
        // viewModelScope 默認(rèn)使用 Dispatchers.Main
        viewModelScope.launch {
            // inner launch, 當(dāng)前線程 = main
            Log.d(TAG, "inner launch, 當(dāng)前線程 = ${Thread.currentThread().name}")
            sortList()
            // 當(dāng)前線程 = main  自動(dòng)切換線程
            Log.d(TAG, "inner launch after sortList, 當(dāng)前線程 = ${Thread.currentThread().name}")
            // 更新 UI
        }
    }

    suspend fun sortList() = withContext(Dispatchers.Default) {
        // 繁重任務(wù)
        // DefaultDispatcher-worker-3
        Log.d(TAG, "inner withContext, 當(dāng)前線程 = ${Thread.currentThread().name}")

    }
}

MainActivity中調(diào)用。

// MainActivity
    private val viewModelScopeTest by lazy {
        ViewModelProvider(this).get(ViewModelScopeTest::class.java)
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModelScopeTest.launchDataLoad()
    }

withContext 可以將當(dāng)前線程從主線程切換到后臺(tái)線程。然后執(zhí)行完畢再切換回來(lái)到ui線程執(zhí)行操作。

5.runBlocking

阻塞式不推薦使用,僅做了解。
另外除了launch,還有async可以啟動(dòng)協(xié)程,launch更常用。async可以返回閉包最后一行,并通過(guò)await()獲取。

        // runBlocking 阻塞式 不推薦
        runBlocking {
            // 當(dāng)前線程 = main
            Log.d(TAG, "inner runBlocking , 當(dāng)前線程 = ${Thread.currentThread().name}")
            launch(Dispatchers.IO) {
                // 當(dāng)前線程 = DefaultDispatcher-worker-1
                Log.d(TAG, "inner runBlocking launch , 當(dāng)前線程 = ${Thread.currentThread().name}")
            }

            /*
            * async 和 launch  和差不多,就是比 launch多了一個(gè)返回值。
            * 返回值 寫(xiě)在閉包 {}最后一行即可,然后通過(guò) await()獲取結(jié)果
            * */
            val job = async(Dispatchers.IO) {
                // 當(dāng)前線程 = DefaultDispatcher-worker-1
                Log.d(TAG, "inner runBlocking async , 當(dāng)前線程 = ${Thread.currentThread().name}")
                23
            }
            val await = job.await()
            // 當(dāng)前線程 = main , await = 23
            Log.d(TAG, "inner runBlocking , 當(dāng)前線程 = ${Thread.currentThread().name} , await = $await")
        }

image.png

最好是使用lifecycleScope和viewModelScope去啟動(dòng)協(xié)程。這2個(gè)擴(kuò)展庫(kù)可以更方便地處理生命周期相關(guān)的問(wèn)題。

二、協(xié)程取消

在寫(xiě)線程的時(shí)候,其實(shí)是沒(méi)有取消的,只有中斷。協(xié)程的取消api為:cancel()。
協(xié)程中有一個(gè)Job,是對(duì)一個(gè)協(xié)程的句柄。創(chuàng)建的每個(gè)協(xié)程,不管是通過(guò)launch還是async來(lái)啟動(dòng)的,它都會(huì)返回一個(gè)Job實(shí)例,唯一標(biāo)識(shí)該協(xié)程,并可以通過(guò)該Job管理其生命周期。

class MainActivity : AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        //Job和Dispatcher 組合成 CoroutineContext
        val scope = CoroutineScope(Job() + Dispatchers.Default)
        val job = scope.launch {
            Log.d(TAG, "inner CoroutineScope before delay, 當(dāng)前線程 = ${Thread.currentThread().name}")
            //通過(guò)延時(shí)模擬耗時(shí)任務(wù)
            delay(1000)
            Log.d(TAG, "inner CoroutineScope after delay, 當(dāng)前線程 = ${Thread.currentThread().name}")
        }
        job.cancel()
    }
}

kotlinx.coroutines 中的所有掛起函數(shù)(例如 withContext 和 delay)都是可取消的。
由于調(diào)用job.cancel()取消,所以不執(zhí)行after delay這條log。


image.png

三、異常處理
協(xié)程的異常處理,即使加了try-catch也會(huì)有問(wèn)題,應(yīng)用可能直接崩潰,而且完全無(wú)法捕獲異常信息。

換句話說(shuō):如果把Java里的那一套異常處理機(jī)制,照搬到Kotlin協(xié)程里來(lái),你一定會(huì)四處碰壁。因?yàn)樵谄胀ǖ某绦虍?dāng)中,你使用try-catch就能解決大部分的異常處理問(wèn)題,但是在協(xié)程當(dāng)中,根據(jù)不同的協(xié)程特性,它的異常處理策略是隨之變化的。
要注意以下幾點(diǎn):

  • 1.協(xié)程的取消需要內(nèi)部的配合。
  • 2.不要輕易打破協(xié)程的父子結(jié)構(gòu)!這一點(diǎn),其實(shí)不僅僅只是針對(duì)協(xié)程的取消異常,而是要貫穿于整個(gè)協(xié)程的使用過(guò)程中。我們知道,協(xié)程的優(yōu)勢(shì)在于結(jié)構(gòu)化并發(fā),它的許多特性都是建立在這個(gè)特性之上的,如果我們無(wú)意中打破了它的父子結(jié)構(gòu),就會(huì)導(dǎo)致協(xié)程無(wú)法按照預(yù)期執(zhí)行。
  • 3.捕獲了CancellationException以后,要考慮是否應(yīng)該重新拋出來(lái)。在協(xié)程體內(nèi)部,協(xié)程是依賴于CancellationException來(lái)實(shí)現(xiàn)結(jié)構(gòu)化取消的,有的時(shí)候我們出于某些目的需要捕獲CancellationException,但捕獲完以后,我們還需要思考是否需要將其重新拋出來(lái)。
  • 4.不要用try-catch直接包裹launch、async。這一點(diǎn)是很多初學(xué)者會(huì)犯的錯(cuò)誤,考慮到協(xié)程代碼的執(zhí)行順序與普通程序不一樣,我們直接使用try-catch包裹launch、async,是不會(huì)有任何效果的。
  • 5.靈活使用SupervisorJob,控制異常傳播的范圍。SupervisorJob是一種特殊的Job,它可以控制異常的傳播范圍。普通的Job,它會(huì)因?yàn)樽訁f(xié)程當(dāng)中的異常而取消自身,而SupervisorJob則不會(huì)受到子協(xié)程異常的影響。在很多業(yè)務(wù)場(chǎng)景下,我們都不希望子協(xié)程影響到父協(xié)程,所以SupervisorJob的應(yīng)用范圍也非常廣。比如說(shuō)Android當(dāng)中的viewModelScope,它就使用了SupervisorJob,這樣一來(lái),我們的App就不會(huì)因?yàn)槟硞€(gè)子協(xié)程的異常導(dǎo)致整個(gè)應(yīng)用的功能出現(xiàn)紊亂。
  • 6.使用CoroutineExceptionHandler處理復(fù)雜結(jié)構(gòu)的協(xié)程異常,它僅在頂層協(xié)程中起作用。我們都知道,傳統(tǒng)的try-catch在協(xié)程當(dāng)中并不能解決所有問(wèn)題,尤其是在協(xié)程嵌套層級(jí)較深的情況下。這時(shí)候,Kotlin官方為我們提供了CoroutineExceptionHandler作為補(bǔ)充。有了它,我們可以輕松捕獲整個(gè)作用域內(nèi)的所有異常。
    image.png

    更多具體部分,可參考《Kotlin編程第一課》

未處理協(xié)程中拋出的異??赡軙?huì)導(dǎo)致應(yīng)用崩潰。如果可能會(huì)發(fā)生異常,請(qǐng)?jiān)谑褂?viewModelScope 或 lifecycleScope 創(chuàng)建的任何協(xié)程主體中捕獲相應(yīng)異常。

https://developer.android.com/kotlin/coroutines/coroutines-best-practices?hl=zh-cn
未處理協(xié)程中拋出的異??赡軙?huì)導(dǎo)致應(yīng)用崩潰。如果可能會(huì)發(fā)生異常,請(qǐng)?jiān)谑褂?viewModelScope 或 lifecycleScope 創(chuàng)建的任何協(xié)程主體中捕獲相應(yīng)異常。
注意:如需啟用協(xié)程取消流程,請(qǐng)不要使用 CancellationException 類型的異常(不要捕獲它們,或在被發(fā)現(xiàn)時(shí)總是重新拋出)。首選捕獲特定類型的異常(如 IOException),而不是 Exception 或 Throwable 等一般類型。

class LoginViewModel(
    private val loginRepository: LoginRepository
) : ViewModel() {

    fun login(username: String, token: String) {
        viewModelScope.launch {
            try {
                loginRepository.login(username, token)
                // Notify view user logged in successfully
            } catch (exception: IOException) {
                // Notify view login attempt failed
            }
        }
    }
}

總而言之,還是要具體情況具體分析。

另外,官方使用demo如下:
在 Android 應(yīng)用中使用 Kotlin 協(xié)程
學(xué)習(xí)采用 Kotlin Flow 和 LiveData 的高級(jí)協(xié)程

參考鏈接:
【Android】在Activity中使用LifecycleScope替代GlobalScope
lifecycleScope 和viewModelScope
ViewModel中的簡(jiǎn)易協(xié)程:viewModelScope
破解 Kotlin 協(xié)程(2):協(xié)程啟動(dòng)篇
kotlin學(xué)習(xí)-Coroutines(協(xié)程)
Kotlin之協(xié)程coroutine使用(1)
Kotlin 協(xié)程一 —— 協(xié)程 Coroutine
協(xié)程的取消和異常Part1-核心概念
在 Android 中使用協(xié)程的最佳實(shí)踐
《Kotlin編程第一課》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容