Kotlin協(xié)程的學習記錄

學習網(wǎng)站 https://kaixue.io/kotlin-coroutines-1/

記錄

Android

Add kotlinx-coroutines-android module as dependency when using kotlinx.coroutines on Android:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7'

文檔

https://www.kotlincn.net/docs/kotlin-docs.pdf

本質(zhì)上蚣驼,協(xié)程事輕量級的線程

基本使用

前面提到蒸痹,launch 函數(shù)不是頂層函數(shù),是不能直接用的曼氛,可以使用下面三種方法來創(chuàng)建協(xié)程:

???
// 方法一度宦,使用 runBlocking 頂層函數(shù)
runBlocking {
    getImage(imageId)
}

// 方法二誓酒,使用 GlobalScope 單例對象
//            ?? 可以直接調(diào)用 launch 開啟協(xié)程
GlobalScope.launch {
    getImage(imageId)
}

// 方法三钠惩,自行通過 CoroutineContext 創(chuàng)建一個 CoroutineScope 對象
//                                    ?? 需要一個類型為 CoroutineContext 的參數(shù)
val coroutineScope = CoroutineScope(context)
coroutineScope.launch {
    getImage(imageId)
}

Kotlin

  • 方法一通常適用于單元測試的場景秕狰,而業(yè)務開發(fā)中不會用到這種方法,因為它是線程阻塞的东跪。
  • 方法二和使用 runBlocking 的區(qū)別在于不會阻塞線程畸陡。但在 Android 開發(fā)中同樣不推薦這種用法,因為它的生命周期會和 app 一致虽填,且不能取消(什么是協(xié)程的取消后面的文章會講)丁恭。
  • 方法三是比較推薦的使用方法,我們可以通過 context 參數(shù)去管理和控制協(xié)程的生命周期(這里的 context 和 Android 里的不是一個東西斋日,是一個更通用的概念牲览,會有一個 Android 平臺的封裝來配合使用)。
???
coroutineScope.launch(Dispatchers.Main) {      // ?? 在 UI 線程開始
    val image = withContext(Dispatchers.IO) {  // ?? 切換到 IO 線程恶守,并在執(zhí)行完成后切回 UI 線程
        getImage(imageId)                      // ?? 將會運行在 IO 線程
    }
    avatarIv.setImageBitmap(image)             // ?? 回到 UI 線程更新 UI
} 

練習題

  1. 開啟一個協(xié)程第献,并在協(xié)程中打印出當前線程名。
  2. 通過協(xié)程下載一張網(wǎng)絡圖片并顯示出來熬的。
package com.tic.planb

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.tic.plan.Plan
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*
import java.net.HttpURLConnection
import java.net.URL

class MainActivity : AppCompatActivity() {
    private val TAG = "MainActivity"
    val imgUrl =
        "http://img1.gamersky.com/image2016/10/20161015_ls_141_5/gamersky_03origin_05_201610151947EAA.jpg"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        CoroutineScope(Dispatchers.Main).launch {
            Log.d(TAG, "當前線程${Thread.currentThread().name}")
            val bitmap = withContext(Dispatchers.IO) {
                Log.d(TAG, "當前線程${Thread.currentThread().name}")
                getImage(imgUrl)
            }
            mImageIv.setImageBitmap(bitmap)
        }
    }

    private fun getImage(imgUrl: String): Bitmap {
        val urlParam = URL(imgUrl)
        val openConnection = urlParam.openConnection() as HttpURLConnection
        openConnection.requestMethod = "GET"
        openConnection.connect()
        val inputStream = openConnection.inputStream
        return BitmapFactory.decodeStream(inputStream)
    }

}

記得開網(wǎng)絡權(quán)限

Kotlin 協(xié)程的掛起好神奇好難懂痊硕?我今天把他的皮給扒了

-朱凱說的

接下來我們繼續(xù)看看 async 是如何使用的,先回憶一下上期中獲取頭像的場景:

???
coroutineScope.launch(Dispatchers.Main) {
    //                      ??  async 函數(shù)啟動新的協(xié)程
    val avatar: Deferred = async { api.getAvatar(user) }    // 獲取用戶頭像
    val logo: Deferred = async { api.getCompanyLogo(user) } // 獲取用戶所在公司的 logo
    //            ??          ?? 獲取返回值
    show(avatar.await(), logo.await())                     // 更新 UI
}

Kotlin

可以看到 avatar 和 logo 的類型可以聲明為 Deferred 押框,通過 await 獲取結(jié)果并且更新到 UI 上顯示岔绸。

在了解了 suspend 關鍵字的來龍去脈之后,我們就可以進入下一個話題了:怎么自定義 suspend 函數(shù)橡伞。

這個「怎么自定義」其實分為兩個問題:

  • 什么時候需要自定義 suspend 函數(shù)盒揉?
  • 具體該怎么寫呢?

什么時候需要自定義 suspend 函數(shù)

如果你的某個函數(shù)比較耗時兑徘,也就是要等的操作刚盈,那就把它寫成 suspend 函數(shù)。這就是原則挂脑。

