初識Kotlin協(xié)程

協(xié)程可以讓我們使用順序的方式去寫異步代碼,而且不會阻塞UI線程。
Kotlin 協(xié)程提供了一種全新處理并發(fā)的方式,你可以在 Android 平臺上使用它來簡化異步執(zhí)行的代碼。協(xié)程從 Kotlin 1.3 版本開始引入,但這一概念在編程世界誕生的黎明之際就有了,最早使用協(xié)程的編程語言可以追溯到 1967 年的 Simula 語言。在過去幾年間,協(xié)程這個(gè)概念發(fā)展勢頭迅猛,現(xiàn)已經(jīng)被諸多主流編程語言采用,比如 Javascript、C#、Python、Ruby 以及 Go 等。Kotlin 協(xié)程是基于來自其他語言的既定概念。
Google 官方推薦將 Kotlin 協(xié)程作為在 Android 上進(jìn)行異步編程的解決方案。

配置

dependencies {
    // lifecycle對于協(xié)程的擴(kuò)展封裝
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
    // 協(xié)程核心庫
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
    // 協(xié)程Android支持庫
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"
}

協(xié)程作用域CoroutineScope

CoroutineScope即協(xié)程作用域,用于對協(xié)程進(jìn)行追蹤。如果我們啟動(dòng)了多個(gè)協(xié)程但是沒有一個(gè)可以對其進(jìn)行統(tǒng)一管理的途徑的話,就會導(dǎo)致我們的代碼臃腫雜亂,甚至發(fā)生內(nèi)存泄露或者任務(wù)泄露。為了確保所有的協(xié)程都會被追蹤,Kotlin 不允許在沒有CoroutineScope的情況下啟動(dòng)協(xié)程。
CoroutineScope 會跟蹤所有協(xié)程,同樣它還可以取消由它所啟動(dòng)的所有協(xié)程。這在 Android 開發(fā)中非常有用,比如它能夠在用戶離開界面時(shí)停止執(zhí)行協(xié)程。
Coroutine 是輕量級的線程,并不意味著就不消耗系統(tǒng)資源。 當(dāng)異步操作比較耗時(shí)的時(shí)候,或者當(dāng)異步操作出現(xiàn)錯(cuò)誤的時(shí)候,需要把這個(gè) Coroutine 取消掉來釋放系統(tǒng)資源。在 Android 環(huán)境中,通常每個(gè)界面(Activity、Fragment 等)啟動(dòng)的 Coroutine 只在該界面有意義,如果用戶在等待 Coroutine 執(zhí)行的時(shí)候退出了這個(gè)界面,則再繼續(xù)執(zhí)行這個(gè) Coroutine 可能是沒必要的。另外 Coroutine 也需要在適當(dāng)?shù)?context 中執(zhí)行,否則會出現(xiàn)錯(cuò)誤,比如在非 UI 線程去訪問 View。 所以 Coroutine 在設(shè)計(jì)的時(shí)候,要求在一個(gè)范圍(Scope)內(nèi)執(zhí)行,這樣當(dāng)這個(gè) Scope 取消的時(shí)候,里面所有的子 Coroutine 也自動(dòng)取消。所以要使用 Coroutine 必須要先創(chuàng)建一個(gè)對應(yīng)的 CoroutineScope。

1、GlobalScope - 全局協(xié)程作用域

