- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- 如何使用kotlin協(xié)程提高app性能
這篇文章將為大家詳細講解有關(guān)如何使用kotlin協(xié)程提高app性能,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
協(xié)程是一種并發(fā)設計模式,您可以在A(yíng)ndroid上使用它來(lái)簡(jiǎn)化異步執行的代碼。Kotlin1.3版本添加了 Coroutines,并基于其他語(yǔ)言的既定概念。
在A(yíng)ndroid上,協(xié)程有助于解決兩個(gè)主要問(wèn)題:
管理長(cháng)時(shí)間運行的任務(wù),否則可能會(huì )阻止主線(xiàn)程并導致應用凍結。提供主安全性,或從主線(xiàn)程安全地調用網(wǎng)絡(luò )或磁盤(pán)操作。
本主題描述了如何使用Kotlin協(xié)程解決這些問(wèn)題,使您能夠編寫(xiě)更清晰,更簡(jiǎn)潔的應用程序代碼。
管理長(cháng)時(shí)間運行的任務(wù)
在A(yíng)ndroid上,每個(gè)應用程序都有一個(gè)主線(xiàn)程來(lái)處理用戶(hù)界面并管理用戶(hù)交互。如果您的應用程序為主線(xiàn)程分配了太多工作,那么應用程序可能會(huì )明顯卡頓或運行緩慢。網(wǎng)絡(luò )請求,JSON解析,從數據庫讀取或寫(xiě)入,甚至只是迭代大型列表都可能導致應用程序運行緩慢,導致可見(jiàn)的緩慢或凍結的UI對觸摸事件響應緩慢。這些長(cháng)時(shí)間運行的操作應該在主線(xiàn)程之外運行。
以下示例顯示了假設的長(cháng)期運行任務(wù)的簡(jiǎn)單協(xié)程實(shí)現:
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)處理長(cháng)時(shí)間運行的任務(wù),從而構建常規功能。除了invoke(或call)和返回之外,協(xié)同程序還添加了suspend和resume:
suspend暫停當前協(xié)同程序的執行,保存所有局部變量。resume恢復從暫停的協(xié)同處繼續執行暫停的協(xié)同程序。
您只能從其他suspend函數調用suspend函數,或者使用諸如啟動(dòng)之類(lèi)的協(xié)程構建器來(lái)啟動(dòng)新的協(xié)程。
在上面的示例中,get()仍然在主線(xiàn)程上運行,但它在啟動(dòng)網(wǎng)絡(luò )請求之前掛起協(xié)同程序。當網(wǎng)絡(luò )請求完成時(shí),get恢復暫停的協(xié)程,而不是使用回調來(lái)通知主線(xiàn)程。
Kotlin使用堆??蚣軄?lái)管理與任何局部變量一起運行的函數。掛起協(xié)程時(shí),將復制并保存當前堆棧幀以供以后使用?;謴蜁r(shí),堆棧幀將從保存位置復制回來(lái),并且該函數將再次開(kāi)始運行。即使代碼看起來(lái)像普通的順序阻塞請求,協(xié)程也可以確保網(wǎng)絡(luò )請求避免阻塞主線(xiàn)程。
Use coroutines for main-safety
Kotlin協(xié)程使用調度程序來(lái)確定哪些線(xiàn)程用于協(xié)程執行。要在主線(xiàn)程之外運行代碼,您可以告訴Kotlin協(xié)程在Default或IO調度程序上執行工作。在Kotlin中,所有協(xié)同程序必須在調度程序中運行,即使它們在主線(xiàn)程上運行。協(xié)同程序可以暫停,調度程序負責恢復它們。要指定協(xié)程應該運行的位置,Kotlin提供了三個(gè)可以使用的調度程序:
Dispatchers.Main - 使用此調度程序在主Android線(xiàn)程上運行協(xié)同程序。 這應該僅用于與UI交互并執行快速工作。 示例包括調用掛起函數,運行Android UI框架操作以及更新LiveData對象。Dispatchers.IO - 此調度程序已經(jīng)過(guò)優(yōu)化,可在主線(xiàn)程外執行磁盤(pán)或網(wǎng)絡(luò )I / O. 示例包括使用Room組件,讀取或寫(xiě)入文件以及運行任何網(wǎng)絡(luò )操作。Dispatchers.Default - 此調度程序已經(jīng)過(guò)優(yōu)化,可以在主線(xiàn)程之外執行CPU密集型工作。 示例用例包括對列表進(jìn)行排序和解析JSON。
繼續前面的示例,您可以使用調度程序重新定義get函數。 在get的主體內部,調用withContext(Dispatchers.IO)來(lái)創(chuàng )建一個(gè)在IO線(xiàn)程池上運行的塊。 放在該塊中的任何代碼總是通過(guò)IO調度程序執行。 由于withContext本身是一個(gè)掛起函數,因此函數get也是一個(gè)掛起函數。
使用協(xié)同程序,您可以調度具有細粒度控制的線(xiàn)程。 因為withContext()允許您控制任何代碼行的線(xiàn)程池而不引入回調,所以您可以將它應用于非常小的函數,例如從數據庫讀取或執行網(wǎng)絡(luò )請求。 一個(gè)好的做法是使用withContext()來(lái)確保每個(gè)函數都是主安全的,這意味著(zhù)您可以從主線(xiàn)程調用該函數。 這樣,調用者永遠不需要考慮應該使用哪個(gè)線(xiàn)程來(lái)執行該函數。
在前面的示例中,fetchDocs()在主線(xiàn)程上執行; 但是,它可以安全地調用get,后者在后臺執行網(wǎng)絡(luò )請求。 因為協(xié)同程序支持掛起和恢復,所以只要withContext塊完成,主線(xiàn)程上的協(xié)程就會(huì )以get結果恢復。
重要說(shuō)明:使用suspend并不能告訴Kotlin在后臺線(xiàn)程上運行函數。 暫停函數在主線(xiàn)程上運行是正常的。 在主線(xiàn)程上啟動(dòng)協(xié)同程序也很常見(jiàn)。 當您需要主安全時(shí),例如在讀取或寫(xiě)入磁盤(pán),執行網(wǎng)絡(luò )操作或運行CPU密集型操作時(shí),應始終在掛起函數內使用withContext()。
與等效的基于回調的實(shí)現相比,withContext()不會(huì )增加額外的開(kāi)銷(xiāo)。 此外,在某些情況下,可以?xún)?yōu)化withContext()調用,而不是基于等效的基于回調的實(shí)現。 例如,如果一個(gè)函數對網(wǎng)絡(luò )進(jìn)行十次調用,則可以通過(guò)使用外部withContext()告訴Kotlin只切換一次線(xiàn)程。 然后,即使網(wǎng)絡(luò )庫多次使用withContext(),它仍然停留在同一個(gè)調度程序上,并避免切換線(xiàn)程。 此外,Kotlin優(yōu)化了Dispatchers.Default和Dispatchers.IO之間的切換,以盡可能避免線(xiàn)程切換。
要點(diǎn):使用使用Dispatchers.IO或Dispatchers.Default等線(xiàn)程池的調度程序并不能保證該塊從上到下在同一個(gè)線(xiàn)程上執行。 在某些情況下,Kotlin協(xié)程可能會(huì )在暫停和恢復后將執行移動(dòng)到另一個(gè)線(xiàn)程。 這意味著(zhù)線(xiàn)程局部變量可能不會(huì )指向整個(gè)withContext()塊的相同值。
指定CoroutineScope
定義協(xié)程時(shí),還必須指定其CoroutineScope。 CoroutineScope管理一個(gè)或多個(gè)相關(guān)協(xié)程。 您還可以使用CoroutineScope在該范圍內啟動(dòng)新協(xié)程。 但是,與調度程序不同,CoroutineScope不會(huì )運行協(xié)同程序。
CoroutineScope的一個(gè)重要功能是當用戶(hù)離開(kāi)應用程序中的內容區域時(shí)停止協(xié)程執行。 使用CoroutineScope,您可以確保正確停止任何正在運行的操作。
將CoroutineScope與Android架構組件配合使用
在A(yíng)ndroid上,您可以將CoroutineScope實(shí)現與組件生命周期相關(guān)聯(lián)。這樣可以避免泄漏內存或為與用戶(hù)不再相關(guān)的activity或fragment執行額外的工作。使用Jetpack組件,它們自然適合ViewModel。由于ViewModel在配置更改(例如屏幕旋轉)期間不會(huì )被銷(xiāo)毀,因此您不必擔心協(xié)同程序被取消或重新啟動(dòng)。
范圍知道他們開(kāi)始的每個(gè)協(xié)同程序。這意味著(zhù)您可以隨時(shí)取消在作用域中啟動(dòng)的所有內容。范圍傳播自己,所以如果一個(gè)協(xié)程開(kāi)始另一個(gè)協(xié)同程序,兩個(gè)協(xié)同程序具有相同的范圍。這意味著(zhù)即使其他庫從您的范圍啟動(dòng)協(xié)程,您也可以隨時(shí)取消它們。如果您在ViewModel中運行協(xié)同程序,這一點(diǎn)尤為重要。如果因為用戶(hù)離開(kāi)了屏幕而導致ViewModel被銷(xiāo)毀,則必須停止它正在執行的所有異步工作。否則,您將浪費資源并可能泄漏內存。如果您在銷(xiāo)毀ViewModel后應該繼續進(jìn)行異步工作,則應該在應用程序架構的較低層中完成。
警告:通過(guò)拋出CancellationException協(xié)同取消協(xié)同程序。 在協(xié)程取消期間觸發(fā)捕獲異?;騎hrowable的異常處理程序。
使用適用于A(yíng)ndroid體系結構的KTX庫組件,您還可以使用擴展屬性viewModelScope來(lái)創(chuàng )建可以運行的協(xié)同程序,直到ViewModel被銷(xiāo)毀。
啟動(dòng)一個(gè)協(xié)程
您可以通過(guò)以下兩種方式之一啟動(dòng)協(xié)同程序:
launch會(huì )啟動(dòng)一個(gè)新的協(xié)程,并且不會(huì )將結果返回給調用者。 任何被認為是“發(fā)射并忘記”的工作都可以使用launch來(lái)開(kāi)始。async啟動(dòng)一個(gè)新的協(xié)同程序,并允許您使用名為await的掛起函數返回結果。
通常,您應該從常規函數啟動(dòng)新協(xié)程,因為常規函數無(wú)法調用等待。 僅在另一個(gè)協(xié)同程序內部或在掛起函數內部執行并行分解時(shí)才使用異步。
在前面的示例的基礎上,這里是一個(gè)帶有viewModelScope KTX擴展屬性的協(xié)程,它使用launch從常規函數切換到協(xié)同程序:
fun onDocsNeeded() { viewModelScope.launch { // Dispatchers.Main fetchDocs() // Dispatchers.Main (suspend function call) }}
警告:?jiǎn)?dòng)和異步處理異常的方式不同。 由于async期望在某個(gè)時(shí)刻最終調用await,它會(huì )保留異常并在await調用中重新拋出它們。 這意味著(zhù)如果您使用await從常規函數啟動(dòng)新的協(xié)同程序,則可能會(huì )以靜默方式刪除異常。 這些丟棄的異常不會(huì )出現在崩潰指標中,也不會(huì )出現在logcat中。
并行分解
當函數返回時(shí),必須停止由掛起函數啟動(dòng)的所有協(xié)同程序,因此您可能需要保證這些協(xié)程在返回之前完成。 通過(guò)Kotlin中的結構化并發(fā),您可以定義一個(gè)啟動(dòng)一個(gè)或多個(gè)協(xié)同程序的coroutineScope。 然后,使用await()(對于單個(gè)協(xié)同程序)或awaitAll()(對于多個(gè)協(xié)程),可以保證這些協(xié)程在從函數返回之前完成。
例如,讓我們定義一個(gè)以異步方式獲取兩個(gè)文檔的coroutineScope。 通過(guò)在每個(gè)延遲引用上調用await(),我們保證在返回值之前兩個(gè)異步操作都完成:
suspend fun fetchTwoDocs() = coroutineScope { val deferredOne = async { fetchDoc(1) } val deferredTwo = async { fetchDoc(2) } deferredOne.await() deferredTwo.await() }
即使fetchTwoDocs()使用異步啟動(dòng)新的協(xié)同程序,該函數也會(huì )使用awaitAll()等待那些啟動(dòng)的協(xié)同程序在返回之前完成。 但請注意,即使我們沒(méi)有調用awaitAll(),coroutineScope構建器也不會(huì )恢復調用fetchTwoDocs的協(xié)程,直到所有新的協(xié)程完成。
此外,coroutineScope捕獲協(xié)程拋出的任何異常并將它們路由回調用者。
有關(guān)并行分解的更多信息,請參閱編寫(xiě)掛起函數。
具有內置支持的架構組件
一些體系結構組件(包括ViewModel和Lifecycle)通過(guò)其自己的CoroutineScope成員包含對協(xié)同程序的內置支持。例如,ViewModel包含一個(gè)內置的viewModelScope。 這提供了在ViewModel范圍內啟動(dòng)協(xié)同程序的標準方法,如以下示例所示:
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還使用帶有liveData塊的協(xié)同程序:liveData { // runs in its own LiveData-specific scope}
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站