在 Android 開發中使用 Kotlin 協程 (一) -- 初識 Kotlin 協程

小德_koude發表於2019-03-23

前言

最近在研究 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

在 Android 開發中使用 Kotlin 協程 (一) -- 初識 Kotlin 協程

在這裡升級 Kotlin

建立使用 Kotlin 開發的 Android 工程

在 Android Studio 中選擇 File -> New -> New Project...

在 Android 開發中使用 Kotlin 協程 (一) -- 初識 Kotlin 協程

在這個介面裡選中 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 方法:

  1. 在協程內部使用
  2. 在另一個 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()
    }

}
複製程式碼
  1. 完全不用擔心會阻塞主執行緒
  2. 用同步的方式來寫非同步程式碼
  3. 而且不用擔心記憶體洩露的問題

Kotlin 協程,你有沒有心動?

未完待續...

相關文章