Kotlin CoroutineScope使用

簡要

本文參考Kotlin Coroutines 那一兩件事情CoroutineScopeAndroid官網

  1. Dispatchers.Main – 使用此調度程序可在 Android 主線程上運行協程。此調度程序只能用於與界面交互和執行快速工作。示例包括調用 suspend 函數,運行 Android 界面框架操作,以及更新 LiveData 對象。
  2. Dispatchers.IO – 此調度程序經過了專門優化,適合在主線程之外執行磁盤或網絡 I/O。示例包括使用 Room 組件、從文件中讀取數據或向文件中寫入數據,以及運行任何網絡操作。
  3. Dispatchers.Default – 此調度程序經過了專門優化,適合在主線程之外執行佔用大量 CPU 資源的工作。用例示例包括對列表排序和解析 JSON

這類似於“注入調度程序”最佳做法。通過使用 GlobalScope,您將對類使用的 CoroutineScope 進行硬編碼,而這會帶來一些問題:

提高硬編碼值。如果您對 GlobalScope 進行硬編碼,則可能同時對 Dispatchers 進行硬編碼。

這會讓測試變得非常困難,因為您的代碼是在非受控的作用域內執行的,您將無法控制其執行。

您無法設置一個通用的 CoroutineContext 來對內置於作用域本身的所有協程執行


launch:可啟動新協程而不將結果返回給調用方。任何被視為“一勞永逸”的工作都可以使用 launch 來啟動。
async:會啟動一個新的協程,並允許您使用一個名為 await 的掛起函數返回結果。


備註:

需要在app Bundle裡面新增

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2-native-mt'
}

1. CoroutineScope vs Thread

下面是使用Thread去倒數計時

val thread = Thread {
for (i in 10 downTo 1) {
try {
sleep(1000)
} catch (e: InterruptedException) {
e.printStackTrace()
return@Thread
}
runOnUiThread {
binding.tvShow.text = "count down $i ..."
}
}
runOnUiThread {
binding.tvShow.text = "Done!"
}
}
thread.start()
binding.btnTest.setOnClickListener {
thread.interrupt()
}

下面使用CoroutineScope功能與上方相同

//GlobalScope 繼承 CoroutineScope
//全域Scope,整個應用程式的生命週期,最頂級的
//使用他相當於使用靜態函數
//可以避免被提早結束
val job: Job = GlobalScope.launch(Dispatchers.Main) {
for (i in 10 downTo 1) {
binding.tvShow.text = "count down $i ..." // update text
delay(1000)
}
binding.tvShow.text = "Done!"
}
binding.btnTest.setOnClickListener {
job.cancel()
}

2. 繼承CoroutineScope使用job

class MainActivity : AppCompatActivity(), CoroutineScope {
    //job沒有特別定義的話
    //launch,會跑在非MainThread,不可刷新UI
    private val job = Job()

    override val coroutineContext: CoroutineContext
        get() = job

    private fun runTimer() {
        launch {
            for (i in 10 downTo 1) {
                //不能刷新UI會閃退
                //binding.tvShow.text = "count down $i ..." // update text
                delay(1000)
            }
            //不能刷新UI會閃退
            //binding.tvShow.text = "Done!"
        }
        binding.btnTest.setOnClickListener {
            job.cancel()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        //只有取消這個job
        job.cancel()
    }
}

3. CoroutineScope委託MainScope

//CoroutineScope委託MainScope
//此時launch會跑在MainThread
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
    override fun onDestroy() {
        super.onDestroy()
        //Scope cancel意味著全部取消
        cancel()
    }

    private fun runTimer() {
        val job = launch {
            for (i in 10 downTo 1) {
                binding.tvShow.text = "count down $i ..." // update text
                delay(1000)
            }
            binding.tvShow.text = "Done!"
        }
        binding.btnTest.setOnClickListener {
            //job cancel意味著只取消這個工作
            job.cancel()
        }
    }
}

4. CoroutineScope搭配suspend

//CoroutineScope委託MainScope
//此時launch會跑在MainThread
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
    override fun onDestroy() {
        super.onDestroy()
        //Scope cancel意味著全部取消
        cancel()
    }

    //suspend讓自己原先的執行緒暫停
    //切換到Dispatchers.IO
    //處理完後,再回來原先的執行緒繼續往下作
    suspend fun readSomeFile() = withContext(Dispatchers.IO) {
        //模擬處理一些資料
        var j = 0
        for(i in 0 until 1000000000) {
            j += 1
        }
    }

    private fun runTimer() {
        val job = launch(Dispatchers.Main) {
            binding.tvShow.text = "123"
            binding.tvShow.visibility = View.VISIBLE
            readSomeFile()
            binding.tvShow.visibility = View.GONE
        }
        binding.btnTest.setOnClickListener {
            //job cancel意味著只取消這個工作
            job.cancel()
        }
    }
}

5. CoroutineScope+async+await

以下會打印result2

class MainActivity : AppCompatActivity(), CoroutineScope {
    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = job
    override fun onCreate(savedInstanceState: Bundle?) {
        val rawText: Deferred<String> = async(Dispatchers.IO) {
            "result2"
        }
        launch {
            //需放在此使用
            val text = rawText.await()
            println(text)
        }
        //在此用會error
        //rawText.await()
    }

    override fun onDestroy() {
        super.onDestroy()
        //Scope cancel意味著全部取消
        job.cancel()
    }
}

6. runBlocking+async+suspend

以下會打印

The answer is 42
Completed in 1049 ms

private fun testFunction() = runBlocking {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}

suspend fun doSomethingUsefulOne(): Int {
delay(1000L)
return 13
}

suspend fun doSomethingUsefulTwo(): Int {
delay(1000L)
return 29
}

7. CoroutineScope+runBlocking

val mScope = object : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = Job()
}

private fun testFunction() = runBlocking {
    //先打印runBlocking 2
    //再打印runBlocking 1
    mScope.launch (Dispatchers.Main){
        delay(300)
        println("runBlocking 1")
    }
    mScope.launch (Dispatchers.Main){
        delay(100)
        println("runBlocking 2")
    }
    Thread.sleep(1000) //Keep process alive
}

fun testFunction2() {
    //先打印runBlocking 1
    //再打印runBlocking 2
    runBlocking (Dispatchers.IO){
        delay(300)
        println("runBlocking 1")
    }
    runBlocking  (Dispatchers.IO){
        delay(100)
        println("runBlocking 2")
    }
    Thread.sleep(1000) //Keep process alive
}

override fun onDestroy() {
    super.onDestroy()
    mScope.cancel()
}

Kotlin

正在載入…

執行時發生錯誤。請重新整理頁面後再試一次。

訂閱Codeilin的旅程,若有最新消息會通知。

廣告

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

透過 WordPress.com 建置的網站.

向上 ↑

%d 位部落客按了讚: