Kotlin Coroutine(協程): 一、樣例


@

前言

你還在用 Hanlder + Message? 或者 AsyncTask? 你還在用 Rxjava?
有人說RxjavaCoroutine是從不同維度解決異步, 並且Rxjava的強大不止於異步問題.
好吧, 管它呢. 讓我們擁抱 Coroutine(協程) 吧.

協程的篇幅有點長, 需要自己編寫大量的測試例子. 從入門到都學完, 難度還是挺大的. 所以:我們直接把例子擺出來. 至於其他的, 我們從淺到深,日后..日后..再說 [奸笑]

沒有描述


Kotlin中文站: 請看官網


准備:
lifecycle 都有吧? 例子中需要用到 lifecycleScope;

//coroutines: 協程
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
//coroutines for Android
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'

//lifecycle
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'

沒有lifecycle 怎么辦? 當頁面銷毀時,那些延時任務, 例如Handler, 網絡請求 啥的, 需要及時關閉, 它們會影響 Activity 的及時銷毀, 帶來內存泄漏的風險. 協程也是如此.

此時我們必須自己管理協程的作用域; 所以最好還是用 lifecycleScope.

//自己定義作用域, 頁面銷毀時, 取消協程.  替換lifecycleScope即可;
val mainScope = MainScope()
override fun onDestroy(){
    super.onDestroy()
    mainScope.cancel()
}

什么是協程作用域呢? 類似於生命周期, 當頁面銷毀時, 協程應當隨之停止.

提示:以下是本篇文章正文內容,下面案例可供參考


一、直接上例子

協程能干嘛? 延時任務, 異步任務, 定時任務, 並行任務, 協程+Retrofit. 異步流? 通道?

1.延時任務.

比較常見的業務場景, 幾秒鍾后執行...

還記得handler怎么寫嗎? 定義 Handler 實現類, 然后

mHandler.sendMessageDelayed()
//或者
mHandler.postDelayed(Runnable{...})

使用協程:

doDelayed()

fun doDelayed() = lifecycleScope.launch {
    delay(2000L)	//協程掛起2秒
    binding.tvTitle.text = "變變變, 我是百變小魔女"
}

  • lifecycleScope: 默認主線程. 它會監聽Activity生命周期, 不需要手動 cancel()
  • launch: 非阻塞形式啟動協程.
  • delay(2000): 掛起協程, 2秒后恢復.

然后封裝一下:

//執行延時任務
fun CoroutineScope.doDelayed(timeMillis: Long, block: suspend () -> Unit) = this.launch {
    delay(timeMillis)
    block.invoke()
}

//使用
lifecycleScope.doDelayed(2000L){
    binding.tvTitle.text = "變變變, 我是百變小魔女"
}

2.異步任務

在子線程執行任務, 完事回調主線程
代碼如下:

doOnBack()

// 模擬子線程中執行了2秒鍾的任務,
private fun doOnBack() = lifecycleScope.launch {
    val result = withContext(Dispatchers.IO){
        delay(2000) // 假裝努力干活中..
        "變變變, 我是百變小魔女"
    }
    binding.tvTitle.text = result
}

//或者. async .
private fun doOnBack() = lifecycleScope.launch {
   val deferred = async (Dispatchers.IO){
        delay(2000) // 假裝努力干活中..
        "變變變, 我是百變小魔女"
    }
    binding.tvTitle.text = deferred.await()
}

  • withContext(): 不新建協程, 它只指定 執行當前代碼塊 所需的線程;
  • async: 創建一個協程; 它返回一個Deferred, 而lanuch返回Job對象. 多個async可以支持並發, await()是等待任務執行完畢,並返回結果.
  • Dispatchers.IO: 協程調度器. 幾種調度器如下所示

參數 意義
不指定 它從啟動了它的 CoroutineScope 中承襲了上下文
Dispatchers.Main 用於Android. 在UI線程中執行
Dispatchers.IO 子線程, 適合執行磁盤或網絡 I/O操作
Dispatchers.Default 子線程,適合 執行 cpu 密集型的工作
Dispatchers.Unconfined 管它呢, 不常用, 先不管