public object GlobalScope : CoroutineScope {
    /**
     * Returns [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

GlobalScope是一個(gè)單例實(shí)現(xiàn),包含一個(gè)空的CoroutineContext,且不包含任何Job,該作用域常被拿來做示例代碼。
GlobalScope 屬于全局作用域,這意味著通過 GlobalScope 啟動(dòng)的協(xié)程的生命周期只受整個(gè)應(yīng)用程序的生命周期的限制,只要整個(gè)應(yīng)用程序還在運(yùn)行且協(xié)程的任務(wù)還未結(jié)束,協(xié)程就可以一直運(yùn)行,這可能會造成內(nèi)存泄漏,因此不推薦使用。

2、MainScope - 主線程協(xié)程作用域

/**
 * Creates the main [CoroutineScope] for UI components.
 *
 * Example of use:
 * ```
 * class MyAndroidActivity {
 *     private val scope = MainScope()
 *
 *     override fun onDestroy() {
 *         super.onDestroy()
 *         scope.cancel()
 *     }
 * }
 * ```
 *
 * The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
 * If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
 * `val scope = MainScope() + CoroutineName("MyActivity")`.
 */
@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

用于返回一個(gè)CoroutineContext為SupervisorJob() + Dispatchers.Main的作用域,默認(rèn)的調(diào)度模式為Main,也就是說該協(xié)程的線程環(huán)境是主線程。
該作用域常被使用在Activity/Fragment,并且在界面銷毀時(shí)onDestroy()中要調(diào)用CoroutineScope.cancel()方法對協(xié)程進(jìn)行取消,這是可以在開發(fā)中使用的一個(gè)用于獲取作用域的頂層函數(shù)。

3、runBlocking

這是一個(gè)頂層函數(shù),運(yùn)行一個(gè)新的協(xié)程并且阻塞當(dāng)前可中斷的線程直至協(xié)程執(zhí)行完成,該函數(shù)被設(shè)計(jì)用于橋接普通阻塞代碼到以掛起風(fēng)格(suspending style)編寫的庫,以用于主函數(shù)與測試。該函數(shù)主要用于測試,不適用于日常開發(fā),該協(xié)程會阻塞當(dāng)前線程直到協(xié)程體執(zhí)行完成。

4、LifecycleOwner.lifecycleScope

/**
 * [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
 *
 * This scope will be cancelled when the [Lifecycle] is destroyed.
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
 */
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

SupervisorJob() + Dispatchers.Main.immediate
該擴(kuò)展屬性是 Android 的Lifecycle Ktx庫提供的具有生命周期感知的協(xié)程作用域,用于返回一個(gè)CoroutineContext為SupervisorJob() + Dispatchers.Main.immediate的作用域,它與LifecycleOwner的Lifecycle綁定,Lifecycle被銷毀時(shí),此作用域?qū)⒈蝗∠?。這是在Activity/Fragment中推薦使用的作用域,因?yàn)樗鼤c當(dāng)前的UI組件綁定生命周期,界面銷毀時(shí)該協(xié)程作用域?qū)⒈蝗∠?,不會造成協(xié)程泄漏。

5、ViewModel.viewModelScope

/**
 * [CoroutineScope] tied to this [ViewModel].
 * This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
 */
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

該擴(kuò)展屬性是ViewModel的擴(kuò)展屬性,和LifecycleOwner.lifecycleScope基本一致,也是返回一個(gè)CoroutineContext為SupervisorJob() + Dispatchers.Main.immediate的作用域,它能夠在此ViewModel銷毀時(shí)自動(dòng)取消,同樣不會造成協(xié)程泄漏。

6、使用 coroutineScope() 和 supervisorScope() 創(chuàng)建子作用域

這兩個(gè)函數(shù)都是掛起函數(shù),需要運(yùn)行在協(xié)程內(nèi)或掛起函數(shù)內(nèi)。
coroutineScope和supervisorScope都會返回一個(gè)作用域,它倆的差別就是異常傳播:coroutineScope 內(nèi)部的異常會向上傳播,子協(xié)程未捕獲的異常會向上傳遞給父協(xié)程,任何一個(gè)子協(xié)程異常退出,會導(dǎo)致整體的退出;supervisorScope 內(nèi)部的異常不會向上傳播,一個(gè)子協(xié)程異常退出,不會影響父協(xié)程和兄弟協(xié)程的運(yùn)行。
因此supervisorScope的設(shè)計(jì)應(yīng)用場景多用于子協(xié)程為獨(dú)立對等的任務(wù)實(shí)體的時(shí)候,比如一個(gè)下載器,每一個(gè)子協(xié)程都是一個(gè)下載任務(wù),當(dāng)一個(gè)下載任務(wù)異常時(shí),它不應(yīng)該影響其他的下載任務(wù)。

