前言
最近在研究 Kotlin 協程,發現功能真的超級強大,很有用,而且很好學,如果你正在或計劃使用 Kotlin 開發 Android,那麼 Kotlin 協程你一定不能錯過!
協程是什麼?
我們平常接觸的都是程式、執行緒,在開發中使用最多的就是執行緒,比如主執行緒、子執行緒,而且作業系統裡最小可操作的單元就是執行緒,那協程又是什麼?協程是比執行緒更小的單位,但並不是說在作業系統裡最小可操作單元就從執行緒變成了協程,而是協程依然執行線上程上,協程是在語言上實現的比執行緒更小的單位。
那麼你可能有疑問,既然協程還是執行線上程上,那直接使用執行緒不就好了嗎?但問題是往往我們用不好執行緒,首先建立一個執行緒的成本很高,在 Android 中建立一個執行緒,大約要消耗 1M 的記憶體,而且,如果使用執行緒池,執行緒間資料的同步也是一個非常麻煩複雜的事情,所以就有了協程:
- 可以看作是輕量級執行緒,建立一個協程的成本很低
- 可以輕鬆的掛起和恢復操作
- 支援阻塞執行緒的協程和不阻塞執行緒的協程
- 可以更好的實現非同步和併發
如果簡單來理解 Kotlin 協程的話,就是封裝好的執行緒池。
Kotlin協程庫:Kotlin.coroutines
實現協程的庫是 Kotlin.coroutines,點選檢視 Kotlin.coroutines 在 Github 上的原始碼。
Kotlin 是一門支援 多平臺的語言,所以 Kotlin.coroutines 也是支援多平臺的,包括:
- Kotlin/JS
- Kotlin/Native 包括 PC 和 Android
我們使用 Kotlin.coroutines 的 Android 版本。
給 Android 工程新增 Kotlin 協程庫
要使用協程,Kotlin 的版本必須在1.3以上。
升級 Kotlin 到 最新版本 1.3.+
在 Android Studio 中選擇 Android Stuido
-> Preference...
-> Languages & Framewroks
-> Kotlin
在這裡升級 Kotlin
建立使用 Kotlin 開發的 Android 工程
在 Android Studio 中選擇 File
-> New
-> New Project...
在這個介面裡選中 Include Kotlin support
,剩下的和建立一般 Android 工程是一樣的。
配置 Kotlin 協程庫的依賴
在 app/build.gradle
裡新增 Kotlin 協程庫的依賴:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
複製程式碼
建立 Coroutine(協程):Coroutine Builder
建立Coroutine需要使用 Coroutine Builder 函式,包括:
作用 | |
---|---|
launch | 建立一個不會阻塞當前執行緒、沒有返回結果的 Coroutine,但會返回一個 Job 物件,可以用於控制這個 Coroutine 的執行和取消 |
runBlocking | 建立一個會阻塞當前執行緒的Coroutine |
其實不止這兩個,但本篇先介紹這兩個。
launch
使用 GlobalScope.launch
來建立協程,使用方法如下:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var job = GlobalScope.launch(Dispatchers.Main) {
var content = fetchData()
Log.d("Coroutine",content)
}
}
suspend fun fetchData():String{
delay(2000)
return "content"
}
複製程式碼
Activity 的 onCreate() 裡,用 GlobalScope.launch
建立一個協程,在協程裡我模擬了一個請求,去獲取資料,然後把資料列印出來。
因為 GlobalScope.launch
是無阻塞
的,所以不會阻塞 UI 執行緒。
這裡 GlobalScope.launch
建立之後,會返回一個 Job 物件,Job 物件可以這麼使用:
- job.cancel() : 取消協程
- job.join() :讓協程執行完
runBlocking
使用 runBlocking
來建立協程,使用方法如下:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
runBlocking {
var content = fetchData()
Log.d("Coroutine",content)
}
}
suspend fun fetchData():String{
delay(2000)
return "content"
}
複製程式碼
功能和上一個例子一樣,但是這裡協程建立改成了 runBlocking()
,但是 runBlocking()
是會阻塞執行緒的,所以這裡會阻塞 UI 執行緒,這裡是一個錯誤用例的示範(Orz...)
suspend 方法
在前面介紹協程的程式碼裡,有個不起眼的函式:
suspend fun fetchData():String{
delay(2000)
return "content"
}
複製程式碼
suspend
方法是協程裡的特有方法。
suspend 方法的定義
suspend
方法的宣告很簡單,只需在方法 或 Lambda 定義前面加 suspend
關鍵字即可。
suspend 方法的使用限制
suspend 方法使用由限制,只能有兩個地方允許使用 suspend
方法:
- 在協程內部使用
- 在另一個 suspend 方法裡使用
如果你在一個普通方法記憶體使用 suspend 方法,是會報語法錯誤的。
suspend 方法的功能
suspend 方法能夠使協程執行暫停,等執行完畢後在返回結果,同時不會阻塞執行緒。
是不是很神奇,只暫停協程,但不阻塞執行緒。
而且暫停協程裡方法的執行,直到方法返回結果,這樣也不用寫 Callback 來取結果,可以使用同步的方式來寫非同步程式碼,真是漂亮啊。
Coroutine context 與 Coroutine dispatchers
想要使用協程,還有兩個重要的元素:
- Coroutine context:協程上下文
- Coroutine dispatchers :協程排程
Coroutine context:協程上下文
協程上下文裡是各種元素的集合。具體的之後的文章在講。
Coroutine dispatchers :協程排程
我們已經知道協程是執行線上程上的,我們獲取資料後要更新 UI ,但是在 Android 裡更新 UI 只能在主執行緒,所以我們要在子執行緒裡獲取資料,然後在主執行緒裡更新 UI。這就需要改變協程的執行執行緒,這就是 Coroutine dispatchers 的功能了。
Coroutine dispatchers 可以指定協程執行在 Android 的哪個執行緒裡。
我們先看下 dispatchers 有哪些種類:
作用 | |
---|---|
Dispatchers.Default | 共享後臺執行緒池裡的執行緒 |
Dispatchers.Main | Android主執行緒 |
Dispatchers.IO | 共享後臺執行緒池裡的執行緒 |
Dispatchers.Unconfined | 不限制,使用父Coroutine的現場 |
newSingleThreadContext | 使用新的執行緒 |
在看前面的程式碼裡,細心的你肯定發現:
var job = GlobalScope.launch(Dispatchers.Main) {
var content = fetchData()
Log.d("Coroutine",content)
}
複製程式碼
GlobalScope.launch 後面的 Dispatchers.Main
就是指定協程在 Android 主執行緒裡執行。
那麼,如何切換執行緒呢?如下:
GlobalScope.launch(Dispatchers.IO) {
...
withContext(Dispatchers.Main) {
...
}
}
複製程式碼
使用 withContext
切換協程,上面的例子就是先在 IO 執行緒裡執行,然後切換到主執行緒。
Android 開發中使用 協程
講完協程的基本用法,你還是不知道改如何用到自己的程式碼裡,這裡給出一個最基本的用法,後續的使用方法會不斷補充。
首先 MainActivity 要 實現 CoroutineScope
這個介面,CoroutineScope
的實現教由代理類 MainScope
,所以是這樣子的:
class MainActivity : AppCompatActivity(),CoroutineScope by MainScope()
複製程式碼
這樣 MainActivity 就是一個協程,那麼要獲取資料,並展示在 UI 上,就可以這麼寫:
class MainActivity : AppCompatActivity(),CoroutineScope by MainScope() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
//載入並顯示資料
loadDataAndShow()
}
fun loadDataAndShow(){
GlobalScope.launch(Dispatchers.IO) {
//IO 執行緒里拉取資料
var result = fetchData()
withContext(Dispatchers.Main){
//主執行緒裡更新 UI
text.setText(result)
}
}
}
suspend fun fetchData():String{
delay(2000)
return "content"
}
override fun onDestroy() {
super.onDestroy()
//取消掉所有協程內容
cancel()
}
}
複製程式碼
- 完全不用擔心會阻塞主執行緒
- 用同步的方式來寫非同步程式碼
- 而且不用擔心記憶體洩露的問題
Kotlin 協程,你有沒有心動?
未完待續...