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

Kotlin coroutines ได้เปลี่ยนแปลงการเขียนโปรแกรมแบบอะซิงโครนัสบน Android อย่างสิ้นเชิง ยุคของ callback hell และ AsyncTask ที่ล้าสมัยได้ผ่านพ้นไปแล้ว ด้วย coroutines โค้ดอะซิงโครนัสสามารถเขียนให้อ่านเหมือนโค้ดซิงโครนัส พร้อมทั้งรักษาประสิทธิภาพและความสามารถในการบำรุงรักษาได้อย่างดี
Coroutines มีน้ำหนักเบา (สามารถรันได้หลายพัน coroutine บน thread เดียว) รองรับการยกเลิกโดยอัตโนมัติ และทำงานร่วมกับ Jetpack และระบบนิเวศ Android สมัยใหม่ได้อย่างราบรื่น
ทำความเข้าใจพื้นฐานสำคัญ
ก่อนเริ่มเขียนโค้ด สิ่งสำคัญคือการทำความเข้าใจว่าอะไรทำให้ coroutines มีประสิทธิภาพสูง
Coroutine คืออะไร?
Coroutine คือหน่วยการคำนวณที่สามารถหยุดชั่วคราวได้ ต่างจาก thread ตรงที่ coroutine ไม่ได้บล็อก (block) การทำงาน แต่จะหยุดชั่วคราว (suspend) แล้วปล่อย thread ให้ทำงานอื่นได้
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
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
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
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)
}
}
}
}
}
}ไม่ควรใช้ GlobalScope ในแอปพลิเคชัน Android เด็ดขาด Coroutines ที่เปิดด้วย GlobalScope ไม่ได้ผูกกับวงจรชีวิตใด จึงอาจทำให้เกิด memory leak ได้
Dispatchers: การควบคุม Thread การทำงาน
Dispatchers กำหนดว่า coroutine จะทำงานบน thread ใด
Dispatcher หลัก 4 ตัว
// 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
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
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 แบบดั้งเดิม
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
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
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
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
}
}
}การตรวจสอบสถานะการยกเลิก
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
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
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 ที่สำคัญ
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
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
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 2.3 สำหรับ Android: Name-Based Destructuring, KMP และคำถามสัมภาษณ์ 2026
คำถามสัมภาษณ์ Kotlin 2.3 สำหรับนักพัฒนา Android ในปี 2026 ครอบคลุม name-based destructuring, KMP, context parameters, Flow และ coroutines พร้อมตัวอย่างโค้ด

Jetpack Compose: แอนิเมชันขั้นสูงทีละขั้นตอน
คู่มือฉบับสมบูรณ์เกี่ยวกับแอนิเมชันขั้นสูงใน Compose: ทรานซิชัน AnimatedVisibility, Animatable, ท่าทาง และประสิทธิภาพสำหรับอินเทอร์เฟซ Android ที่ลื่นไหล

20 คำถามสัมภาษณ์ Jetpack Compose ยอดนิยมประจำปี 2026
20 คำถามสัมภาษณ์ Jetpack Compose ที่พบบ่อยที่สุด: recomposition, การจัดการ state, navigation, ประสิทธิภาพ และ pattern สถาปัตยกรรม พร้อมตัวอย่างโค้ดละเอียด