Kotlin 中的協程提供了一種全新處理併發的方式,您能夠在 Android 平臺上使用它來簡化異步執行的代碼。協程是從 Kotlin 1.3 版本開始引入,但這一律念在編程世界誕生的黎明之際就有了,最先使用協程的編程語言能夠追溯到 1967 年的 Simula 語言。javascript
在過去幾年間,協程這個概念發展勢頭迅猛,現已經被諸多主流編程語言採用,好比 Javascript、C#、Python、Ruby 以及 Go 等。Kotlin 的協程是基於來自其餘語言的既定概念。html
在 Android 平臺上,協程主要用來解決兩個問題:java
讓咱們來深刻上述問題,看看該如何將協程運用到咱們代碼中。python
獲取網頁內容或與遠程 API 交互都會涉及到發送網絡請求,從數據庫裏獲取數據或者從磁盤中讀取圖片資源涉及到文件的讀取操做。一般咱們把這類操做歸類爲耗時任務 —— 應用會停下並等待它們處理完成,這會耗費大量時間。android
當今手機處理代碼的速度要遠快於處理網絡請求的速度。以 Pixel 2 爲例,單個 CPU 週期耗時低於 0.0000000004 秒,這個數字很難用人類語言來表述,然而,若是將網絡請求以 「眨眼間」 來表述,大概是 400 毫秒 (0.4 秒),則更容易理解 CPU 運行速度之快。僅僅是一眨眼的功夫內,或是一個速度比較慢的網絡請求處理完的時間內,CPU 就已完成了超過 10 億次的時鐘週期了。git
Android 中的每一個應用都會運行一個主線程,它主要是用來處理 UI (好比進行界面的繪製) 和協調用戶交互。若是主線程上須要處理的任務太多,應用運行會變慢,看上去就像是 「卡」 住了,這樣是很影響用戶體驗的。因此想讓應用運行上不 「卡」、作到動畫可以流暢運行或者可以快速響應用戶點擊事件,就得讓那些耗時的任務不阻塞主線程的運行。github
要作處處理網絡請求不會阻塞主線程,一個經常使用的作法就是使用回調。回調就是在以後的某段時間去執行您的回調代碼,使用這種方式,請求 developer.android.google.cn 的網站數據的代碼就會相似於下面這樣:golang
class ViewModel: ViewModel() {
fun fetchDocs() {
get("developer.android.google.cn") { result ->
show(result)
}
}
}
複製代碼
在上面示例中,即便 get 是在主線程中調用的,可是它會使用另一個線程來執行網絡請求。一旦網絡請求返回結果,result 可用後,回調代碼就會被主線程調用。這是一個處理耗時任務的好方法,相似於 Retrofit 這樣的庫就是採用這種方式幫您處理網絡請求,並不會阻塞主線程的執行。數據庫
使用協程能夠簡化您的代碼來處理相似 fetchDocs 這樣的耗時任務。咱們先用協程的方法來重寫上面的代碼,以此來說解協程是如何處理耗時任務,從而使代碼更清晰簡潔的。編程
// Dispatchers.Main
suspend fun fetchDocs() {
// Dispatchers.Main
val result = get("developer.android.google.cn")
// Dispatchers.Main
show(result)
}
// 在接下來的章節中查看這段代碼
suspend fun get(url: String) = withContext(Dispatchers.IO){/*...*/}
複製代碼
在上面的示例中,您可能會有不少疑問,難道它不會阻塞主線程嗎?get 方法是如何作到不等待網絡請求和線程阻塞而返回結果的?其實,是 Kotlin 中的協程提供了這種執行代碼而不阻塞主線程的方法。
協程在常規函數的基礎上新增了兩項操做。在invoke (或 call) 和 return 以外,協程新增了 suspend 和 resume:
Kotlin 經過新增 suspend 關鍵詞來實現上面這些功能。您只可以在 suspend 函數中調用另外的 suspend 函數,或者經過協程構造器 (如 launch) 來啓動新的協程。
搭配使用 suspend 和 resume 來替代回調的使用。
在上面的示例中,get 仍在主線程上運行,但它會在啓動網絡請求以前暫停協程。當網絡請求完成時,get 會恢復已暫停的協程,而不是使用回調來通知主線程。
當主線程下全部的協程都被暫停,主線程處理別的事件時就會毫無壓力。
即便代碼可能看起來像普通的順序阻塞請求,協程也能確保網絡請求避免阻塞主線程。
接下來,讓咱們來看一下協程是如何保證主線程安全 (main-safety),並來探討一下調度器。
在 Kotlin 的協程中,主線程調用編寫良好的 suspend 函數一般是安全的。無論那些 suspend 函數是作什麼的,它們都應該容許任何線程調用它們。
可是在咱們的 Android 應用中有不少的事情處理起來太慢,是不該該放在主線程上去作的,好比網絡請求、解析 JSON 數據、從數據庫中進行讀寫操做,甚至是遍歷比較大的數組。這些會致使執行時間長從而讓用戶感受很 「卡」 的操做都不該該放在主線程上執行。
使用 suspend 並不意味着告訴 Kotlin 要在後臺線程上執行一個函數,這裏要強調的是,協程會在主線程上運行。事實上,當要響應一個 UI 事件從而啓動一個協程時,使用 Dispatchers.Main.immediate 是一個很是好的選擇,這樣的話哪怕是最終沒有執行須要保證主線程安全的耗時任務,也能夠在下一幀中給用戶提供可用的執行結果。
協程會在主線程中運行,suspend 並不表明後臺執行。
若是須要處理一個函數,且這個函數在主線程上執行太耗時,可是又要保證這個函數是主線程安全的,那麼您可讓 Kotlin 協程在 Default 或 IO 調度器上執行工做。在 Kotlin 中,全部協程都必須在調度器中運行,即便它們是在主線程上運行也是如此。協程能夠自行暫停,而調度器負責將其恢復。
Kotlin 提供了三個調度器,您可使用它們來指定應在何處運行協程:
接着前面的示例來說,您可使用調度器來從新定義 get 函數。在 get 的主體內,調用 withContext(Dispatchers.IO) 來建立一個在 IO 線程池中運行的塊。您放在該塊內的任何代碼都始終經過 IO 調度器執行。因爲 withContext 自己就是一個 suspend 函數,它會使用協程來保證主線程安全。
// Dispatchers.Main
suspend fun fetchDocs() {
// Dispatchers.Main
val result = get("developer.android.google.cn")
// Dispatchers.Main
show(result)
}
// Dispatchers.Main
suspend fun get(url: String) =
// Dispatchers.Main
withContext(Dispatchers.IO) {
// Dispatchers.IO
}
// Dispatchers.Main
複製代碼
藉助協程,您能夠經過精細控制來調度線程。因爲 withContext 可以讓您在不引入回調的狀況下控制任何代碼行的線程池,所以您能夠將其應用於很是小的函數,如從數據庫中讀取數據或執行網絡請求。一種不錯的作法是使用 withContext 來確保每一個函數都是主線程安全的,這意味着,您能夠從主線程調用每一個函數。這樣,調用方就無需再考慮應該使用哪一個線程來執行函數了。
在這個示例中,fetchDocs 會在主線程中執行,不過,它能夠安全地調用 get 來在後臺執行網絡請求。由於協程支持 suspend 和 resume,因此一旦 withContext 塊完成後,主線程上的協程就會恢復繼續執行。
主線程調用編寫良好的 suspend 函數一般是安全的。
確保每一個 suspend 函數都是主線程安全的是頗有用的。若是某個任務是須要接觸到磁盤、網絡,甚至只是佔用過多的 CPU,那應該使用 withContext 來確保能夠安全地從主線程進行調用。這也是相似於 Retrofit 和 Room 這樣的代碼庫所遵循的原則。若是您在寫代碼的過程當中也遵循這一點,那麼您的代碼將會變得很是簡單,而且不會將線程問題與應用邏輯混雜在一塊兒。同時,協程在這個原則下也能夠被主線程自由調用,網絡請求或數據庫操做代碼也變得很是簡潔,還能確保用戶在使用應用的過程當中不會以爲 「卡」。
withContext 同回調或者是提供主線程安全特性的 RxJava 相比的話,性能是差很少的。在某些狀況下,甚至還能夠優化 withContext 調用,讓它的性能超越基於回調的等效實現。若是某個函數須要對數據庫進行 10 次調用,您可使用外部 withContext 來讓 Kotlin 只切換一次線程。這樣一來,即便數據庫的代碼庫會不斷調用 withContext,它也會留在同一調度器並跟隨快速路徑,以此來保證性能。此外,在 Dispatchers.Default 和 Dispatchers.IO 中進行切換也獲得了優化,以儘量避免了線程切換所帶來的性能損失。
本篇文章介紹了使用協程來解決什麼樣的問題。協程是一個計算機編程語言領域比較古老的概念,但由於它們可以讓網絡請求的代碼比較簡潔,從而又開始流行起來。
在 Android 平臺上,您可使用協程來處理兩個常見問題:
接下來的文章中咱們將繼續探討協程在 Android 中是如何使用的,感興趣的讀者請繼續關注。
點擊這裏利用 Kotlin 協程提高應用性能