日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Android >内容正文

Android

在 Android 上使用协程(二):Getting started

發(fā)布時(shí)間:2024/4/14 Android 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 在 Android 上使用协程(二):Getting started 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文作者 :Sean McQuillan

原文地址: Coroutines on Android (part II): Getting started

譯者 : 秉心說

這是關(guān)于在 Android 中使用協(xié)程的一系列文章。本篇的重點(diǎn)是開始任務(wù)以及追蹤已經(jīng)開始的任務(wù)。

上一篇 :

在 Android 上使用協(xié)程(一):Getting The Background

協(xié)程解決了什么問題?

追蹤協(xié)程

在上篇文章中,我們探索了協(xié)程擅長解決的問題。通常,協(xié)程對于下面兩個(gè)常見的編程問題來說都是不錯(cuò)的解決方案:

  • 耗時(shí)任務(wù),運(yùn)行時(shí)間過長阻塞主線程
  • 主線程安全,允許你在主線程中調(diào)用任意 suspend(掛起) 函數(shù)
  • 為了解決這些問題,協(xié)程基于基礎(chǔ)函數(shù)添加了 suspend 和 resume。當(dāng)特定線程上的所有協(xié)程都被掛起,該線程就可以做其他工作了。

    但是,協(xié)程本身并不能幫助你追蹤正在進(jìn)行的任務(wù)。同時(shí)擁有并掛起數(shù)百甚至上千的協(xié)程是不可能的。盡管協(xié)程是輕量的,但它們執(zhí)行的任務(wù)并不是,例如文件讀寫,網(wǎng)絡(luò)請求等。

    使用代碼手動(dòng)追蹤一千個(gè)協(xié)程的確是很困難的。你可以嘗試去追蹤它們,并且手動(dòng)保證它們最后會完成或者取消,但是這樣的代碼冗余,而且容易出錯(cuò)。如果你的代碼不夠完美,你將失去對一個(gè)協(xié)程的追蹤,我把它稱之為任務(wù)泄露。

    任務(wù)泄露就像內(nèi)存泄露一樣,而且更加糟糕。對于已經(jīng)丟失泄露的協(xié)程,除了內(nèi)存消耗之外,它還會恢復(fù)自己來消耗 CPU,磁盤,甚至啟動(dòng)一個(gè)網(wǎng)絡(luò)請求。

    泄露的協(xié)程會浪費(fèi)內(nèi)存,CPU,磁盤,甚至發(fā)送一個(gè)不需要的網(wǎng)絡(luò)請求。

    為了避免泄露協(xié)程,Kotlin 引入了 structured concurrency(結(jié)構(gòu)化并發(fā))。結(jié)構(gòu)化并集合了語言特性和最佳實(shí)踐,遵循這個(gè)原則將幫助你追蹤協(xié)程中的所有任務(wù)。

    在 Android 中,我們使用結(jié)構(gòu)化并發(fā)可以做三件事:

  • 取消不再需要的任務(wù)
  • 追蹤所有正在進(jìn)行的任務(wù)
  • 協(xié)程失敗時(shí)的錯(cuò)誤信號
  • 讓我們深入探討這幾點(diǎn),來看看結(jié)構(gòu)化并發(fā)是如何幫助我們避免丟失對協(xié)程的追蹤以及任務(wù)泄露。

    通過作用域取消任務(wù)

    在 Kotlin 中,協(xié)程必須運(yùn)行在 CoroutineScope 中。CoroutineScope 會追蹤你的協(xié)程,即使協(xié)程已經(jīng)被掛起。不同于上一篇文章中說過的 Dispatchers,它實(shí)際上并不執(zhí)行協(xié)程,它僅僅只是保證你不會丟失對協(xié)程的追蹤。

    為了保證所有的協(xié)程都被追蹤到,Kotlin 不允許你在沒有 CoroutineScope 的情況下開啟新的協(xié)程。你可以把 CoroutineScope 想象成具有特殊能力的輕量級的 ExecutorServicce。它賦予你創(chuàng)建新協(xié)程的能力,這些協(xié)程都具備我們在上篇文章中討論過的掛起和恢復(fù)的能力。

    CoroutineScope 會追蹤所有的協(xié)程,并且它也可以取消所有由他開啟的協(xié)程。這很適合 Android 開發(fā)者,當(dāng)用戶離開當(dāng)前頁面后,可以保證清理掉所有已經(jīng)開啟的東西。

    CoroutineScope 會追蹤所有的協(xié)程,并且它也可以取消所有由他開啟的協(xié)程。

    啟動(dòng)新的協(xié)程

    有一點(diǎn)需要注意的是,你不是在任何地方都可以調(diào)用掛起函數(shù)。掛起和恢復(fù)機(jī)制要求你從普通函數(shù)切換到協(xié)程。

    啟動(dòng)協(xié)程有兩種方法,且有不同的用法:

  • 使用 launch 協(xié)程構(gòu)建器啟動(dòng)一個(gè)新的協(xié)程,這個(gè)協(xié)程是沒返回值的
  • 使用 async 協(xié)程構(gòu)建器啟動(dòng)一個(gè)新的協(xié)程,它允許你返回一個(gè)結(jié)果,通過掛起函數(shù) await 來獲取。
  • 在大多數(shù)情況下,如何從一個(gè)普通函數(shù)啟動(dòng)協(xié)程的答案都是使用 launch。因?yàn)槠胀ê瘮?shù)是不能調(diào)用 await 的(記住,普通函數(shù)不能直接調(diào)用掛起函數(shù))。稍后我們會討論什么時(shí)候應(yīng)該使用 async。

    你應(yīng)該調(diào)用 launch 來使用協(xié)程作用域啟動(dòng)一個(gè)新的協(xié)程。

    scope.launch {// This block starts a new coroutine// "in" the scope.//// It can call suspend functionsfetchDocs() } 復(fù)制代碼

    你可以把 launch 想象成一座橋梁,連接了普通函數(shù)中的代碼和協(xié)程的世界。在 launch 內(nèi)部,你可以調(diào)用掛起函數(shù),并且創(chuàng)建主線程安全性,就像上篇文章中提到的那樣。

    Launch 是把普通函數(shù)帶進(jìn)協(xié)程世界的橋梁。

    提示:launch 和 async 很大的一個(gè)區(qū)別是異常處理。async 期望你通過調(diào)用 await 來獲取結(jié)果(或異常),所以它默認(rèn)不會拋出異常。這就意味著使用 async 啟動(dòng)新的協(xié)程,它會悄悄的把異常丟棄。

    由于 launch 和 async 只能在 CoroutineScope 中使用,所以你創(chuàng)建的每一個(gè)協(xié)程都會被協(xié)程作用域追蹤。Kotlin 不允許你創(chuàng)建未被追蹤的協(xié)程,這樣可以有效避免任務(wù)泄露。

    在 ViewModel 中啟動(dòng)

    如果一個(gè) CoroutineScope 追蹤在其中啟動(dòng)的所有協(xié)程,launch 會新建一個(gè)協(xié)程,那么你應(yīng)該在何處調(diào)用 launch 并將其置于協(xié)程作用域中呢?還有,你應(yīng)該在什么時(shí)候取消在作用域中啟動(dòng)的所有協(xié)程呢?

    在 Android 中,通常將 CoroutineScope 和用戶界面相關(guān)聯(lián)起來。這將幫助你避免協(xié)程泄露,并且使得用戶不再需要的 Activity 或者 Fragment 不再做額外的工作。當(dāng)用戶離開當(dāng)前頁面,與頁面相關(guān)聯(lián)的 CoroutineScope 將取消所有工作。

    結(jié)構(gòu)化并發(fā)保證當(dāng)協(xié)程作用域取消,其中的所有協(xié)程都會取消。

    當(dāng)通過 Android Architecture Components 集成協(xié)程時(shí),一般都是在 ViewModel 中啟動(dòng)協(xié)程。這里是許多重要任務(wù)開始工作的地方,并且你不必?fù)?dān)心旋轉(zhuǎn)屏幕會殺死協(xié)程。

    為了在 ViewModel 中使用協(xié)程,你可以來自 lifecycle-viewmodel-ktx:2.1.0- alpha04 這個(gè)庫的 viewModelScope。viewModelScope 即將在 Android Lifecycle v2.1.0 發(fā)布,現(xiàn)在仍然是 alpha 版本。關(guān)于 viewModelScope 的原理可以閱讀 這篇博客。既然這個(gè)庫目前還是 alpha 版本,就可能會有 bug,API 也可能發(fā)生變動(dòng)。如果你找到了 bug,可以在 這里 提交。

    看一下使用的例子:

    class MyViewModel(): ViewModel() {fun userNeedsDocs() {// Start a new coroutine in a ViewModelviewModelScope.launch {fetchDocs()}} } 復(fù)制代碼

    當(dāng) viewModelScope 被清除(即 onCleared() 被調(diào)用)時(shí),它會自動(dòng)取消由它啟動(dòng)的所有協(xié)程。這肯定是正確的行為,當(dāng)我們還沒有讀取到文檔,用戶已經(jīng)關(guān)閉了 app,我們還繼續(xù)請求的話只是在浪費(fèi)電量。

    為了更高的安全性,協(xié)程作用域會自動(dòng)傳播。如果你啟動(dòng)的協(xié)程中又啟動(dòng)了另一個(gè)協(xié)程,它們最終會在同一個(gè)作用域中結(jié)束。這就意味著你依賴的庫通過你的 viewModelScope 啟動(dòng)了新的協(xié)程,你就有辦法取消它們了!

    Warning: Coroutines are cancelled cooperatively by throwing a CancellationException when the coroutine is suspended. Exception handlers that catch a top-level exception like Throwable will catch this exception. If you consume the exception in an exception handler, or never suspend, the coroutine will linger in a semi-canceled state.(這段沒有理解)

    所以,當(dāng)你需要協(xié)程和 ViewModel 的生命周期保持一致時(shí),使用 viewModelScope 來從普通函數(shù)切換到協(xié)程。那么,由于 viewModelScope 會自動(dòng)取消協(xié)程,編寫下面這樣的無限循環(huán)是沒有問題的,不會造成泄露。

    fun runForever() {// start a new coroutine in the ViewModelviewModelScope.launch {// cancelled when the ViewModel is clearedwhile(true) {delay(1_000)// do something every second}} } 復(fù)制代碼

    使用 viewModelScope,你可以確保任何工作,即使是死循環(huán),都能在不再需要執(zhí)行的時(shí)候?qū)⑵淙∠?/p>

    追蹤任務(wù)

    啟動(dòng)一個(gè)協(xié)程是沒問題的,很多時(shí)候也正是這樣做的。通過一個(gè)協(xié)程,進(jìn)行網(wǎng)絡(luò)請求,保存數(shù)據(jù)到數(shù)據(jù)庫。

    有時(shí)候,情況會稍微有點(diǎn)復(fù)雜。如果你想在一個(gè)協(xié)程中同時(shí)進(jìn)行兩個(gè)網(wǎng)絡(luò)請求,你就需要啟動(dòng)更多的協(xié)程。

    為了啟動(dòng)更多的協(xié)程,任何掛起函數(shù)都可以使用 coroutineScope 或者 supervisorScope 構(gòu)建器來新建協(xié)程。這個(gè) API,說實(shí)話有點(diǎn)讓人困惑。coroutineScope 構(gòu)建器和 CoroutineScope 是兩個(gè)不同的東西,卻只有一個(gè)字母不一樣。

    在任何地方啟動(dòng)新協(xié)程,這可能會導(dǎo)致潛在的任務(wù)泄露。調(diào)用者可能都不知道新協(xié)程的啟動(dòng),它又如何其跟蹤呢?

    結(jié)構(gòu)化并發(fā)幫助我們解決了這個(gè)問題。它給我們提供了一個(gè)保障,保證當(dāng)掛起函數(shù)返回時(shí),它的所有工作都已經(jīng)完成。

    結(jié)構(gòu)化并發(fā)保證當(dāng)掛起函數(shù)返回時(shí),它的所有任務(wù)都已經(jīng)完成。

    下面是使用 coroutineScope 來查詢文檔的例子:

    suspend fun fetchTwoDocs() {coroutineScope {launch { fetchDoc(1) }async { fetchDoc(2) }} } 復(fù)制代碼

    在這個(gè)例子中,同時(shí)從網(wǎng)絡(luò)讀取兩個(gè)文檔。第一個(gè)是在由 launch 啟動(dòng)的協(xié)程中執(zhí)行,它不會給調(diào)用者返回任何結(jié)果。

    第二個(gè)使用的是 async,所以文檔可以返回給調(diào)用者。這里例子有點(diǎn)奇怪,通常兩個(gè)文檔都會使用 async。但是我只是想向你展示你可以根據(jù)你的需求混合使用 launch 和 async。

    coroutineScope 和 supervisorScope 讓你可以安全的在掛起函數(shù)中啟動(dòng)協(xié)程。

    盡管上面的代碼沒有在任何地方顯示的聲明要等待協(xié)程的執(zhí)行完成,看起來當(dāng)協(xié)程還在運(yùn)行的時(shí)候,fetDocs 方法就會返回。

    為了結(jié)構(gòu)化并發(fā)和避免任務(wù)泄露,我們希望確保當(dāng)掛起函數(shù)(例如 fetchDocs)返回時(shí),它的所有任務(wù)都已經(jīng)完成。這就意味著,由 fetchDocs 啟動(dòng)的所有協(xié)程都會先于它返回之前執(zhí)行結(jié)束。

    Kotlin 通過 coroutineScope 構(gòu)建器確保 fetchDocs 中的任務(wù)不會泄露。coroutineScope 構(gòu)建器直到在其中啟動(dòng)的所有協(xié)程都執(zhí)行結(jié)束時(shí)才會掛起自己。正因如此,在 coroutineScope 中的所有協(xié)程尚未結(jié)束之前就從 fetchDocs 中返回是不可能的。

    許多許多任務(wù)

    現(xiàn)在我們已經(jīng)探索了如何追蹤一個(gè)和兩個(gè)協(xié)程,現(xiàn)在是時(shí)候來嘗試追蹤一千個(gè)協(xié)程了!

    看一下下面的動(dòng)畫:

    這個(gè)例子展示了同時(shí)進(jìn)行一千次網(wǎng)絡(luò)請求。這在真實(shí)的代碼中是不建議的,會浪費(fèi)大量資源。

    上面的代碼中,我們在 coroutineScope 中通過 launch 啟動(dòng)了一千個(gè)協(xié)程。你可以看到它們是如何連接起來的。由于我們是在掛起函數(shù)中,所以某個(gè)地方的代碼一定是使用了 CoroutineScope 來啟動(dòng)協(xié)程。對于這個(gè) CoroutineScope,我們一無所知,它可能是 viewModelScope 或者定義在其他地方的 CoroutineScope。無論它是什么作用域,coroutineScope 構(gòu)建器都會把它當(dāng)做新建作用域的父親。

    在 coroutineScope 代碼塊中,launch 將在新的作用域中啟動(dòng)協(xié)程。當(dāng)協(xié)程完成啟動(dòng),這個(gè)新的作用域?qū)⒆粉櫵W詈?#xff0c;一旦在 coroutineScope 中啟動(dòng)的所有協(xié)程都完成了,loadLots 就可以返回了。

    Note: the parent-child relationship between scopes and coroutines is created using Job objects. But you can often think of the relationship between coroutines and scopes without diving into that level.

    coroutineScope 和 supervisorScope 會等待所有子協(xié)程執(zhí)行結(jié)束。

    這里有很多事情在進(jìn)行,其中最重要的就是使用 coroutineScope 或者 supervisorScope,你可以在任意掛起函數(shù)中安全的啟動(dòng)協(xié)程。盡管這將啟動(dòng)一個(gè)新協(xié)程,你也不會意外的泄露任務(wù),因?yàn)橹挥兴行聟f(xié)程都完成了你才可以掛起調(diào)用者。

    很酷的是 coroutineScope 可以創(chuàng)建子作用域。如果父作用域被取消,它會將取消動(dòng)作傳遞給所有的新協(xié)程。如果調(diào)用者是 viewModelScope,當(dāng)用戶離開頁面是,所有的一千個(gè)協(xié)程都會自動(dòng)取消。多么的整潔!

    在我們移步談?wù)摦惓L幚碇?#xff0c;有必要來討論一下 coroutineScope 和 supervisorScope。它們之間最大的不同就是,當(dāng)其中任意一個(gè)子協(xié)程失敗時(shí),coroutineScope 會取消。所以,如果一個(gè)網(wǎng)絡(luò)請求失敗了,其他的所有請求都會立刻被取消。如果你想繼續(xù)執(zhí)行其他請求的話,你可以使用 supervisorScope,當(dāng)一個(gè)子協(xié)程失敗時(shí),它不會取消其他的子協(xié)程。

    協(xié)程失敗的異常處理

    在協(xié)程中,錯(cuò)誤也是用過拋出異常來發(fā)出信號,和普通函數(shù)一樣。掛起函數(shù)的異常將在 resume 的時(shí)候重新拋出給調(diào)用者。和普通函數(shù)一樣,你不會被限制使用 try/catch 來處理錯(cuò)誤,你也可以按你喜歡的方式來處理異常。

    但是,有一些情況下,協(xié)程中的異常會丟失。

    val unrelatedScope = MainScope()// example of a lost errorsuspend fun lostError() {// async without structured concurrencyunrelatedScope.async {throw InAsyncNoOneCanHearYou("except")} } 復(fù)制代碼

    注意,上面的代碼中聲明了一個(gè)未經(jīng)關(guān)聯(lián)的協(xié)程作用域,并且未通過結(jié)構(gòu)化并發(fā)啟動(dòng)新協(xié)程。記住我開始說過的,結(jié)構(gòu)化并發(fā)集合了語言特性和最佳實(shí)踐,在掛起函數(shù)中引入未經(jīng)關(guān)聯(lián)的協(xié)程作用并不是結(jié)構(gòu)化并發(fā)的最佳實(shí)踐。

    上面代碼中的錯(cuò)誤會丟失,因?yàn)?async 認(rèn)為你會調(diào)用 await,這時(shí)候會重新拋出異常。但是如果你沒有調(diào)用 await,這個(gè)錯(cuò)誤將永遠(yuǎn)被保存,靜靜的等待被發(fā)現(xiàn)。

    結(jié)構(gòu)化并發(fā)保證當(dāng)一個(gè)協(xié)程發(fā)生錯(cuò)誤,它的調(diào)用者或者作用域可以發(fā)現(xiàn)。

    如果我們使用結(jié)構(gòu)化并發(fā)寫上面的代碼,異常將會正確的拋給調(diào)用者。

    suspend fun foundError() {coroutineScope {async {throw StructuredConcurrencyWill("throw")}} } 復(fù)制代碼

    由于 coroutineScope 會等待所有子協(xié)程執(zhí)行完成,所以當(dāng)子協(xié)程失敗時(shí)它也會知道。當(dāng) coroutineScope 啟動(dòng)的協(xié)程拋出了異常,coroutineScope 會將異常扔給調(diào)用者。如果使用 coroutineScope 代替 supervisorScope,當(dāng)異常拋出時(shí),會立刻停止所有的子協(xié)程。

    使用結(jié)構(gòu)化并發(fā)

    在這篇文章中,我介紹了結(jié)構(gòu)化并發(fā),以及在代碼中配合 ViewModel 使用來避免任務(wù)泄露。我還談?wù)摿怂侨绾巫寬炱鸷瘮?shù)更加簡單。兩者都確保在返回之前完成任務(wù),也可以確保正確的異常處理。

    我們使用非結(jié)構(gòu)化并發(fā),很容易造成意外的任務(wù)泄露,這對調(diào)用者來說是未知的。任務(wù)將變得不可取消,也不能保證異常被正確的拋出。這會導(dǎo)致我們的代碼產(chǎn)生一些模糊的錯(cuò)誤。

    使用未關(guān)聯(lián)的 CoroutineScope(注意是大寫字母 C),或者使用全局作用域 GlobalScope ,會導(dǎo)致非結(jié)構(gòu)化并發(fā)。只有在少數(shù)情況下,你需要協(xié)程的生命周期長于調(diào)用者的作用域時(shí),才考慮使用非結(jié)構(gòu)化并發(fā)。通常情況下,你都應(yīng)該使用結(jié)構(gòu)化并發(fā)來追蹤協(xié)程,處理異常,擁有良好的取消機(jī)制。

    如果你有非結(jié)構(gòu)化并發(fā)的經(jīng)驗(yàn),那么結(jié)構(gòu)化并發(fā)的確需要一些時(shí)間來適應(yīng)。這種保障使得和掛起函數(shù)交互更加安全和簡單。我們應(yīng)該盡可能的使用結(jié)構(gòu)化并發(fā),因?yàn)樗沟么a更加簡單和易讀。

    在文章的開頭,我列舉了結(jié)構(gòu)化并發(fā)幫助我們解決的三個(gè)問題:

  • 取消不再需要的任務(wù)
  • 追蹤所有正在進(jìn)行的任務(wù)
  • 協(xié)程失敗時(shí)的錯(cuò)誤信號
  • 結(jié)構(gòu)化并發(fā)給予我們?nèi)缦卤WC:

  • 當(dāng)作用域取消,其中的協(xié)程也會取消
  • 當(dāng)掛起函數(shù)返回,其中的所有任務(wù)都已完成
  • 當(dāng)協(xié)程發(fā)生錯(cuò)誤,其調(diào)用者會得到通知
  • 這些加在一起,使得我們的代碼更加安全,簡潔,并且?guī)椭覀儽苊馊蝿?wù)泄露。

    What's Next?

    這篇文章中,我們探索了如何在 Android 的 ViewModel 中啟動(dòng)協(xié)程,以及如何使用結(jié)構(gòu)化并發(fā)來優(yōu)化代碼。

    下一篇中,我們將更多的討論在特定情況下使用協(xié)程。

    文章首發(fā)微信公眾號: 秉心說 , 專注 Java 、 Android 原創(chuàng)知識分享,LeetCode 題解。

    更多 JDK 源碼解析,掃碼關(guān)注我吧!

    總結(jié)

    以上是生活随笔為你收集整理的在 Android 上使用协程(二):Getting started的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。