Kotlin Coroutines สำหรับ Android: คู่มือฉบับสมบูรณ์ 2026

คู่มือครบถ้วนเกี่ยวกับ Kotlin coroutines ในการพัฒนา Android: suspend functions, scopes, dispatchers และ patterns ขั้นสูง

คู่มือฉบับสมบูรณ์เกี่ยวกับ Kotlin Coroutines สำหรับ Android

Kotlin coroutines ได้เปลี่ยนแปลงการเขียนโปรแกรมแบบอะซิงโครนัสบน Android อย่างสิ้นเชิง ยุคของ callback hell และ AsyncTask ที่ล้าสมัยได้ผ่านพ้นไปแล้ว ด้วย coroutines โค้ดอะซิงโครนัสสามารถเขียนให้อ่านเหมือนโค้ดซิงโครนัส พร้อมทั้งรักษาประสิทธิภาพและความสามารถในการบำรุงรักษาได้อย่างดี

ทำไมต้อง coroutines?

Coroutines มีน้ำหนักเบา (สามารถรันได้หลายพัน coroutine บน thread เดียว) รองรับการยกเลิกโดยอัตโนมัติ และทำงานร่วมกับ Jetpack และระบบนิเวศ Android สมัยใหม่ได้อย่างราบรื่น

ทำความเข้าใจพื้นฐานสำคัญ

ก่อนเริ่มเขียนโค้ด สิ่งสำคัญคือการทำความเข้าใจว่าอะไรทำให้ coroutines มีประสิทธิภาพสูง

Coroutine คืออะไร?

Coroutine คือหน่วยการคำนวณที่สามารถหยุดชั่วคราวได้ ต่างจาก thread ตรงที่ coroutine ไม่ได้บล็อก (block) การทำงาน แต่จะหยุดชั่วคราว (suspend) แล้วปล่อย thread ให้ทำงานอื่นได้

BasicCoroutine.ktkotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        delay(1000L) // Suspend without blocking
        println("World!")
    }
    println("Hello,")
}
// Output: Hello, World!

Suspend Functions: หัวใจของ Coroutines

คีย์เวิร์ด suspend บ่งบอกว่าฟังก์ชันนั้นสามารถหยุดการทำงานของ coroutine ชั่วคราวได้โดยไม่บล็อก thread

SuspendFunction.ktkotlin
suspend fun fetchUserData(userId: String): User {
    return withContext(Dispatchers.IO) {
        // Network call - runs on an IO thread
        apiService.getUser(userId)
    }
}

suspend fun fetchUserWithPosts(userId: String): UserWithPosts {
    // Sequential execution
    val user = fetchUserData(userId)
    val posts = fetchUserPosts(userId)
    return UserWithPosts(user, posts)
}

กฎสำคัญ: suspend function สามารถเรียกใช้ได้จาก suspend function อื่นหรือจากภายใน coroutine เท่านั้น

Coroutine Scopes: การจัดการวงจรชีวิต

Scope กำหนดวงจรชีวิตของ coroutines ซึ่งเป็นปัจจัยสำคัญในการป้องกัน memory leak

viewModelScope: Scope สำหรับ ViewModel

UserViewModel.ktkotlin
class UserViewModel(
    private val userRepository: UserRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()

    fun loadUser(userId: String) {
        viewModelScope.launch {
            _uiState.value = UserUiState.Loading

            try {
                val user = userRepository.getUser(userId)
                _uiState.value = UserUiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UserUiState.Error(e.message)
            }
        }
    }
}

sealed class UserUiState {
    object Loading : UserUiState()
    data class Success(val user: User) : UserUiState()
    data class Error(val message: String?) : UserUiState()
}

lifecycleScope: สำหรับ Activity และ Fragment

UserFragment.ktkotlin
class UserFragment : Fragment() {

    private val viewModel: UserViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    when (state) {
                        is UserUiState.Loading -> showLoading()
                        is UserUiState.Success -> showUser(state.user)
                        is UserUiState.Error -> showError(state.message)
                    }
                }
            }
        }
    }
}
ระวัง memory leak

ไม่ควรใช้ GlobalScope ในแอปพลิเคชัน Android เด็ดขาด Coroutines ที่เปิดด้วย GlobalScope ไม่ได้ผูกกับวงจรชีวิตใด จึงอาจทำให้เกิด memory leak ได้

Dispatchers: การควบคุม Thread การทำงาน

Dispatchers กำหนดว่า coroutine จะทำงานบน thread ใด

Dispatcher หลัก 4 ตัว

Dispatchers.ktkotlin
// Main: main thread (UI)
viewModelScope.launch(Dispatchers.Main) {
    textView.text = "UI update"
}

// IO: I/O operations (network, database)
viewModelScope.launch(Dispatchers.IO) {
    val data = repository.fetchFromNetwork()
}

// Default: CPU-intensive computations
viewModelScope.launch(Dispatchers.Default) {
    val result = heavyComputation(data)
}

