誤入Android-開發Day6:協程(Coroutine)

影山小麥機
9 min readJan 6, 2025

聊聊Kotlin中的Coroutine

Coroutine in Android

前言

近期在使用Android開發的時候,經常在RxJava與Coroutine之間的使用異步語法與否有很多次的問題,所以,基於這樣的理由,我決定來一一搞清楚協程是幹啥用的,所以接下來會主要基於這個方式來寫接下來的技術筆記,算是把Blog當作是自己日常搞清楚某些技術的筆記本。

最近真的很常使用AI,但我覺得使用AI的效率固然高,但也遇到一定的問題是要依靠工程師的基本素質去完成的:「就算這樣有用,也要搞清楚為什麼會這樣成功。」這幾個點或許是開始用AI之後產生的盲點,好像不用東西丟給AI就會自己生了,但沒有認真的釐清哪個地方該用什麼。

接著,來認真聊聊Coroutine,我猜這個章節或許不會是只有一篇而已,但隨筆寫寫我對Coroutine的認識,也就如此囉。

正文

關於核心概念:

協程算是一種可以暫停、恢復的函數,有別于傳統的執行緒,它更輕量,可以避開大量的上下文的切換(比如保存當前執行緒的狀態、切換到新的執行緒、切換到新任務後執行)

幾個重要的關鍵字:

掛起函數(suspend)

這個是啥咧,表示這個函數可以被暫停並在稍後恢復:

suspend fun doTask(): String {
// 模擬一個耗時操作
delay(2000) // 延遲2秒
return "任務完成"
}
import kotlinx.coroutines.*

fun main() = runBlocking {
println("開始執行")

// 調用掛起函數
val result = doTask()

println(result)
println("結束執行")
}
開始執行
(等待2秒)
任務完成
結束執行

掛起函數主要的目的就是兩個:

  1. 非阻塞執行:delay(2000) 並不會阻塞執行緒,只是暫停當前協程,讓出執行緒給其他協程。
  2. 協程調度:掛起函數會暫停,但執行緒可以繼續處理其他任務,提升了效率。

那如果是普通的函數的作法呢?

普通函數:

fun blockingTask(): String {
Thread.sleep(2000) // 阻塞執行緒2秒
return "普通函數任務完成"
}

fun main() {
println("開始執行")
val result = blockingTask()
println(result)
println("結束執行")
}

執行緒在 Thread.sleep 期間會無法做其他事。這樣就會造成一些資源的使用不當。

作用域(Coroutine Scope)

接下來會聊作用域:

作用域有四種:

  1. GlobalScope:全域作用域,生命周期與應用程序一致(使用時要小心)。
  2. lifecycleScope:在 Activity 或 Fragment 中,與其生命周期綁定。
  3. viewModelScope:在 ViewModel 中,與 ViewModel 的生命周期綁定。
  4. CoroutineScope:自定義協程作用域。

四種作用域大概看命名就可以知道會用在什麼地方,LifeCycle的Scope,就是用在UI中,ViewModel的就是用在Kotlin最經典的MVVM框架中的ViewModelM,而Global就是所謂的全作用域,如果要另外自訂義協程,就直接用Coroutine,直白吧?

下面一一舉例:

1. GlobalScope

import kotlinx.coroutines.*

fun main() {
GlobalScope.launch {
for (i in 5 downTo 1) {
println("倒計時: $i")
delay(1000)
}
println("任務完成!")
}

Thread.sleep(6000) // 保證主線程存活以觀察結果
}

2. lifecycleScope

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

lifecycleScope.launch {
for (i in 5 downTo 1) {
println("倒計時: $i")
delay(1000)
}
println("任務完成!")
}
}
}

3. viewModelScope

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class MyViewModel : ViewModel() {
fun fetchData() {
viewModelScope.launch {
val data = withContext(Dispatchers.IO) {
// 模擬網路請求
delay(2000)
"數據加載完成"
}
println("更新 UI: $data")
}
}
}

4. CoroutineScope

import kotlinx.coroutines.*

class CustomScopeExample {
private val scope = CoroutineScope(Dispatchers.Default)

fun startTask() {
scope.launch {
for (i in 1..5) {
println("任務進行中: $i")
delay(1000)
}
println("任務完成!")
}
}

fun cancelTask() {
scope.cancel() // 手動取消
println("任務已取消!")
}
}

fun main() {
val example = CustomScopeExample()
example.startTask()

Thread.sleep(3000) // 等待3秒
example.cancelTask()

Thread.sleep(2000) // 觀察取消後是否有其他輸出
}

調度器(Dispatcher)

調度器大致上可分為四種:

1. Dispatchers.Main

適用於 Android 和桌面應用程序,用於執行與 UI 操作相關的任務。協程會運行在主線程(UI 線程)上。

2. Dispatchers.IO

用於執行 IO 密集型操作,比如文件讀寫、網絡請求、數據庫操作。它基於一個共享的線程池,能高效處理多個 IO 任務。

3. Dispatchers.Default

適用於 CPU 密集型操作,比如計算、排序、處理數據等。默認使用與 CPU 核心數相等的線程數。

4. Dispatchers.Unconfined

不限制協程在哪個線程上運行,初始時在調用者線程上運行,後續由掛起函數決定線程切換。適用於輕量、無上下文依賴的操作。

5. 自定義調度器

我們可以通過 newSingleThreadContext 或 Executors 創建自己的調度器。

舉個例子:

import kotlinx.coroutines.*

fun main() = runBlocking {
launch(Dispatchers.Main) {
println("運行在主線程: ${Thread.currentThread().name}")
}

launch(Dispatchers.IO) {
println("運行在 IO 線程: ${Thread.currentThread().name}")
}

launch(Dispatchers.Default) {
println("運行在計算密集型線程: ${Thread.currentThread().name}")
}

launch(Dispatchers.Unconfined) {
println("初始運行線程: ${Thread.currentThread().name}")
delay(100)
println("掛起後的線程: ${Thread.currentThread().name}")
}
}

執行結果大概就會是下面這個樣子:

運行在主線程: main
運行在 IO 線程: DefaultDispatcher-worker-1
運行在計算密集型線程: DefaultDispatcher-worker-2
初始運行線程: main
掛起後的線程: DefaultDispatcher-worker-3

如果硬要說調度器大概怎麼使用,可能可以這樣分:

不過我目前是處於沒什麼使用調度器的經驗,大部分都使用作用域去解決事情,所以也許之後有更多相關使用經驗會再更新上來。

尾聲

Android跟大坑一樣,就是一個開發環境,但是會build在不同類型的手機上,很多時候你以為的可行,可能換了一個開發機或是網路環境之類的,你以為的可以就會化為泡影,但最重要的還是自己知道為什麼這麼用,才是工程師必須要有的素質。

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

影山小麥機
影山小麥機

Written by 影山小麥機

本職為Mobile工程師,熱愛分享視野,也樂意站在ChatGPT的肩膀上。訂閱小麥機,收割技術、職涯、人生的難題。

No responses yet

Write a response