總結(jié)一下,關(guān)于作用域,更推薦的是在UI組件中使用LifecycleOwner.lifecycleScope,在ViewModel中使用ViewModel.viewModelScope


創(chuàng)建協(xié)程

常用的創(chuàng)建協(xié)程方式有兩種:CoroutineScope.launch()和CoroutineScope.async()。
兩者的區(qū)別在于返回值:launch創(chuàng)建新協(xié)程,但不將結(jié)果返回,返回一個(gè)Job,用于協(xié)程的監(jiān)督與取消,適用于無返回值的場景。async創(chuàng)建新協(xié)程,返回一個(gè)Job的子類Deferred,可通過await()獲取完成時(shí)返回值。async的返回值功能,需要與await()掛起函數(shù)結(jié)合使用。
此外兩者之間的很大差異是它們對異常的處理方式不同:如果使用 async 作為最外層協(xié)程的開啟方式,它期望最終是通過調(diào)用 await 來獲取結(jié)果 (或者異常),所以默認(rèn)情況下它不會拋出異常。這意味著如果使用 async 啟動(dòng)新的最外層協(xié)程,而不使用await,它會靜默地將異常丟棄。

CoroutineScope.launch()

簡單示例:

//MainScope()獲取一個(gè)協(xié)程作用域用于創(chuàng)建協(xié)程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 創(chuàng)建一個(gè)默認(rèn)參數(shù)的協(xié)程,其默認(rèn)的調(diào)度模式為Main 也就是說該協(xié)程的線程環(huán)境是Main線程
        mScope.launch {
            // 這里就是協(xié)程體
            // 延遲1000毫秒  delay是一個(gè)掛起函數(shù)
            // 在這1000毫秒內(nèi)該協(xié)程所處的線程不會阻塞
            // 協(xié)程將線程的執(zhí)行權(quán)交出去,該線程該干嘛干嘛,到時(shí)間后會恢復(fù)至此繼續(xù)向下執(zhí)行
            Log.d(TAG, "launch開啟第一個(gè)協(xié)程")
            delay(1000)
            Log.d(TAG, "launch開啟第一個(gè)協(xié)程結(jié)束")
        }
        // 創(chuàng)建一個(gè)指定了調(diào)度模式的協(xié)程,該協(xié)程的運(yùn)行線程為IO線程
        mScope.launch(Dispatchers.IO) {
            // 此處是IO線程模式
            Log.d(TAG, "launch開啟第二個(gè)協(xié)程")
            delay(1000)
            //將協(xié)程所處的線程環(huán)境切換至指定的調(diào)度模式Main 
            //和launch、async及runBlocking不同,withContext不會創(chuàng)建新的協(xié)程,常用于切換代碼執(zhí)行所運(yùn)行的線程。 
            //它也是一個(gè)掛起方法,直到結(jié)束返回結(jié)果。多個(gè)withContext是串行執(zhí)行的, 所以很適合那種一個(gè)任務(wù)依賴上一個(gè)任務(wù)返回結(jié)果的情況
            withContext(Dispatchers.Main) {
                // 現(xiàn)在這里就是Main線程了  可以在此進(jìn)行UI操作了
                Log.d(TAG, "切換至主線程")
            }
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消協(xié)程 防止協(xié)程泄漏  如果lifecycleScope或者viewModelScope則不需要手動(dòng)取消
        mScope.cancel()
    }

CoroutineScope.async()

展示async的返回值功能,需要與await()掛起函數(shù)結(jié)合使用:

//MainScope()獲取一個(gè)協(xié)程作用域用于創(chuàng)建協(xié)程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch {
            // 開啟一個(gè)IO模式的線程,并返回一個(gè)Deferred,Deferred可以用來獲取返回值
            // 代碼執(zhí)行到此處時(shí)會新開一個(gè)協(xié)程 然后去執(zhí)行協(xié)程體,父協(xié)程的代碼會接著往下走
            val deferred = async(Dispatchers.IO) {
                // 模擬耗時(shí)
                delay(2000)
                // 返回一個(gè)值
                return@async "獲得返回值"
            }
            // 等待async執(zhí)行完成獲取返回值,此處并不會阻塞線程,而是掛起,將線程的執(zhí)行權(quán)交出去
            // 等到async的協(xié)程體執(zhí)行完畢后,會恢復(fù)協(xié)程繼續(xù)往下執(zhí)行
            val data = deferred.await()
            Log.d(TAG, "data:$data")
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消協(xié)程 防止協(xié)程泄漏  如果lifecycleScope或者viewModelScope則不需要手動(dòng)取消
        mScope.cancel()
    }

展示async的并發(fā)能力:

//MainScope()獲取一個(gè)協(xié)程作用域用于創(chuàng)建協(xié)程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch {
            // 此處有一個(gè)需求  同時(shí)請求5個(gè)接口  并且將返回值拼接起來
            val job1 = async {
                // 請求1
                delay(5000)
                return@async "1"
            }
            val job2 = async {
                // 請求2
                delay(5000)
                return@async "2"
            }
            val job3 = async {
                // 請求3
                delay(5000)
                return@async "3"
            }
            val job4 = async {
                // 請求4
                delay(5000)
                return@async "4"
            }
            val job5 = async {
                // 請求5
                delay(5000)
                return@async "5"
            }
            // 代碼執(zhí)行到此處時(shí),5個(gè)請求已經(jīng)同時(shí)在執(zhí)行了
            // 等待各job執(zhí)行完 將結(jié)果合并
            Log.d(TAG, "async: ${job1.await()} ${job2.await()} ${job3.await()} ${job4.await()} ${job5.await()}"
            )
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消協(xié)程 防止協(xié)程泄漏  如果lifecycleScope或者viewModelScope則不需要手動(dòng)取消
        mScope.cancel()
    }

suspend 掛起函數(shù)

Kotlin協(xié)程提供了 suspend 關(guān)鍵字,用于定義一個(gè)掛起函數(shù),它就是一個(gè)標(biāo)記,告知編譯器,這個(gè)函數(shù)需在協(xié)程中執(zhí)行,編譯器會將掛起函數(shù)用有限狀態(tài)機(jī)轉(zhuǎn)換為一種優(yōu)化版的回調(diào)。
掛起函數(shù)掛起協(xié)程的時(shí)候,并不會阻塞線程。
一個(gè) suspend function 只能在一個(gè)協(xié)程或一個(gè) suspend function 中使用。


CoroutineContext

CoroutineContext是Kotlin協(xié)程的一個(gè)基本結(jié)構(gòu)單元。巧妙的運(yùn)用協(xié)程上下文是至關(guān)重要的,以此來實(shí)現(xiàn)正確的線程行為、生命周期、異常以及調(diào)試。由如下幾項(xiàng)構(gòu)成:

  • Job: 控制協(xié)程的生命周期;
  • CoroutineDispatcher: 向合適的線程分發(fā)任務(wù);
  • CoroutineName: 協(xié)程的名稱,調(diào)試的時(shí)候很有用;
  • CoroutineExceptionHandler: 處理未被捕捉的異常。
    CoroutineContext 有兩個(gè)非常重要的元素:Job 和 Dispatcher,Job 是當(dāng)前的 Coroutine 實(shí)例而 Dispatcher 決定了當(dāng)前 Coroutine 執(zhí)行的線程,還可以添加CoroutineName,用于調(diào)試,添加 CoroutineExceptionHandler 用于捕獲異常。
    簡單示例:
val coroutineContext = Job() + Dispatchers.Default + CoroutineName("協(xié)程名字")
Log.d(TAG, "coroutineContext:$coroutineContext")

日志輸出結(jié)果為:

coroutineContext:[JobImpl{Active}@1f12c95, CoroutineName(協(xié)程名字), Dispatchers.Default]

Job

