自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

在 Android 開發(fā)中使用協(xié)程 | 背景介紹

移動(dòng)開發(fā) Android
本文是介紹 Android 協(xié)程系列中的第一部分,主要會(huì)介紹協(xié)程是如何工作的,它們主要解決什么問題。

本文是介紹 Android 協(xié)程系列中的第一部分,主要會(huì)介紹協(xié)程是如何工作的,它們主要解決什么問題。

[[321378]]

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

Kotlin 中的協(xié)程提供了一種全新處理并發(fā)的方式,您可以在 Android 平臺(tái)上使用它來簡化異步執(zhí)行的代碼。協(xié)程是從 Kotlin 1.3 版本開始引入,但這一概念在編程世界誕生的黎明之際就有了,最早使用協(xié)程的編程語言可以追溯到 1967 年的 Simula 語言。

在過去幾年間,協(xié)程這個(gè)概念發(fā)展勢頭迅猛,現(xiàn)已經(jīng)被諸多主流編程語言采用,比如 Javascript、C#、Python、Ruby 以及 Go 等。Kotlin 的協(xié)程是基于來自其他語言的既定概念。

  • 協(xié)程:https://kotlinlang.org/docs/reference/coroutines-overview.html
  • Simula:https://en.wikipedia.org/wiki/Simula
  • Javascript:https://javascript.info/async-await
  • C#:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
  • Python:https://docs.python.org/3/library/asyncio-task.html
  • Ruby:https://ruby-doc.org/core-2.1.1/Fiber.html
  • Go:https://tour.golang.org/concurrency/1

在 Android 平臺(tái)上,協(xié)程主要用來解決兩個(gè)問題:

  • 處理耗時(shí)任務(wù) (Long running tasks),這種任務(wù)常常會(huì)阻塞住主線程;
  • 保證主線程安全 (Main-safety) ,即確保安全地從主線程調(diào)用任何 suspend 函數(shù)。

讓我們來深入上述問題,看看該如何將協(xié)程運(yùn)用到我們代碼中。

處理耗時(shí)任務(wù)

獲取網(wǎng)頁內(nèi)容或與遠(yuǎn)程 API 交互都會(huì)涉及到發(fā)送網(wǎng)絡(luò)請(qǐng)求,從數(shù)據(jù)庫里獲取數(shù)據(jù)或者從磁盤中讀取圖片資源涉及到文件的讀取操作。通常我們把這類操作歸類為耗時(shí)任務(wù) —— 應(yīng)用會(huì)停下并等待它們處理完成,這會(huì)耗費(fèi)大量時(shí)間。

當(dāng)今手機(jī)處理代碼的速度要遠(yuǎn)快于處理網(wǎng)絡(luò)請(qǐng)求的速度。以 Pixel 2 為例,單個(gè) CPU 周期耗時(shí)低于 0.0000000004 秒,這個(gè)數(shù)字很難用人類語言來表述,然而,如果將網(wǎng)絡(luò)請(qǐng)求以 “眨眼間” 來表述,大概是 400 毫秒 (0.4 秒),則更容易理解 CPU 運(yùn)行速度之快。僅僅是一眨眼的功夫內(nèi),或是一個(gè)速度比較慢的網(wǎng)絡(luò)請(qǐng)求處理完的時(shí)間內(nèi),CPU 就已完成了超過 10 億次的時(shí)鐘周期了。

Android 中的每個(gè)應(yīng)用都會(huì)運(yùn)行一個(gè)主線程,它主要是用來處理 UI (比如進(jìn)行界面的繪制) 和協(xié)調(diào)用戶交互。如果主線程上需要處理的任務(wù)太多,應(yīng)用運(yùn)行會(huì)變慢,看上去就像是 “卡” 住了,這樣是很影響用戶體驗(yàn)的。所以想讓應(yīng)用運(yùn)行上不 “卡”、做到動(dòng)畫能夠流暢運(yùn)行或者能夠快速響應(yīng)用戶點(diǎn)擊事件,就得讓那些耗時(shí)的任務(wù)不阻塞主線程的運(yùn)行。