由於異步任務需要兩段代碼塊. 子線程代碼,及主線程響應; 所以就不封裝了; 並且里面其實就一句 withContext(Dispatchers.IO) 或者 val deferred = async (Dispatchers.IO)

3.並行任務:

有時多個任務異步進行, 而我們不清楚哪一個會先完成, 我們需要等待它們的最終結果時.

private suspend fun taskOne(): Int {
    delay(2000)	//模擬任務執行 2秒
    return 1
}

private suspend fun taskTwo(): Int {
    delay(1500) 	//模擬任務執行 1.5秒
    return 2
}

private suspend fun taskExecute(): Int = coroutineScope {
    val result1 = async(Dispatchers.IO) { taskOne() }
    val result2 = async(Dispatchers.IO) { taskTwo() }
    result1.await() + result2.await()
}

//使用
lifecycleScope.launch {
    val sum = taskExecute()
    binding.tvTitle.text = "變變變, 我是百變小魔女$sum"
}

taskOne 執行需要2秒; taskTwo 執行需要1.5秒; 並行, 任務總共耗費約 2秒時間.
suspend: 標志掛起函數, 掛起函數只能在協程中運行.

4.定時任務:

定時任務簡單啊, delay()不阻塞線程啊.

lifecycleScope.launch {
    repeat(Int.MAX_VALUE){
        delay(1000L)
        binding.tvTitle.text = "變變變, 我是百變小魔女$it"
    }
}

這個定時任務, 每次執行, 只設置TextView的text.
我們假設我們的業務比較復雜, 每次需要耗費300ms; 如下所示:

lifecycleScope.launch {
    repeat(Int.MAX_VALUE){
        delay(1000L)

        //假設我們的任務比較繁重, 每次需要消耗300ms
        delay(300L)
        binding.tvTitle.text = "變變變, 我是百變小魔女$it"
    }
}

顯而易見, 此時至少要 1300ms 才能循環一次. 所以上面寫的1000ms只能算是間隔時間. 而任務執行需要時間, 協程掛起,恢復需要時間, 任務進入隊列到線程執行也需要時間.

因此:
當定時任務的精確度要求不高, 每次執行的代碼任務比較輕便. 耗時較少時, 可以用這種方式.

有的時候, 我們鎖屏了, 或者跳到別的頁面了, 我們不需要定時一直執行, 即便更新了UI, 我們也看不到啊! 所以,我們希望頁面離開時, 定時取消. 頁面顯示時,重新啟動定時即可:
我們就以秒殺倒計時為例;

private var endTime: Long = 0	//秒殺截止時間

//秒殺剩余 5分鍾
endTime = System.currentTimeMillis() + 300 * 1000

lifecycleScope.launchWhenResumed {  //onPause 的時候會暫停.
    repeat(Int.MAX_VALUE){
        val now = System.currentTimeMillis()
        binding.tvTitle.text = if(now >= endTime){
            "秒殺結束"
        }else{
            val second = (endTime - now) / 1000 + 1
            "秒殺倒計時($second); it=$it"
        }
        delay(1000L)
    }
}

launchWhenResumed: 當頁面 Resumed 時, 啟動協程. onPause()時自動停止. 頁面重新顯示時恢復. 通過觀察這里的 it, 可以斷定 協程只是被掛起, 而不是銷毀重建.
同樣的還有: launchWhenCreated; launchWhenStarted 當然這是 lifecycle 的知識了.

有時, 我們需要精確的定時器.

可以用, java.util 包下的 Timer, 如下所示; 也可以用 CountDownTimer

val timer = timer("定時Thread_name", false, 2000, 1000){
    letUsPrintln("定時器, 這是個子線程")
}

當然, 它沒有自動暫停恢復的功能; 取消執行時, 別忘了 cancel();

協程+Retrofit 的封裝還是放到后面吧..


總結

沒啥好總結的, 復制粘貼就完事了. 下一篇我們再深入淺出的了解協程 [奸笑]


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM