使用kotlin協(xié)程提高app性能(譯)
協(xié)程是一種并發(fā)設(shè)計(jì)模式,您可以在Android上使用它來(lái)簡(jiǎn)化異步執(zhí)行的代碼。Kotlin1.3版本添加了 Coroutines,并基于其他語(yǔ)言的既定概念。
在Android上,協(xié)程有助于解決兩個(gè)主要問(wèn)題:
- 管理長(zhǎng)時(shí)間運(yùn)行的任務(wù),否則可能會(huì)阻止主線程并導(dǎo)致應(yīng)用凍結(jié)。
- 提供主安全性,或從主線程安全地調(diào)用網(wǎng)絡(luò)或磁盤操作。
本主題描述了如何使用Kotlin協(xié)程解決這些問(wèn)題,使您能夠編寫(xiě)更清晰,更簡(jiǎn)潔的應(yīng)用程序代碼。
管理長(zhǎng)時(shí)間運(yùn)行的任務(wù)
在Android上,每個(gè)應(yīng)用程序都有一個(gè)主線程來(lái)處理用戶界面并管理用戶交互。如果您的應(yīng)用程序?yàn)橹骶€程分配了太多工作,那么應(yīng)用程序可能會(huì)明顯卡頓或運(yùn)行緩慢。網(wǎng)絡(luò)請(qǐng)求,JSON解析,從數(shù)據(jù)庫(kù)讀取或?qū)懭耄踔林皇堑笮土斜矶伎赡軐?dǎo)致應(yīng)用程序運(yùn)行緩慢,導(dǎo)致可見(jiàn)的緩慢或凍結(jié)的UI對(duì)觸摸事件響應(yīng)緩慢。這些長(zhǎng)時(shí)間運(yùn)行的操作應(yīng)該在主線程之外運(yùn)行。
以下示例顯示了假設(shè)的長(zhǎng)期運(yùn)行任務(wù)的簡(jiǎn)單協(xié)程實(shí)現(xiàn):
suspend fun fetchDocs() { // Dispatchers.Main val result = get("https://developer.android.com") // Dispatchers.IO for `get` show(result) // Dispatchers.Main } suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }
協(xié)同程序通過(guò)添加兩個(gè)操作來(lái)處理長(zhǎng)時(shí)間運(yùn)行的任務(wù),從而構(gòu)建常規(guī)功能。除了invoke(或call)和返回之外,協(xié)同程序還添加了suspend和resume:
- suspend暫停當(dāng)前協(xié)同程序的執(zhí)行,保存所有局部變量。
- resume恢復(fù)從暫停的協(xié)同處繼續(xù)執(zhí)行暫停的協(xié)同程序。
您只能從其他suspend函數(shù)調(diào)用suspend函數(shù),或者使用諸如啟動(dòng)之類的協(xié)程構(gòu)建器來(lái)啟動(dòng)新的協(xié)程。
在上面的示例中,get()仍然在主線程上運(yùn)行,但它在啟動(dòng)網(wǎng)絡(luò)請(qǐng)求之前掛起協(xié)同程序。當(dāng)網(wǎng)絡(luò)請(qǐng)求完成時(shí),get恢復(fù)暫停的協(xié)程,而不是使用回調(diào)來(lái)通知主線程。
Kotlin使用堆??蚣軄?lái)管理與任何局部變量一起運(yùn)行的函數(shù)。掛起協(xié)程時(shí),將復(fù)制并保存當(dāng)前堆棧幀以供以后使用。恢復(fù)時(shí),堆棧幀將從保存位置復(fù)制回來(lái),并且該函數(shù)將再次開(kāi)始運(yùn)行。即使代碼看起來(lái)像普通的順序阻塞請(qǐng)求,協(xié)程也可以確保網(wǎng)絡(luò)請(qǐng)求避免阻塞主線程。
Use coroutines for main-safety
Kotlin協(xié)程使用調(diào)度程序來(lái)確定哪些線程用于協(xié)程執(zhí)行。要在主線程之外運(yùn)行代碼,您可以告訴Kotlin協(xié)程在Default或IO調(diào)度程序上執(zhí)行工作。在Kotlin中,所有協(xié)同程序必須在調(diào)度程序中運(yùn)行,即使它們?cè)谥骶€程上運(yùn)行。協(xié)同程序可以暫停,調(diào)度程序負(fù)責(zé)恢復(fù)它們。
要指定協(xié)程應(yīng)該運(yùn)行的位置,Kotlin提供了三個(gè)可以使用的調(diào)度程序:
- Dispatchers.Main - 使用此調(diào)度程序在主Android線程上運(yùn)行協(xié)同程序。 這應(yīng)該僅用于與UI交互并執(zhí)行快速工作。 示例包括調(diào)用掛起函數(shù),運(yùn)行Android UI框架操作以及更新LiveData對(duì)象。
- Dispatchers.IO - 此調(diào)度程序已經(jīng)過(guò)優(yōu)化,可在主線程外執(zhí)行磁盤或網(wǎng)絡(luò)I / O. 示例包括使用Room組件,讀取或?qū)懭胛募约斑\(yùn)行任何網(wǎng)絡(luò)操作。
- Dispatchers.Default - 此調(diào)度程序已經(jīng)過(guò)優(yōu)化,可以在主線程之外執(zhí)行CPU密集型工作。 示例用例包括對(duì)列表進(jìn)行排序和解析JSON。
繼續(xù)前面的示例,您可以使用調(diào)度程序重新定義get函數(shù)。 在get的主體內(nèi)部,調(diào)用withContext(Dispatchers.IO)來(lái)創(chuàng)建一個(gè)在IO線程池上運(yùn)行的塊。 放在該塊中的任何代碼總是通過(guò)IO調(diào)度程序執(zhí)行。 由于withContext本身是一個(gè)掛起函數(shù),因此函數(shù)get也是一個(gè)掛起函數(shù)。
使用協(xié)同程序,您可以調(diào)度具有細(xì)粒度控制的線程。 因?yàn)閣ithContext()允許您控制任何代碼行的線程池而不引入回調(diào),所以您可以將它應(yīng)用于非常小的函數(shù),例如從數(shù)據(jù)庫(kù)讀取或執(zhí)行網(wǎng)絡(luò)請(qǐng)求。 一個(gè)好的做法是使用withContext()來(lái)確保每個(gè)函數(shù)都是主安全的,這意味著您可以從主線程調(diào)用該函數(shù)。 這樣,調(diào)用者永遠(yuǎn)不需要考慮應(yīng)該使用哪個(gè)線程來(lái)執(zhí)行該函數(shù)。
在前面的示例中,fetchDocs()在主線程上執(zhí)行; 但是,它可以安全地調(diào)用get,后者在后臺(tái)執(zhí)行網(wǎng)絡(luò)請(qǐng)求。 因?yàn)閰f(xié)同程序支持掛起和恢復(fù),所以只要withContext塊完成,主線程上的協(xié)程就會(huì)以get結(jié)果恢復(fù)。
重要說(shuō)明:使用suspend并不能告訴Kotlin在后臺(tái)線程上運(yùn)行函數(shù)。 暫停函數(shù)在主線程上運(yùn)行是正常的。 在主線程上啟動(dòng)協(xié)同程序也很常見(jiàn)。 當(dāng)您需要主安全時(shí),例如在讀取或?qū)懭氪疟P,執(zhí)行網(wǎng)絡(luò)操作或運(yùn)行CPU密集型操作時(shí),應(yīng)始終在掛起函數(shù)內(nèi)使用withContext()。
與等效的基于回調(diào)的實(shí)現(xiàn)相比,withContext()不會(huì)增加額外的開(kāi)銷。 此外,在某些情況下,可以優(yōu)化withContext()調(diào)用,而不是基于等效的基于回調(diào)的實(shí)現(xiàn)。 例如,如果一個(gè)函數(shù)對(duì)網(wǎng)絡(luò)進(jìn)行十次調(diào)用,則可以通過(guò)使用外部withContext()告訴Kotlin只切換一次線程。 然后,即使網(wǎng)絡(luò)庫(kù)多次使用withContext(),它仍然停留在同一個(gè)調(diào)度程序上,并避免切換線程。 此外,Kotlin優(yōu)化了Dispatchers.Default和Dispatchers.IO之間的切換,以盡可能避免線程切換。
要點(diǎn):使用使用Dispatchers.IO或Dispatchers.Default等線程池的調(diào)度程序并不能保證該塊從上到下在同一個(gè)線程上執(zhí)行。 在某些情況下,Kotlin協(xié)程可能會(huì)在暫停和恢復(fù)后將執(zhí)行移動(dòng)到另一個(gè)線程。 這意味著線程局部變量可能不會(huì)指向整個(gè)withContext()塊的相同值。
指定CoroutineScope
定義協(xié)程時(shí),還必須指定其CoroutineScope。 CoroutineScope管理一個(gè)或多個(gè)相關(guān)協(xié)程。 您還可以使用CoroutineScope在該范圍內(nèi)啟動(dòng)新協(xié)程。 但是,與調(diào)度程序不同,CoroutineScope不會(huì)運(yùn)行協(xié)同程序。
CoroutineScope的一個(gè)重要功能是當(dāng)用戶離開(kāi)應(yīng)用程序中的內(nèi)容區(qū)域時(shí)停止協(xié)程執(zhí)行。 使用CoroutineScope,您可以確保正確停止任何正在運(yùn)行的操作。
將CoroutineScope與Android架構(gòu)組件配合使用
在Android上,您可以將CoroutineScope實(shí)現(xiàn)與組件生命周期相關(guān)聯(lián)。這樣可以避免泄漏內(nèi)存或?yàn)榕c用戶不再相關(guān)的activity或fragment執(zhí)行額外的工作。使用Jetpack組件,它們自然適合ViewModel。由于ViewModel在配置更改(例如屏幕旋轉(zhuǎn))期間不會(huì)被銷毀,因此您不必?fù)?dān)心協(xié)同程序被取消或重新啟動(dòng)。
范圍知道他們開(kāi)始的每個(gè)協(xié)同程序。這意味著您可以隨時(shí)取消在作用域中啟動(dòng)的所有內(nèi)容。范圍傳播自己,所以如果一個(gè)協(xié)程開(kāi)始另一個(gè)協(xié)同程序,兩個(gè)協(xié)同程序具有相同的范圍。這意味著即使其他庫(kù)從您的范圍啟動(dòng)協(xié)程,您也可以隨時(shí)取消它們。如果您在ViewModel中運(yùn)行協(xié)同程序,這一點(diǎn)尤為重要。如果因?yàn)橛脩綦x開(kāi)了屏幕而導(dǎo)致ViewModel被銷毀,則必須停止它正在執(zhí)行的所有異步工作。否則,您將浪費(fèi)資源并可能泄漏內(nèi)存。如果您在銷毀ViewModel后應(yīng)該繼續(xù)進(jìn)行異步工作,則應(yīng)該在應(yīng)用程序架構(gòu)的較低層中完成。
警告:通過(guò)拋出CancellationException協(xié)同取消協(xié)同程序。 在協(xié)程取消期間觸發(fā)捕獲異?;騎hrowable的異常處理程序。
使用適用于Android體系結(jié)構(gòu)的KTX庫(kù)組件,您還可以使用擴(kuò)展屬性viewModelScope來(lái)創(chuàng)建可以運(yùn)行的協(xié)同程序,直到ViewModel被銷毀。
啟動(dòng)一個(gè)協(xié)程
您可以通過(guò)以下兩種方式之一啟動(dòng)協(xié)同程序:
- launch會(huì)啟動(dòng)一個(gè)新的協(xié)程,并且不會(huì)將結(jié)果返回給調(diào)用者。 任何被認(rèn)為是“發(fā)射并忘記”的工作都可以使用launch來(lái)開(kāi)始。
- async啟動(dòng)一個(gè)新的協(xié)同程序,并允許您使用名為await的掛起函數(shù)返回結(jié)果。
通常,您應(yīng)該從常規(guī)函數(shù)啟動(dòng)新協(xié)程,因?yàn)槌R?guī)函數(shù)無(wú)法調(diào)用等待。 僅在另一個(gè)協(xié)同程序內(nèi)部或在掛起函數(shù)內(nèi)部執(zhí)行并行分解時(shí)才使用異步。
在前面的示例的基礎(chǔ)上,這里是一個(gè)帶有viewModelScope KTX擴(kuò)展屬性的協(xié)程,它使用launch從常規(guī)函數(shù)切換到協(xié)同程序:
fun onDocsNeeded() { viewModelScope.launch { // Dispatchers.Main fetchDocs() // Dispatchers.Main (suspend function call) } }
警告:?jiǎn)?dòng)和異步處理異常的方式不同。 由于async期望在某個(gè)時(shí)刻最終調(diào)用await,它會(huì)保留異常并在await調(diào)用中重新拋出它們。 這意味著如果您使用await從常規(guī)函數(shù)啟動(dòng)新的協(xié)同程序,則可能會(huì)以靜默方式刪除異常。 這些丟棄的異常不會(huì)出現(xiàn)在崩潰指標(biāo)中,也不會(huì)出現(xiàn)在logcat中。
并行分解
當(dāng)函數(shù)返回時(shí),必須停止由掛起函數(shù)啟動(dòng)的所有協(xié)同程序,因此您可能需要保證這些協(xié)程在返回之前完成。 通過(guò)Kotlin中的結(jié)構(gòu)化并發(fā),您可以定義一個(gè)啟動(dòng)一個(gè)或多個(gè)協(xié)同程序的coroutineScope。 然后,使用await()(對(duì)于單個(gè)協(xié)同程序)或awaitAll()(對(duì)于多個(gè)協(xié)程),可以保證這些協(xié)程在從函數(shù)返回之前完成。
例如,讓我們定義一個(gè)以異步方式獲取兩個(gè)文檔的coroutineScope。 通過(guò)在每個(gè)延遲引用上調(diào)用await(),我們保證在返回值之前兩個(gè)異步操作都完成:
suspend fun fetchTwoDocs() = coroutineScope { val deferredOne = async { fetchDoc(1) } val deferredTwo = async { fetchDoc(2) } deferredOne.await() deferredTwo.await() }
即使fetchTwoDocs()使用異步啟動(dòng)新的協(xié)同程序,該函數(shù)也會(huì)使用awaitAll()等待那些啟動(dòng)的協(xié)同程序在返回之前完成。 但請(qǐng)注意,即使我們沒(méi)有調(diào)用awaitAll(),coroutineScope構(gòu)建器也不會(huì)恢復(fù)調(diào)用fetchTwoDocs的協(xié)程,直到所有新的協(xié)程完成。
此外,coroutineScope捕獲協(xié)程拋出的任何異常并將它們路由回調(diào)用者。
有關(guān)并行分解的更多信息,請(qǐng)參閱編寫(xiě)掛起函數(shù)。
具有內(nèi)置支持的架構(gòu)組件
一些體系結(jié)構(gòu)組件(包括ViewModel和Lifecycle)通過(guò)其自己的CoroutineScope成員包含對(duì)協(xié)同程序的內(nèi)置支持。
例如,ViewModel包含一個(gè)內(nèi)置的viewModelScope。 這提供了在ViewModel范圍內(nèi)啟動(dòng)協(xié)同程序的標(biāo)準(zhǔn)方法,如以下示例所示:
class MyViewModel : ViewModel() { fun launchDataLoad() { viewModelScope.launch { sortList() // Modify UI } } /** * Heavy operation that cannot be done in the Main Thread */ suspend fun sortList() = withContext(Dispatchers.Default) { // Heavy work } } LiveData還使用帶有l(wèi)iveData塊的協(xié)同程序: liveData { // runs in its own LiveData-specific scope }
原文
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持我們。
上一篇:android自定義view實(shí)現(xiàn)鐘表效果
欄 目:Android
下一篇:Android動(dòng)畫(huà)學(xué)習(xí)筆記之補(bǔ)間動(dòng)畫(huà)
本文標(biāo)題:使用kotlin協(xié)程提高app性能(譯)
本文地址:http://mengdiqiu.com.cn/a1/Android/8985.html
您可能感興趣的文章
- 01-10使用RecyclerView實(shí)現(xiàn)水平列表
- 01-10Android 使用URLConnection下載音頻文件的方法
- 01-10Android使用MediaPlayer和TextureView實(shí)現(xiàn)視頻無(wú)縫切換
- 01-10Android使用MediaCodec將攝像頭采集的視頻編碼為h264
- 01-10Android中butterknife的使用與自動(dòng)化查找組件插件詳解
- 01-10android 使用okhttp可能引發(fā)OOM的一個(gè)點(diǎn)
- 01-10Android使用第三方庫(kù)實(shí)現(xiàn)日期選擇器
- 01-10如何使用Flutter實(shí)現(xiàn)58同城中的加載動(dòng)畫(huà)詳解
- 01-10詳解Android使用CoordinatorLayout+AppBarLayout實(shí)現(xiàn)拉伸頂部圖片功能
- 01-10Kotlin 擴(kuò)展函數(shù)和擴(kuò)展屬性的使用方法


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實(shí)現(xiàn)雙擊返回鍵退出應(yīng)用實(shí)現(xiàn)方
- 01-10android實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
- 01-10android實(shí)現(xiàn)記住用戶名和密碼以及自動(dòng)
- 01-10C++自定義API函數(shù)實(shí)現(xiàn)大數(shù)相乘算法
- 01-10Android 友盟第三方登錄與分享的實(shí)現(xiàn)代
- 01-10android實(shí)現(xiàn)指紋識(shí)別功能
- 01-10如何給Flutter界面切換實(shí)現(xiàn)點(diǎn)特效
- 01-10Android實(shí)現(xiàn)圓形漸變加載進(jìn)度條
- 01-10Emoji表情在Android JNI中的兼容性問(wèn)題詳
隨機(jī)閱讀
- 04-02jquery與jsp,用jquery
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 01-10delphi制作wav文件的方法
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-10C#中split用法實(shí)例總結(jié)
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改