要做到處理網(wǎng)絡(luò)請(qǐng)求不會(huì)阻塞主線程,一個(gè)常用的做法就是使用回調(diào)?;卣{(diào)就是在之后的某段時(shí)間去執(zhí)行您的回調(diào)代碼,使用這種方式,請(qǐng)求 developer.android.google.cn 的網(wǎng)站數(shù)據(jù)的代碼就會(huì)類似于下面這樣:

  1. class ViewModel: ViewModel() { 
  2.    fun fetchDocs() { 
  3.        get("developer.android.google.cn") { result -> 
  4.            show(result) 
  5.        } 
  6.     } 

在上面示例中,即使 get 是在主線程中調(diào)用的,但是它會(huì)使用另外一個(gè)線程來執(zhí)行網(wǎng)絡(luò)請(qǐng)求。一旦網(wǎng)絡(luò)請(qǐng)求返回結(jié)果,result 可用后,回調(diào)代碼就會(huì)被主線程調(diào)用。這是一個(gè)處理耗時(shí)任務(wù)的好方法,類似于 Retrofit 這樣的庫就是采用這種方式幫您處理網(wǎng)絡(luò)請(qǐng)求,并不會(huì)阻塞主線程的執(zhí)行。

Retrofi:thttps://square.github.io/retrofit/

使用協(xié)程來處理協(xié)程任務(wù)

使用協(xié)程可以簡化您的代碼來處理類似 fetchDocs 這樣的耗時(shí)任務(wù)。我們先用協(xié)程的方法來重寫上面的代碼,以此來講解協(xié)程是如何處理耗時(shí)任務(wù),從而使代碼更清晰簡潔的。

  1. // Dispatchers.Main 
  2. suspend fun fetchDocs() { 
  3.     // Dispatchers.Main 
  4.     val result = get("developer.android.google.cn") 
  5.     // Dispatchers.Main 
  6.     show(result) 
  7. // 在接下來的章節(jié)中查看這段代碼 
  8. suspend fun get(url: String) = withContext(Dispatchers.IO){/*...*/} 

在上面的示例中,您可能會(huì)有很多疑問,難道它不會(huì)阻塞主線程嗎?get 方法是如何做到不等待網(wǎng)絡(luò)請(qǐng)求和線程阻塞而返回結(jié)果的?其實(shí),是 Kotlin 中的協(xié)程提供了這種執(zhí)行代碼而不阻塞主線程的方法。

協(xié)程在常規(guī)函數(shù)的基礎(chǔ)上新增了兩項(xiàng)操作。在 invoke (或 call) 和 return 之外,協(xié)程新增了 suspend 和 resume:

  • suspend — 也稱掛起或暫停,用于暫停執(zhí)行當(dāng)前協(xié)程,并保存所有局部變量;
  • resume — 用于讓已暫停的協(xié)程從其暫停處繼續(xù)執(zhí)行。

Kotlin 通過新增 suspend 關(guān)鍵詞來實(shí)現(xiàn)上面這些功能。您只能夠在 suspend 函數(shù)中調(diào)用另外的 suspend 函數(shù),或者通過協(xié)程構(gòu)造器 (如 launch) 來啟動(dòng)新的協(xié)程。

  • launch:https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html

(1) 搭配使用 suspend 和 resume 來替代回調(diào)的使用

在上面的示例中,get 仍在主線程上運(yùn)行,但它會(huì)在啟動(dòng)網(wǎng)絡(luò)請(qǐng)求之前暫停協(xié)程。當(dāng)網(wǎng)絡(luò)請(qǐng)求完成時(shí),get 會(huì)恢復(fù)已暫停的協(xié)程,而不是使用回調(diào)來通知主線程。

上述動(dòng)畫展示了 Kotlin 如何使用 suspend 和 resume 來代替回調(diào)

觀察上圖中 fetchDocs 的執(zhí)行,就能明白 suspend 是如何工作的。Kotlin 使用堆棧幀來管理要運(yùn)行哪個(gè)函數(shù)以及所有局部變量。暫停協(xié)程時(shí),會(huì)復(fù)制并保存當(dāng)前的堆棧幀以供稍后使用?;謴?fù)協(xié)程時(shí),會(huì)將堆棧幀從其保存位置復(fù)制回來,然后函數(shù)再次開始運(yùn)行。在上面的動(dòng)畫中,當(dāng)主線程下所有的協(xié)程都被暫停,主線程處理屏幕繪制和點(diǎn)擊事件時(shí)就會(huì)毫無壓力。所以用上述的 suspend 和 resume 的操作來代替回調(diào)看起來十分的清爽。

(2) 當(dāng)主線程下所有的協(xié)程都被暫停,主線程處理別的事件時(shí)就會(huì)毫無壓力

即使代碼可能看起來像普通的順序阻塞請(qǐng)求,協(xié)程也能確保網(wǎng)絡(luò)請(qǐng)求避免阻塞主線程。

接下來,讓我們來看一下協(xié)程是如何保證主線程安全 (main-safety),并來探討一下調(diào)度器。

使用協(xié)程保證主線程安全

在 Kotlin 的協(xié)程中,主線程調(diào)用編寫良好的 suspend 函數(shù)通常是安全的。不管那些 suspend 函數(shù)是做什么的,它們都應(yīng)該允許任何線程調(diào)用它們。

但是在我們的 Android 應(yīng)用中有很多的事情處理起來太慢,是不應(yīng)該放在主線程上去做的,比如網(wǎng)絡(luò)請(qǐng)求、解析 JSON 數(shù)據(jù)、從數(shù)據(jù)庫中進(jìn)行讀寫操作,甚至是遍歷比較大的數(shù)組。這些會(huì)導(dǎo)致執(zhí)行時(shí)間長從而讓用戶感覺很 “卡” 的操作都不應(yīng)該放在主線程上執(zhí)行。

使用 suspend 并不意味著告訴 Kotlin 要在后臺(tái)線程上執(zhí)行一個(gè)函數(shù),這里要強(qiáng)調(diào)的是,協(xié)程會(huì)在主線程上運(yùn)行。事實(shí)上,當(dāng)要響應(yīng)一個(gè) UI 事件從而啟動(dòng)一個(gè)協(xié)程時(shí),使用 Dispatchers.Main.immediate 是一個(gè)非常好的選擇,這樣的話哪怕是最終沒有執(zhí)行需要保證主線程安全的耗時(shí)任務(wù),也可以在下一幀中給用戶提供可用的執(zhí)行結(jié)果。

Dispatchers.Main.immediateh:

ttps://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-coroutine-dispatcher/immediate.html

(1) 協(xié)程會(huì)在主線程中運(yùn)行,suspend 并不代表后臺(tái)執(zhí)行。

如果需要處理一個(gè)函數(shù),且這個(gè)函數(shù)在主線程上執(zhí)行太耗時(shí),但是又要保證這個(gè)函數(shù)是主線程安全的,那么您可以讓 Kotlin 協(xié)程在 Default 或 IO 調(diào)度器上執(zhí)行工作。在 Kotlin 中,所有協(xié)程都必須在調(diào)度器中運(yùn)行,即使它們是在主線程上運(yùn)行也是如此。協(xié)程可以自行暫停,而調(diào)度器負(fù)責(zé)將其恢復(fù)。

Kotlin 提供了三個(gè)調(diào)度器,您可以使用它們來指定應(yīng)在何處運(yùn)行協(xié)程:

如果您在 Room 中使用了 suspend 函數(shù)、RxJava 或者 LiveData,Room 會(huì)自動(dòng)保障主線程安全。

類似于 Retrofit 和 Volley 這樣的網(wǎng)絡(luò)庫會(huì)管理它們自身所使用的線程,所以當(dāng)您在 Kotlin 協(xié)程中調(diào)用這些庫的代碼時(shí)不需要專門來處理主線程安全這一問題。

  • 調(diào)度器:https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/
  • suspend:https://medium.com/androiddevelopers/room-coroutines-422b786dc4c5
  • RxJava:https://medium.com/androiddevelopers/room-rxjava-acb0cd4f3757
  • LiveData:https://developer.android.google.cn/topic/libraries/architecture/livedata#use_livedata_with_room
  • Room:https://developer.android.google.cn/topic/libraries/architecture/room
  • Retrofit:https://square.github.io/retrofit/
  • Volley:https://developer.android.google.cn/training/volley

接著前面的示例來講,您可以使用調(diào)度器來重新定義 get 函數(shù)。在 get 的主體內(nèi),調(diào)用 withContext(Dispatchers.IO) 來創(chuàng)建一個(gè)在 IO 線程池中運(yùn)行的塊。您放在該塊內(nèi)的任何代碼都始終通過 IO 調(diào)度器執(zhí)行。由于 withContext 本身就是一個(gè) suspend 函數(shù),它會(huì)使用協(xié)程來保證主線程安全。

  1. // Dispatchers.Main 
  2. suspend fun fetchDocs() { 
  3.     // Dispatchers.Main 
  4.     val result = get("developer.android.google.cn") 
  5.     // Dispatchers.Main 
  6.     show(result) 
  7. // Dispatchers.Main 
  8. suspend fun get(url: String) = 
  9.     // Dispatchers.Main 
  10.     withContext(Dispatchers.IO) { 
  11.         // Dispatchers.IO 
  12.     } 
  13.     // Dispatchers.Main 

借助協(xié)程,您可以通過精細(xì)控制來調(diào)度線程。由于 withContext 可讓您在不引入回調(diào)的情況下控制任何代碼行的線程池,因此您可以將其應(yīng)用于非常小的函數(shù),如從數(shù)據(jù)庫中讀取數(shù)據(jù)或執(zhí)行網(wǎng)絡(luò)請(qǐng)求。一種不錯(cuò)的做法是使用 withContext 來確保每個(gè)函數(shù)都是主線程安全的,這意味著,您可以從主線程調(diào)用每個(gè)函數(shù)。這樣,調(diào)用方就無需再考慮應(yīng)該使用哪個(gè)線程來執(zhí)行函數(shù)了。

在這個(gè)示例中,fetchDocs 會(huì)在主線程中執(zhí)行,不過,它可以安全地調(diào)用 get 來在后臺(tái)執(zhí)行網(wǎng)絡(luò)請(qǐng)求。因?yàn)閰f(xié)程支持 suspend 和 resume,所以一旦 withContext 塊完成后,主線程上的協(xié)程就會(huì)恢復(fù)繼續(xù)執(zhí)行。

(2) 主線程調(diào)用編寫良好的 suspend 函數(shù)通常是安全的。

確保每個(gè) suspend 函數(shù)都是主線程安全的是很有用的。如果某個(gè)任務(wù)是需要接觸到磁盤、網(wǎng)絡(luò),甚至只是占用過多的 CPU,那應(yīng)該使用 withContext 來確??梢园踩貜闹骶€程進(jìn)行調(diào)用。這也是類似于 Retrofit 和 Room 這樣的代碼庫所遵循的原則。如果您在寫代碼的過程中也遵循這一點(diǎn),那么您的代碼將會(huì)變得非常簡單,并且不會(huì)將線程問題與應(yīng)用邏輯混雜在一起。同時(shí),協(xié)程在這個(gè)原則下也可以被主線程自由調(diào)用,網(wǎng)絡(luò)請(qǐng)求或數(shù)據(jù)庫操作代碼也變得非常簡潔,還能確保用戶在使用應(yīng)用的過程中不會(huì)覺得 “卡”。

withContext 的性能

withContext 同回調(diào)或者是提供主線程安全特性的 RxJava 相比的話,性能是差不多的。在某些情況下,甚至還可以優(yōu)化 withContext 調(diào)用,讓它的性能超越基于回調(diào)的等效實(shí)現(xiàn)。如果某個(gè)函數(shù)需要對(duì)數(shù)據(jù)庫進(jìn)行 10 次調(diào)用,您可以使用外部 withContext 來讓 Kotlin 只切換一次線程。這樣一來,即使數(shù)據(jù)庫的代碼庫會(huì)不斷調(diào)用 withContext,它也會(huì)留在同一調(diào)度器并跟隨快速路徑,以此來保證性能。此外,在 Dispatchers.Default 和 Dispatchers.IO 中進(jìn)行切換也得到了優(yōu)化,以盡可能避免了線程切換所帶來的性能損失。

下一步

本篇文章介紹了使用協(xié)程來解決什么樣的問題。協(xié)程是一個(gè)計(jì)算機(jī)編程語言領(lǐng)域比較古老的概念,但因?yàn)樗鼈兡軌蜃尵W(wǎng)絡(luò)請(qǐng)求的代碼比較簡潔,從而又開始流行起來。

在 Android 平臺(tái)上,您可以使用協(xié)程來處理兩個(gè)常見問題:

  • 簡化處理類似于網(wǎng)絡(luò)請(qǐng)求、磁盤讀取甚至是較大 JSON 數(shù)據(jù)解析這樣的耗時(shí)任務(wù);
  • 保證主線程安全,這樣可以在不增加代碼復(fù)雜度和保證代碼可讀性的前提下做到不會(huì)阻塞主線程的執(zhí)行。

下篇文章:《在 Android 開發(fā)中使用協(xié)程 | 上手指南

【本文是51CTO專欄機(jī)構(gòu)“谷歌開發(fā)者”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者(微信公眾號(hào):Google_Developers)】

戳這里,看該作者更多好文

 

責(zé)任編輯:趙寧寧 來源: 51CTO專欄
相關(guān)推薦

2020-07-07 09:19:28

Android 協(xié)程開發(fā)

2020-04-23 09:33:32

Android 協(xié)程開發(fā)

2019-10-23 14:34:15

KotlinAndroid協(xié)程

2023-10-24 19:37:34

協(xié)程Java

2021-08-04 16:19:55

AndroidKotin協(xié)程Coroutines

2023-12-22 09:11:45

AndroidNFC移動(dòng)開發(fā)

2021-09-16 09:59:13

PythonJavaScript代碼

2018-03-26 14:25:55

KubernetesSkaffold命令

2012-04-19 12:58:26

TitaniumJSS

2010-10-18 13:16:24

GalleryAndroid

2020-07-07 10:03:27

Android 協(xié)程開發(fā)

2023-11-17 11:36:59

協(xié)程纖程操作系統(tǒng)

2019-03-01 08:57:47

iOScoobjc協(xié)程

2025-02-08 09:13:40

2025-02-28 09:04:08

2021-12-09 06:41:56

Python協(xié)程多并發(fā)

2009-07-16 14:22:02

Windows Emb

2020-06-19 08:01:48

Kotlin 協(xié)程編程

2012-12-27 13:04:17

Android開發(fā)SQLite數(shù)據(jù)庫

2022-09-06 20:30:48

協(xié)程Context主線程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)