Kotlin 協程 看這一篇就夠了

語言: CN / TW / HK

前言

Kotlin協程是什麼,如何使用?如何結合Retrofit使用?Kotlin協程的優勢在哪裡?相信看完這一篇你一定有所收穫!

協程基本使用

若使用協程,首先我們得引入協程相關的開發包

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7-mpp-dev-11'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7-mpp-dev-11'
複製程式碼

上面一個是java環境的,下面一個是android環境下的,這裡我們都引入進來。

協程最簡單的使用,我們可以使用GlobalScope.launch去開啟一個協程程式碼如下所示:

GlobalScope.launch {
    Log.d(TAG, Thread.currentThread().name)
}
複製程式碼

執行結果如下所示:

說明這段程式碼是執行在一個子執行緒中的,當然我們可以再launch中傳遞引數,讓他執行在主執行緒中:

GlobalScope.launch(Dispatchers.Main) {
    Log.d(TAG, Thread.currentThread().name)
}
複製程式碼

這樣裡面的這段程式碼就執行在主執行緒中了,由此可見 協程是什麼?簡單的說就是協程可以讓我們開啟一個執行緒,是一個執行緒框架。當然實際專案中,開啟協程我們不會使用上面的方法。別急,我們要循序漸進~

協程和執行緒相比 有什麼優勢

協程和執行緒相比的優勢,我們直接用一個例子來說明,比如當前我們要去請求網路,拿到資料後將資料顯示出來,這裡我們模擬兩個方法,分別為 網路請求獲取資料 和 將資料顯示在UI上,我們定義方法如下:

/**
 * 從伺服器取資訊
 */
private fun getMessageFromNetwork(): String {

    for (i in 0..1000000) {
        //這裡模擬一個耗時操作
    }

    var name = "Huanglinqing"
    return name
}

/**
 * 顯示資訊
 * @message :資訊
 */
private fun showMessage(message: String) {
    tvName.text = message
}
複製程式碼

由於getMessage是一個耗時操作,所以我們將他放在子執行緒中,而在Android中 UI更新操作不能放在子執行緒中,所以我們必須將showMessage方法放在UI執行緒中,在之前我們實現這種需求 只能自己去執行切執行緒的程式碼,程式碼如下所示

/**
 * 從伺服器取資訊
 */
private fun getMessageFromNetwork() {

    for (i in 0..1000000) {
        //這裡模擬一個耗時操作
    }

    var name = "Huanglinqing"
    runOnUiThread {
        showMessage(name)
    }
}
複製程式碼

在onCreate中執行如下程式碼:

Thread {
    getMessageFromNetwork()
}.start()
複製程式碼

這樣呢 看起來還好,但是如果現在需求改為:請求第一個 後 顯示出來,再請求第二個 顯示出來呢,程式碼就是這個樣子

private fun getMessageFromNetwork() {

    for (i in 0..1000000) {
        //這裡模擬一個耗時操作
    }

    var name = "Huanglinqing"
    runOnUiThread {
        showMessage(name)
        Thread{
            getMessageFromNetwork1()
            runOnUiThread{

            }
        }.start()
    }
}
複製程式碼

依次類推,我們可以想到,如果請求很多的話,第一 程式碼結構會很難看,第二 寫著寫著就很亂了,那麼協程就可以很好的解決這個問題,下面我們來看使用協程的方式 怎麼寫.

首先,對於一個耗時的操作,我們需要將他切換到後臺執行緒執行,withContext函式可以構建一個協程作用域,他必須在掛起函式或者協程中執行,suspend關鍵字是kotlin為我們提供的 用於標記掛起函式的關鍵字。我們修改getMessageFromNetwork方法如下:

/**
 * 從伺服器取資訊
 */
private suspend fun getMessageFromNetwork(): String {

    var name = ""
    withContext(Dispatchers.IO) {
        for (i in 0..1000000) {
            //這裡模擬一個耗時操作
        }

        name = "Huanglinqing1111"
    }

    return name
}
複製程式碼

在onCreate中協程中直接這樣寫:

GlobalScope.launch(Dispatchers.Main) {
    var name = getMessageFromNetwork()
    showMessage(name)
}
複製程式碼

執行結果如下所示:

如果我們有多個請求呢,那就再多加幾個

GlobalScope.launch(Dispatchers.Main) {
    var name = getMessageFromNetwork()
    showMessage(name)
    var name1 = getMessageFromNetwork()
    showMessage(name1)
    var name2 = getMessageFromNetwork()
    showMessage(name2)
}
複製程式碼

這樣getMessageFromNetwork在後臺執行,showMessage在前臺執行,由此看來。

協程比執行緒的優勢在什麼地方?

1、協程可以幫我們自動切執行緒

2、擺脫了鏈式回撥的問題

Retrofit 如何使用協程

從Retrofit2.6.0開始,retrofit就自動支援協程了,這裡我們從「聚合資料」上找到一個開放api

我們先來看之前我們怎麼使用的,首先在Apiservice中定義一個介面如下:

@GET("https://wanandroid.com/article/listproject/0/json")
fun queryData(): Call<BaseReqData<DataReqBean>>
複製程式碼

在activity中新增如下程式碼:

var retrofit = Retrofit.Builder()
    .baseUrl("http://v.juhe.cn/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

val apiService = retrofit.create(ApiService::class.java)

tvName.setOnClickListener {
    apiService.queryData("top","04ea095cbea56775e2d1669713f34cc2")
        .enqueue(object :Callback<BaseReqData<NewBean>>{
            override fun onFailure(call: Call<BaseReqData<NewBean>>, t: Throwable) {
                tvName.text = t.toString()
                Log.d("網路請求錯誤", t.toString())
            }

            override fun onResponse(
                call: Call<BaseReqData<NewBean>>,
                response: Response<BaseReqData<NewBean>>
            ) {
                tvName.text = response.code().toString()
            }
        })
}
複製程式碼

這裡我們將返回結果的狀態碼顯示在view上,執行結果如圖所示:

上面程式碼看起來沒有什麼問題,如果我們用到了mvp模式什麼的,便於職責單一,還要單獨放一個類中,這樣就需要添加回調才能獲取返回結果。

那麼協程中怎麼使用呢?

首先我們在ApiService中新增一個函式 ,宣告為掛起函式,型別不需要新增Call

@GET("toutiao/index")
suspend fun queryDataKotlin(@Query("type") type: String?, @Query("key") key: String?): BaseReqData<NewBean>
複製程式碼

在onCreate中程式碼如下所示:

GlobalScope.launch(Dispatchers.Main) {
    try {
        var result = apiService.queryDataKotlin("top", "04ea095cbea56775e2d1669713f34cc2")
        tvName.text = result.toString()
    }catch (e:Exception){
        tvName.text = e.toString()
    }
}
複製程式碼

沒錯就是這麼簡單,沒有什麼回撥,因為queryDataKotlin是一個掛起函式,當執行到掛起函式的時候,協程會處於等待狀態,等返回結果後,主動切回主執行緒,執行下面的方法。

而try catch的作用,就等同於上面onFailure的回撥,這個時候你可能會說了,我去!還要寫try catch ,好low的感覺,別忘了,協程的另一個優勢就是可以減少回撥,如果仍然有成功方法或者失敗方法 那還是走了回撥的邏輯!

協程提升效率

協程可以提升什麼效率,假設,我們現在有兩個介面請求,需要等到兩個介面都請求完畢的時候 顯示出結果,如果在之前會怎麼樣,先請求介面1 介面1有結果後再請求結果2,而協程可以做到,介面1和介面2同時請求,等請求結束後將結果合併顯示過來,這裡我們請求統一介面來模擬請求兩個不同的介面

GlobalScope.launch(Dispatchers.Main) {
    try {
        var result1 =
            async { apiService.queryDataKotlin("top", "04ea095cbea56775e2d1669713f34cc2") }
        var result2 =
            async { apiService.queryDataKotlin("top", "04ea095cbea56775e2d1669713f34cc2") }
        tvName.text = result1.await().toString() + "\n==\n" + result2.await().toString()+"介面2"
    } catch (e: Exception) {
        tvName.text = e.toString()
    }

}
複製程式碼

執行結果如下所示:

這樣,本來要分步做的兩件事情可以同時做了,當然可以提高效率了,async函式必須在協程作用域中呼叫,會建立一個新的子協程,並返回一個Deferred物件,呼叫這個物件的await方法 就可以獲取執行結果。

在線上專案中如何使用協程

建立協程的方法有很多,有我們上面說的GlobalScope.launch方法,還有runBlocking方法

GlobalScope.launch 建立的是頂級協程,runBlocking建立的協程在協程作用域的程式碼沒有執行完畢前會一直阻塞執行緒,所以上面。兩個方法都不建議使用。

coroutineScope函式是一個掛起函式,它會繼承外部的協程作用域並建立一個子協程,只能在協程作用域或者掛起函式中呼叫

launch函式必須在協程的作用域中才能呼叫。

說了這麼多 在專案中我們改如何建立協程呢

我們可以直接建立一個CoroutineScope物件,如下所示:

var coroutineScope = CoroutineScope(Dispatchers.Main)
複製程式碼

coroutineScope.launch {

}
複製程式碼

這樣我們就建立了一個協程,可以按照上面的方法使用了

如果在頁面開啟的時候,我們在協程中進行網路請求,當頁面銷燬的時候我們也要將協程任務取消以免造成不必要的問題

如何取消協程任務

coroutineScope 直接呼叫cancle方法即可,如果我們使用的是GlobalScope.launch方法 ,它會返回一個job物件 我們使用job.cancle即可取消協程任務。

最後的最後,協程的強大遠遠不止上述

在專案中使用Jetpack 對kotlin的擴充套件,可以更加便捷的建立和使用協程.

如下:

lifecycleScope.launch { 

}
複製程式碼

viewModelScope.launch {

}
複製程式碼

可那又是另外一個故事了~

Android JetPack系列文章 已完結