Job 用于處理協(xié)程。對于每一個(gè)所創(chuàng)建的協(xié)程 (通過 launch 或者 async),它會返回一個(gè) Job實(shí)例,該實(shí)例是協(xié)程的唯一標(biāo)識,并且負(fù)責(zé)管理協(xié)程的生命周期。
CoroutineScope.launch 函數(shù)返回的是一個(gè) Job 對象,代表一個(gè)異步的任務(wù)。Job 具有生命周期并且可以取消。 Job 還可以有層級關(guān)系,一個(gè)Job可以包含多個(gè)子Job,當(dāng)父Job被取消后,所有的子Job也會被自動(dòng)取消;當(dāng)子Job被取消或者出現(xiàn)異常后父Job也會被取消。
除了通過 CoroutineScope.launch 來創(chuàng)建Job對象之外,還可以通過 Job() 工廠方法來創(chuàng)建該對象。默認(rèn)情況下,子Job的失敗將會導(dǎo)致父Job被取消,這種默認(rèn)的行為可以通過 SupervisorJob 來修改。
具有多個(gè)子 Job 的父Job 會等待所有子Job完成(或者取消)后,自己才會執(zhí)行完成。

Job生命周期

Job生命周期包括New(新創(chuàng)建)、Active(活躍)、Completing(完成中)、 Completed(已完成)、Cancelling(取消中)、Cancelled(已取消)。雖然我們無法直接訪問這些狀態(tài),但是我們可以訪問 Job 的屬性: isActive、isCancelled 和 isCompleted。

Job常用API

isActive: Boolean    //是否存活
isCancelled: Boolean //是否取消
isCompleted: Boolean //是否完成
children: Sequence<Job> // 所有子Job
cancel()             // 取消協(xié)程
join()               // 堵塞當(dāng)前線程直到協(xié)程執(zhí)行完畢
cancelAndJoin()      // 兩者結(jié)合,取消并等待協(xié)程完成
cancelChildren()     // 取消所有子協(xié)程,可傳入CancellationException作為取消原因
attachChild(child: ChildJob) // 附加一個(gè)子協(xié)程到當(dāng)前協(xié)程上
invokeOnCompletion(handler: CompletionHandler): DisposableHandle //給 Job 設(shè)置一個(gè)完成通知,當(dāng) Job 執(zhí)行完成的時(shí)候會同步執(zhí)行這個(gè)通知函數(shù)

Deferred

通過使用async創(chuàng)建協(xié)程可以得到一個(gè)有返回值Deferred,Deferred 接口繼承自 Job 接口,額外提供了獲取 Coroutine 返回結(jié)果的方法。由于 Deferred 繼承自 Job 接口,所以 Job 相關(guān)的內(nèi)容在 Deferred 上也是適用的。 Deferred 提供了額外三個(gè)函數(shù)來處理和Coroutine執(zhí)行結(jié)果相關(guān)的操作。

await() //用來等待這個(gè)Coroutine執(zhí)行完畢并返回結(jié)果
getCompleted() //用來獲取Coroutine執(zhí)行的結(jié)果。如果Coroutine還沒有執(zhí)行完成則會拋出 IllegalStateException ,如果任務(wù)被取消了也會拋出對應(yīng)的異常。所以在執(zhí)行這個(gè)函數(shù)之前,可以通過 isCompleted 來判斷一下當(dāng)前任務(wù)是否執(zhí)行完畢了。
getCompletionExceptionOrNull() //獲取已完成狀態(tài)的Coroutine異常信息,如果任務(wù)正常執(zhí)行完成了,則不存在異常信息,返回null。如果還沒有處于已完成狀態(tài),則調(diào)用該函數(shù)同樣會拋出 IllegalStateException,可以通過 isCompleted 來判斷一下當(dāng)前任務(wù)是否執(zhí)行完畢了。

SupervisorJob