// Unconfined: inherits caller context (rare use)

withContext: การสลับ Dispatcher

ImageProcessor.ktkotlin
class ImageProcessor {

    suspend fun processImage(bitmap: Bitmap): Bitmap {
        return withContext(Dispatchers.Default) {
            // CPU-intensive processing on Default
            applyFilters(bitmap)
        }
    }

    suspend fun saveToGallery(bitmap: Bitmap) {
        withContext(Dispatchers.IO) {
            // Disk write on IO
            saveToFile(bitmap)
        }
    }
}

// Usage in ViewModel
viewModelScope.launch {
    val processed = imageProcessor.processImage(originalBitmap)
    imageProcessor.saveToGallery(processed)
    // Automatic return to Main for UI update
    _uiState.value = UiState.Success(processed)
}

การทำงานแบบขนานด้วย async/await

เมื่อต้องการทำงานหลายอย่างพร้อมกันและรวมผลลัพธ์เข้าด้วยกัน ให้ใช้ async

ParallelExecution.ktkotlin
suspend fun loadDashboard(): Dashboard {
    return coroutineScope {
        // Parallel launch
        val userDeferred = async { userRepository.getUser() }
        val statsDeferred = async { statsRepository.getStats() }
        val notificationsDeferred = async { notificationRepository.getNotifications() }

        // Await results
        Dashboard(
            user = userDeferred.await(),
            stats = statsDeferred.await(),
            notifications = notificationsDeferred.await()
        )
    }
}
ประสิทธิภาพ

เมื่อใช้ async ทั้ง 3 การเรียกจะทำงานแบบขนาน หากแต่ละการเรียกใช้เวลา 1 วินาที เวลารวมจะอยู่ที่ประมาณ 1 วินาทีแทนที่จะเป็น 3 วินาทีเมื่อทำงานแบบลำดับ

การจัดการข้อผิดพลาดใน Coroutines

try/catch แบบดั้งเดิม

ErrorHandling.ktkotlin
viewModelScope.launch {
    try {
        val user = userRepository.getUser(userId)
        _uiState.value = UiState.Success(user)
    } catch (e: HttpException) {
        _uiState.value = UiState.Error("Server error: ${e.code()}")
    } catch (e: IOException) {
        _uiState.value = UiState.Error("Network error")
    } catch (e: Exception) {
        _uiState.value = UiState.Error("Unexpected error")
    }
}

CoroutineExceptionHandler

ExceptionHandler.ktkotlin
class UserViewModel : ViewModel() {

    private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
        _uiState.value = UiState.Error(throwable.message)
        Timber.e(throwable, "Error in coroutine")
    }

    fun loadUser(userId: String) {
        viewModelScope.launch(exceptionHandler) {
            val user = userRepository.getUser(userId)
            _uiState.value = UiState.Success(user)
        }
    }
}

Result Wrapper Pattern

ResultPattern.ktkotlin
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Throwable) : Result<Nothing>()
}

suspend fun <T> safeApiCall(apiCall: suspend () -> T): Result<T> {
    return try {
        Result.Success(apiCall())
    } catch (e: Exception) {
        Result.Error(e)
    }
}

// Usage
class UserRepository(private val api: UserApi) {
    suspend fun getUser(id: String): Result<User> = safeApiCall {
        api.getUser(id)
    }
}

// In ViewModel
viewModelScope.launch {
    when (val result = userRepository.getUser(userId)) {
        is Result.Success -> _uiState.value = UiState.Success(result.data)
        is Result.Error -> _uiState.value = UiState.Error(result.exception.message)
    }
}

พร้อมที่จะพิชิตการสัมภาษณ์ Android แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

การยกเลิก Coroutine: การจัดการทรัพยากรอย่างถูกต้อง

Coroutines รองรับการยกเลิกแบบร่วมมือ (cooperative cancellation) ซึ่งเป็นสิ่งจำเป็นในการป้องกันการรั่วไหลของทรัพยากร

การยกเลิกอัตโนมัติด้วย Scopes

SearchViewModel.ktkotlin
class SearchViewModel : ViewModel() {

    private var searchJob: Job? = null

    fun search(query: String) {
        // Cancel previous search
        searchJob?.cancel()

        searchJob = viewModelScope.launch {
            delay(300) // Debounce
            val results = searchRepository.search(query)
            _searchResults.value = results
        }
    }
}

การตรวจสอบสถานะการยกเลิก

CancellationCheck.ktkotlin
suspend fun processLargeList(items: List<Item>) {
    items.forEach { item ->
        // Check if coroutine is cancelled
        ensureActive()

        processItem(item)
    }
}

suspend fun downloadFiles(urls: List<String>) = coroutineScope {
    urls.map { url ->
        async {
            try {
                downloadFile(url)
            } catch (e: CancellationException) {
                cleanupPartialDownload(url)
                throw e // Re-throw to propagate cancellation
            }
        }
    }.awaitAll()
}