耗時操作一般分為兩類:I/O 操作和 CPU 計算工作藕漱。比如文件的讀寫、網(wǎng)絡交互崭闲、圖片的模糊處理肋联,都是耗時的,通通可以把它們寫進 suspend 函數(shù)里刁俭。

另外這個「耗時」還有一種特殊情況橄仍,就是這件事本身做起來并不慢,但它需要等待,比如 5 秒鐘之后再做這個操作侮繁。這種也是 suspend 函數(shù)的應用場景虑粥。

具體該怎么寫

給函數(shù)加上 suspend 關鍵字,然后在 withContext 把函數(shù)的內(nèi)容包住就可以了宪哩。

提到用 withContext是因為它在掛起函數(shù)里功能最簡單直接:把線程自動切走和切回娩贷。

當然并不是只有 withContext 這一個函數(shù)來輔助我們實現(xiàn)自定義的 suspend 函數(shù),比如還有一個掛起函數(shù)叫 delay斋射,它的作用是等待一段時間后再繼續(xù)往下執(zhí)行代碼育勺。

使用它就可以實現(xiàn)剛才提到的等待類型的耗時操作:

???
suspend fun suspendUntilDone() {
  while (!done) {
    delay(5)
  }
}

Kotlin

這些東西,在我們初步使用協(xié)程的時候不用立馬接觸罗岖,可以先把協(xié)程最基本的方法和概念理清楚。

練習

使用協(xié)程下載一張圖腹躁,并行進行兩次切割

  • 一次切成大小相同的 4 份桑包,取其中的第一份
  • 一次切成大小相同的 9 份,取其中的最后一份

得到結(jié)果后纺非,將它們展示在兩個 ImageView 上哑了。

package com.tic.planb

import android.app.ProgressDialog.show
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.tic.plan.Plan
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*
import java.net.HttpURLConnection
import java.net.URL

class MainActivity : AppCompatActivity() {
    private val TAG = "MainActivity"
    val imgUrl =
        "http://img1.gamersky.com/image2016/10/20161015_ls_141_5/gamersky_03origin_05_201610151947EAA.jpg"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        CoroutineScope(Dispatchers.Main).launch {
            Log.d(TAG, "當前線程${Thread.currentThread().name}")
            
            val bitmap = getImage(imgUrl)
            mImageIv.setImageBitmap(bitmap)
            
            val bitmap1 = cropBitmap(bitmap, 0, 0, bitmap.width / 2, bitmap.height / 2)
            mImageIv2.setImageBitmap(bitmap1)

            val bitmap2 = cropBitmap(
                bitmap,
                bitmap.width * 2 / 3,
                bitmap.height * 2 / 3,
                bitmap.width / 3,
                bitmap.height / 3
            )
            mImageIv3.setImageBitmap(bitmap2)

        }
    }

    /**
     * 下載圖片,耗時
     */
    private suspend fun getImage(imgUrl: String): Bitmap = withContext(Dispatchers.IO) {
        Log.d(TAG, "當前線程${Thread.currentThread().name}")
        val urlParam = URL(imgUrl)
        val openConnection = urlParam.openConnection() as HttpURLConnection
        openConnection.requestMethod = "GET"
        openConnection.connect()
        val inputStream = openConnection.inputStream
        BitmapFactory.decodeStream(inputStream)
    }

    /**
     * 切割圖片
     * @param source   The bitmap we are subsetting
     * @param x        The x coordinate of the first pixel in source
     * @param y        The y coordinate of the first pixel in source
     * @param width    The number of pixels in each row
     * @param height   The number of rows
     */
    private suspend fun cropBitmap(
        source: Bitmap,
        x: Int,
        y: Int,
        width: Int,
        height: Int
    ): Bitmap = withContext(Dispatchers.IO) {

        Bitmap.createBitmap(source, x, y, width, height)
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烧颖,一起剝皮案震驚了整個濱河市弱左,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌炕淮,老刑警劉巖拆火,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涂圆,居然都是意外死亡们镜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門润歉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來模狭,“玉大人,你說我怎么就攤上這事踩衩〗鲤模” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵驱富,是天一觀的道長锚赤。 經(jīng)常有香客問我,道長萌朱,這世上最難降的妖魔是什么宴树? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮晶疼,結(jié)果婚禮上酒贬,老公的妹妹穿的比我還像新娘又憨。我一直安慰自己,他們只是感情好锭吨,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布蠢莺。 她就那樣靜靜地躺著,像睡著了一般零如。 火紅的嫁衣襯著肌膚如雪躏将。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天考蕾,我揣著相機與錄音祸憋,去河邊找鬼。 笑死肖卧,一個胖子當著我的面吹牛蚯窥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播塞帐,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼拦赠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了葵姥?” 一聲冷哼從身側(cè)響起荷鼠,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎榔幸,沒想到半個月后允乐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡牡辽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年喳篇,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片态辛。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡麸澜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奏黑,到底是詐尸還是另有隱情炊邦,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布熟史,位于F島的核電站馁害,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蹂匹。R本人自食惡果不足惜碘菜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧忍啸,春花似錦仰坦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凿滤,卻和暖如春妈橄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背翁脆。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工眷蚓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人反番。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓溪椎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親恬口。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350