/**
 * Creates a _supervisor_ job object in an active state.
 * Children of a supervisor job can fail independently of each other.
 * 
 * A failure or cancellation of a child does not cause the supervisor job to fail and does not affect its other children,
 * so a supervisor can implement a custom policy for handling failures of its children:
 *
 * * A failure of a child job that was created using [launch][CoroutineScope.launch] can be handled via [CoroutineExceptionHandler] in the context.
 * * A failure of a child job that was created using [async][CoroutineScope.async] can be handled via [Deferred.await] on the resulting deferred value.
 *
 * If [parent] job is specified, then this supervisor job becomes a child job of its parent and is cancelled when its
 * parent fails or is cancelled. All this supervisor's children are cancelled in this case, too. The invocation of
 * [cancel][Job.cancel] with exception (other than [CancellationException]) on this supervisor job also cancels parent.
 *
 * @param parent an optional parent job.
 */
@Suppress("FunctionName")
public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)

Job 是有父子關(guān)系的,如果子Job 失敗了父Job會自動(dòng)失敗,這種默認(rèn)的行為可能不是我們期望的。
而SupervisorJob就是這么一個(gè)特殊的 Job,里面的子Job不相互影響,一個(gè)子Job失敗了,不影響其他子Job的執(zhí)行。SupervisorJob(parent:Job?) 具有一個(gè)parent參數(shù),如果指定了這個(gè)參數(shù),則所返回的 Job 就是參數(shù) parent 的子Job。如果 Parent Job 失敗了或者取消了,則這個(gè) Supervisor Job 也會被取消。當(dāng) Supervisor Job 被取消后,所有 Supervisor Job 的子Job也會被取消。
MainScope() 的實(shí)現(xiàn)就使用了 SupervisorJob 和一個(gè) Main Dispatcher:

/**
 * Creates the main [CoroutineScope] for UI components.
 *
 * Example of use:
 * ```
 * class MyAndroidActivity {
 *     private val scope = MainScope()
 *
 *     override fun onDestroy() {
 *         super.onDestroy()
 *         scope.cancel()
 *     }
 * }
 * ```
 *
 * The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
 * If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
 * `val scope = MainScope() + CoroutineName("MyActivity")`.
 */
@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

協(xié)程異常處理

對異常的常規(guī)處理是使用try-catch,然而協(xié)程作用域內(nèi)的異常無法使用try-catch取捕獲,這個(gè)時(shí)候就需要使用CoroutineExceptionHandler來捕獲協(xié)程作用域內(nèi)的異常。
簡單示例:

//MainScope()獲取一個(gè)協(xié)程作用域用于創(chuàng)建協(xié)程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch(Dispatchers.Default) {
            delay(500)
            Log.e(TAG, "Child 1")
        }
        // 在Child 2添加了異常處理
        mScope.launch(Dispatchers.Default + CoroutineExceptionHandler { coroutineContext, throwable ->
            Log.e(TAG, "CoroutineExceptionHandler: $throwable")
        }) {
            delay(1000)
            Log.e(TAG, "Child 2")
            throw RuntimeException("--> RuntimeException <--")
        }
        mScope.launch(Dispatchers.Default) {
            delay(1500)
            Log.e(TAG, "Child 3")
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消協(xié)程 防止協(xié)程泄漏  如果lifecycleScope或者viewModelScope則不需要手動(dòng)取消
        mScope.cancel()
    }

日志輸出結(jié)果:

Child 1
Child 2
CoroutineExceptionHandler: java.lang.RuntimeException: --> RuntimeException <--
Child 3

在第二個(gè)協(xié)程中拋出的異常,使用CoroutineExceptionHandler就能捕獲到異常了。
普通協(xié)程如果產(chǎn)生未處理異常會將此異常傳播至它的父協(xié)程,然后父協(xié)程會取消所有的子協(xié)程、取消自己、將異常繼續(xù)向上傳遞。
MainScope()因?yàn)樗膶?shí)現(xiàn)是用了SupervisorJob,所以執(zhí)行結(jié)果就是Child 2拋出異常后,但是不影響其他的Job,Child 3也就正常執(zhí)行了。
使用supervisorScope也能達(dá)到類似SupervisorJob的效果,一個(gè)子協(xié)程異常退出,不會影響父協(xié)程和兄弟協(xié)程的運(yùn)行。
簡單示例:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val scope = CoroutineScope(Job() + Dispatchers.Default)
        //如果將supervisorScope換成coroutineScope,結(jié)果就不是這樣了,Child 3就沒有了
        scope.launch(CoroutineExceptionHandler { coroutineContext, throwable ->
            Log.e(TAG, "CoroutineExceptionHandler: $throwable")
        }) {
            supervisorScope {
                launch {
                    delay(500)
                    Log.e(TAG, "Child 1 ")
                }
                launch {
                    delay(1000)
                    Log.e(TAG, "Child 2 ")
                    throw  RuntimeException("--> RuntimeException <--")
                }
                launch {
                    delay(1500)
                    Log.e(TAG, "Child 3 ")
                }
            }
        }
    }

日志輸出結(jié)果:

Child 1
Child 2
CoroutineExceptionHandler: java.lang.RuntimeException: --> RuntimeException <--
Child 3

如果將supervisorScope換成coroutineScope,結(jié)果就不是這樣了,Child 3就沒有了:
日志輸出結(jié)果:

Child 1
Child 2
CoroutineExceptionHandler: java.lang.RuntimeException: --> RuntimeException <--

CoroutineDispatcher

CoroutineDispatcher,調(diào)度器,定義了 Coroutine 執(zhí)行的線程。CoroutineDispatcher 可以限定 Coroutine 在某一個(gè)線程執(zhí)行、也可以分配到一個(gè)線程池來執(zhí)行、也可以不限制其執(zhí)行的線程。
CoroutineDispatcher 是一個(gè)抽象類,所有 dispatcher 都應(yīng)該繼承這個(gè)類來實(shí)現(xiàn)對應(yīng)的功能。Dispatchers 是一個(gè)標(biāo)準(zhǔn)庫中幫我們封裝了切換線程的幫助類,可以簡單理解為一個(gè)線程池。
Kotlin協(xié)程預(yù)置4種調(diào)度器:

  • Dispatchers.Default:默認(rèn)的調(diào)度器,適合處理后臺計(jì)算,是一個(gè)CPU密集型任務(wù)調(diào)度器。如果創(chuàng)建 Coroutine 的時(shí)候沒有指定 dispatcher,則一般默認(rèn)使用這個(gè)作為默認(rèn)值。適合在主線程之外執(zhí)行占用大量 CPU 資源的工作,示例包括對列表排序和解析 JSON。
    Default dispatcher 使用一個(gè)共享的后臺線程池來運(yùn)行里面的任務(wù)。注意它和Dispatchers.IO共享線程池,只不過限制了最大并發(fā)數(shù)不同。
  • Dispatchers.IO:用來執(zhí)行阻塞 IO 操作的,適合執(zhí)行IO相關(guān)操作,是一個(gè)IO密集型任務(wù)調(diào)度器。適合在主線程之外執(zhí)行磁盤或網(wǎng)絡(luò) I/O,示例包括使用Room組件、從文件中讀取數(shù)據(jù)或向文件中寫入數(shù)據(jù),以及運(yùn)行任何網(wǎng)絡(luò)操作。
    和Dispatchers.Default共用一個(gè)線程池來執(zhí)行里面的任務(wù)。根據(jù)同時(shí)運(yùn)行的任務(wù)數(shù)量,在需要的時(shí)候會創(chuàng)建額外的線程,當(dāng)任務(wù)執(zhí)行完畢后會釋放不需要的線程。
  • Dispatchers.Main:根據(jù)平臺不同會初始化為對應(yīng)UI線程的調(diào)度器,如Android的主線程。此調(diào)度程序只能用于與界面交互和執(zhí)行快速工作。示例包括調(diào)用 suspend 函數(shù),運(yùn)行 Android 界面框架操作,以及更新LiveData對象。
  • Dispatchers.Unconfined:不指定線程,所以執(zhí)行的時(shí)候默認(rèn)在啟動(dòng)線程。如果子協(xié)程切換線程,接下來的代碼也在該線程繼續(xù)執(zhí)行。
    由于子協(xié)程會繼承父協(xié)程的 context,所以為了方便使用,一般會在父協(xié)程上設(shè)定一個(gè)Dispatcher,然后所有子協(xié)程自動(dòng)使用這個(gè)Dispatcher。

withContext

和launch、async及runBlocking不同,withContext不會創(chuàng)建新的協(xié)程,常用于切換協(xié)程執(zhí)行所運(yùn)行的線程。它也是一個(gè)掛起方法,直到結(jié)束返回結(jié)果。多個(gè)withContext是串行執(zhí)行的, 所以很適合那種一個(gè)任務(wù)依賴上一個(gè)任務(wù)返回結(jié)果的情況。


ContinuationInterceptor

ContinuationInterceptor,攔截器,用來攔截協(xié)程做一些附加操作的,比如CoroutineDispatcher調(diào)度器,就是用攔截器實(shí)現(xiàn)的。
簡單示例:打印日志的攔截器

//MainScope()獲取一個(gè)協(xié)程作用域用于創(chuàng)建協(xié)程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch(Dispatchers.IO) {
            launch(MyInterceptor()) {
                Log.d(TAG, "1")
                val deferred = async {
                    Log.d(TAG, "2")
                    delay(100)
                    Log.d(TAG, "3")
                    return@async "返回值"
                }
                Log.d(TAG, "4")
                val result = deferred.await()
                Log.d(TAG, "5:$result")
            }.join()
            Log.d(TAG, "6")
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消協(xié)程 防止協(xié)程泄漏  如果lifecycleScope或者viewModelScope則不需要手動(dòng)取消
        mScope.cancel()
    }

class MyInterceptor : ContinuationInterceptor {
        override val key: CoroutineContext.Key<*>
            get() = ContinuationInterceptor

        override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
            return MyContinuation(continuation)
        }
    }

class MyContinuation<T>(val continuation: Continuation<T>) : Continuation<T> {
        override val context: CoroutineContext
            get() = continuation.context

        override fun resumeWith(result: Result<T>) {
            Log.d(TAG, "result:$result")
            continuation.resumeWith(result)
        }
    }

日志輸出結(jié)果:

result:Success(kotlin.Unit)
1
result:Success(kotlin.Unit)
2
4
result:Success(kotlin.Unit)
3
result:Success(返回值)
5:返回值
6

發(fā)生了4次攔截,依次是:協(xié)程啟動(dòng)時(shí)(前兩個(gè)),掛起時(shí),返回結(jié)果時(shí)。
簡單示例:線程調(diào)度器

//MainScope()獲取一個(gè)協(xié)程作用域用于創(chuàng)建協(xié)程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch {
            Log.d(TAG, Thread.currentThread().name)
            withContext(MyInterceptor2()) {
                Log.d(TAG, Thread.currentThread().name)
                delay(100)
                Log.d(TAG, Thread.currentThread().name)
            }
            Log.d(TAG, Thread.currentThread().name)
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消協(xié)程 防止協(xié)程泄漏  如果lifecycleScope或者viewModelScope則不需要手動(dòng)取消
        mScope.cancel()
    }

class MyInterceptor : ContinuationInterceptor {
        override val key: CoroutineContext.Key<*>
            get() = ContinuationInterceptor

        override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
            return MyContinuation(continuation)
        }
    }

class MyContinuation<T>(val continuation: Continuation<T>) : Continuation<T> {
        override val context: CoroutineContext
            get() = continuation.context

        override fun resumeWith(result: Result<T>) {
            Log.d(TAG, "result:$result")
            continuation.resumeWith(result)
        }
    }

日志輸出結(jié)果:

main
result:Success(kotlin.Unit)
我的線程池
result:Success(kotlin.Unit)
我的線程池
main

參考文章:
萬字長文 - Kotlin 協(xié)程進(jìn)階
Kotlin協(xié)程應(yīng)用實(shí)戰(zhàn)(硬核分享,一看就會)

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

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

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