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

前言

最近在研究 Kotlin 協程,發現功能真的超級強大,頗有用,並且很好學,若是你正在或計劃使用 Kotlin 開發 Android,那麼 Kotlin 協程你必定不能錯過!android

協程是什麼?

咱們日常接觸的都是進程、線程,在開發中使用最多的就是線程,好比主線程、子線程,並且操做系統裏最小可操做的單元就是線程,那協程又是什麼?協程是比線程更小的單位,但並非說在操做系統裏最小可操做單元就從線程變成了協程,而是協程依然運行在線程上,協程是在語言上實現的比線程更小的單位。git

那麼你可能有疑問,既然協程仍是運行在線程上,那直接使用線程不就行了嗎?但問題是每每咱們用很差線程,首先建立一個線程的成本很高,在 Android 中建立一個線程,大約要消耗 1M 的內存,並且,若是使用線程池,線程間數據的同步也是一個很是麻煩複雜的事情,因此就有了協程:github

  • 能夠看做是輕量級線程,建立一個協程的成本很低
  • 能夠輕鬆的掛起和恢復操做
  • 支持阻塞線程的協程和不阻塞線程的協程
  • 能夠更好的實現異步和併發

若是簡單來理解 Kotlin 協程的話,就是封裝好的線程池。bash

Kotlin協程庫:Kotlin.coroutines

實現協程的庫是 Kotlin.coroutines,點擊查看 Kotlin.coroutines 在 Github 上的源碼。併發

Kotlin 是一門支持 多平臺的語言,因此 Kotlin.coroutines 也是支持多平臺的,包括:app

  • Kotlin/JS
  • Kotlin/Native 包括 PC 和 Android

咱們使用 Kotlin.coroutines 的 Android 版本。異步

給 Android 工程添加 Kotlin 協程庫

要使用協程,Kotlin 的版本必須在1.3以上。 ide

升級 Kotlin 到 最新版本 1.3.+

在 Android Studio 中選擇 Android Stuido -> Preference... -> Languages & Framewroks -> Kotlin函數

在這裏升級 Kotlinfetch

建立使用 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 方法:

  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 協程,你有沒有心動?

未完待續...

相關文章
相關標籤/搜索