ห้ามกลืน CancellationException เด็ดขาด: หากมีการ catch Exception ต้อง throw CancellationException ออกไปใหม่เสมอ เพื่อให้การยกเลิกแพร่กระจายได้อย่างถูกต้อง

Flow: การเขียนโปรแกรมแบบรีแอคทีฟ

Flow เปรียบเสมือน RxJava Observable ในโลกของ coroutines แต่ใช้งานง่ายกว่าและถูกรวมเข้ากับระบบตั้งแต่แรก

การสร้างและเก็บข้อมูลจาก Flow

FlowBasics.ktkotlin
fun getUsers(): Flow<List<User>> = flow {
    while (true) {
        val users = userApi.getUsers()
        emit(users)
        delay(5000) // Poll every 5 seconds
    }
}

// Flow from Room
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAllUsers(): Flow<List<User>>
}

// Collecting in ViewModel
viewModelScope.launch {
    userDao.getAllUsers()
        .catch { e -> _uiState.value = UiState.Error(e.message) }
        .collect { users ->
            _uiState.value = UiState.Success(users)
        }
}

StateFlow กับ SharedFlow

StateFlowVsSharedFlow.ktkotlin
class EventViewModel : ViewModel() {

    // StateFlow: keeps last value, ideal for UI state
    private val _uiState = MutableStateFlow(UiState.Initial)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    // SharedFlow: for one-shot events (navigation, snackbar)
    private val _events = MutableSharedFlow<UiEvent>()
    val events: SharedFlow<UiEvent> = _events.asSharedFlow()

    fun onButtonClick() {
        viewModelScope.launch {
            _events.emit(UiEvent.NavigateToDetail)
        }
    }
}

sealed class UiEvent {
    object NavigateToDetail : UiEvent()
    data class ShowSnackbar(val message: String) : UiEvent()
}

ตัวดำเนินการ Flow ที่สำคัญ

FlowOperators.ktkotlin
userRepository.getUsers()
    .map { users -> users.filter { it.isActive } }
    .distinctUntilChanged()
    .debounce(300)
    .flatMapLatest { users ->
        fetchUserDetails(users)
    }
    .catch { e ->
        emit(emptyList())
    }
    .onEach { users ->
        analytics.logUserCount(users.size)
    }
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = emptyList()
    )

Patterns ขั้นสูง

Retry ด้วย Exponential Backoff

RetryPattern.ktkotlin
suspend fun <T> retryWithBackoff(
    times: Int = 3,
    initialDelay: Long = 100,
    maxDelay: Long = 1000,
    factor: Double = 2.0,
    block: suspend () -> T
): T {
    var currentDelay = initialDelay
    repeat(times - 1) { attempt ->
        try {
            return block()
        } catch (e: Exception) {
            Timber.w("Attempt ${attempt + 1} failed, retrying in ${currentDelay}ms")
        }
        delay(currentDelay)
        currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
    }
    return block()
}

// Usage
val user = retryWithBackoff {
    userApi.getUser(userId)
}

Timeout

TimeoutPattern.ktkotlin
suspend fun fetchWithTimeout() {
    try {
        val result = withTimeout(5000L) {
            api.fetchData()
        }
        processResult(result)
    } catch (e: TimeoutCancellationException) {
        showError("Request took too long")
    }
}

// Or with a default value
val result = withTimeoutOrNull(5000L) {
    api.fetchData()
} ?: defaultValue

บทสรุป

Kotlin coroutines กลายเป็นเครื่องมือที่ขาดไม่ได้ในการพัฒนา Android สมัยใหม่ Coroutines มอบแนวทางการเขียนโปรแกรมอะซิงโครนัสที่สวยงามและมีประสิทธิภาพ พร้อมทั้งทำงานร่วมกับระบบนิเวศ Jetpack ได้อย่างสมบูรณ์

รายการตรวจสอบ

  • ใช้ viewModelScope และ lifecycleScope เพื่อป้องกัน memory leak
  • เลือก Dispatcher ที่เหมาะสม (Main, IO, Default)
  • จัดการข้อผิดพลาดด้วย try/catch หรือ Result wrapper
  • นำระบบการยกเลิกแบบร่วมมือไปใช้งาน
  • ใช้ Flow สำหรับกระแสข้อมูลแบบรีแอคทีฟ
  • เลือก StateFlow สำหรับสถานะ UI และ SharedFlow สำหรับเหตุการณ์

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

การเชี่ยวชาญ coroutines จะมอบข้อได้เปรียบที่สำคัญทั้งในโปรเจกต์ Android และการสัมภาษณ์เชิงเทคนิค การฝึกฝนอย่างสม่ำเสมอและการสำรวจ patterns ขั้นสูงจะช่วยยกระดับทักษะการพัฒนาได้อย่างมาก

แท็ก

#kotlin
#coroutines
#android
#async
#concurrency

แชร์

บทความที่เกี่ยวข้อง