viewModelScope: คืออะไร การเชื่อมโยงกับ ViewModel และการทำงานใน Android

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-06-23 เวลาอ่าน: 9 นาที

viewModelScope เป็น CoroutineScope ในตัวจากไลบรารี androidx.lifecycle ที่เชื่อมโยงกับวงจรชีวิตของ ViewModel และถูกยกเลิกโดยอัตโนมัติเมื่อถูกล้าง ตามข้อมูลจาก Google Android Developers, 2025 viewModelScope เป็นกลไกมาตรฐานสำหรับเริ่มคอรูทีนในสถาปัตยกรรม MVVM ซึ่งรับประกันการทำงานแบบอะซิงโครนัสที่ปลอดภัยโดยไม่มีความเสี่ยงในการรั่วไหลของหน่วยความจำ ViewModelScope ใช้ Dispatchers.Main เป็นค่าเริ่มต้น และการดำเนินการ IO ทั้งหมดภายในจะต้องดำเนินการผ่าน withContext

ประเด็นสำคัญ

  • viewModelScope — CoroutineScope จาก lifecycle-viewmodel-ktx ถูกยกเลิกเมื่อเรียก ViewModel.onCleared()
  • Dispatchers.Main — ตัวจัดส่งค่าเริ่มต้น ดังนั้นการอัปเดต UI ภายในคอรูทีนจึงปลอดภัย
  • onCleared — คอลแบ็กที่เรียกใช้การยกเลิกคอรูทีนที่ทำงานอยู่ทั้งหมดใน viewModelScope โดยอัตโนมัติ
  • clear() กับ onCleared() — clear() ถูกเรียกโดยเฟรมเวิร์กก่อน onCleared เพื่อรับประกันการยกเลิก scope
  • launch — วิธีหลักในการเริ่มคอรูทีนใน viewModelScope สำหรับการดำเนินการแบบ fire-and-forget

viewModelScope ใน Android คืออะไร?

viewModelScope เป็นคุณสมบัติส่วนขยายบนอินเทอร์เฟซ ViewModel ที่ถูกเพิ่มในไลบรารี lifecycle-viewmodel-ktx (ตั้งแต่เวอร์ชัน 2.1.0) โดยให้ CoroutineScope ที่พร้อมใช้งานเชื่อมโยงกับวงจรชีวิตของ ViewModel

kotlin
// Internal structure (simplified)
val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope = this.getTag(JOB_KEY)
        if (scope != null) return scope
        return CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
            .also { setTag(JOB_KEY, it) }
    }

scope ถูกสร้างแบบขี้เกียจ (lazy) เมื่อเข้าถึงครั้งแรกและถูกแคชผ่าน setTag โดยใช้ SupervisorJob ซึ่งหมายความว่าข้อยกเว้นในคอรูทีนย่อยหนึ่งจะไม่ยกเลิกคอรูทีนอื่น ตัวจัดส่งค่าเริ่มต้นคือ Dispatchers.Main.immediate ซึ่งดำเนินการโค้ดบนเธรดหลักโดยไม่ต้องจัดส่งเพิ่มเติมหากอยู่บน Main อยู่แล้ว

viewModelScope ได้รับการแจ้งเตือนการล้างข้อมูลอย่างไร

เมื่อ ViewModel ออกจากวงจรชีวิต (Activity สิ้นสุดลงหรือ Fragment ถูกลบออก) ระบบจะเรียก clear() ซึ่งเรียกใช้ onCleared() ในคอลแบ็กนี้ viewModelScope จะยกเลิก Job ของมัน ซึ่งจะสิ้นสุดคอรูทีนที่ทำงานอยู่ทั้งหมดแบบเรียกซ้ำ กลไกนี้ถูกนำไปใช้ผ่านอินเทอร์เฟซ Closeable โดยที่ Job ของ scope ถูกลงทะเบียนเป็นทรัพยากรสำหรับการปิดอัตโนมัติ

viewModelScope ทำงานอย่างไร: การเชื่อมโยงกับวงจรชีวิตของ ViewModel

กลไกการเชื่อมโยง viewModelScope กับวงจรชีวิตของ ViewModel ขึ้นอยู่กับการติดแท็กและคอลแบ็ก onCleared มาดูทีละขั้นตอน

ขั้นตอนที่ 1: การสร้าง scope เมื่อเข้าถึงครั้งแรก

เมื่อ ViewModel ดำเนินการ viewModelScope.launch { ... } getter จะตรวจสอบว่ามี scope ที่ถูกเก็บไว้ภายใต้แท็ก JOB_KEY หรือไม่ หากไม่มี scope จะสร้างอินสแตนซ์ใหม่ของ CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) scope ถูกเก็บไว้ภายใน ViewModel ผ่านแผนที่แท็กภายใน

ขั้นตอนที่ 2: วงจรชีวิตของคอรูทีน

คอรูทีนทั้งหมดที่เริ่มผ่าน viewModelScope.launch หรือ viewModelScope.async จะกลายเป็นลูกของ SupervisorJob ของ scope พวกมันทำงานบนเธรดหลัก (เว้นแต่จะระบุตัวจัดส่งอื่นผ่าน withContext) ตราบใดที่ ViewModel ยังทำงานอยู่ คอรูทีนสามารถทำงาน ถูกระงับ หรือเสร็จสมบูรณ์

ขั้นตอนที่ 3: การยกเลิกที่ onCleared

เมื่อระบบทำลาย ViewModel จะเรียก ViewModel.clear() ภายใน clear() จะเกิดสิ่งต่อไปนี้:

  • เรียก onCleared() สำหรับตรรกะการล้างข้อมูลที่กำหนดเอง
  • ปิดทรัพยากร Closeable ทั้งหมดที่ลงทะเบียนผ่าน addCloseable
  • Job ของ viewModelScope เปลี่ยนเป็นสถานะ Cancelled
  • คอรูทีนย่อยทั้งหมดถูกยกเลิกแบบเรียกซ้ำ
  • ปล่อยการอ้างอิงไปยัง scope สำหรับตัวเก็บขยะ

ความทนทานต่อการหมุน

เมื่อหน้าจอหมุน Activity จะถูกสร้างใหม่ แต่ ViewModel ยังคงอยู่ (ต้องขอบคุณ ViewModelStoreOwner) ซึ่งหมายความว่า viewModelScope ยังคงทำงานอยู่และคอรูทีนดำเนินการต่อไปอย่างไม่ขาดตอน หลังจากสร้าง Activity ใหม่ ViewModel เดียวกัน (และ scope เดียวกัน) จะถูกนำกลับมาใช้ใหม่ — การโหลดข้อมูลจะไม่เริ่มต้นใหม่

viewModelScope ในสถาปัตยกรรม MVVM

MVVM (Model-View-ViewModel) เป็นสถาปัตยกรรมที่ Google แนะนำสำหรับแอปพลิเคชัน Android viewModelScope มีบทบาทสำคัญในฐานะผู้ดำเนินการทำงานแบบอะซิงโครนัส

บทบาทของ viewModelScope ในชั้นสถาปัตยกรรม

ชั้นส่วนประกอบบทบาทของ viewModelScope
UIActivity / Fragmentสังเกต StateFlow/LiveData จาก ViewModel
ViewModelViewModelเริ่มคอรูทีนผ่าน viewModelScope จัดการสถานะ UI
RepositoryRepositoryเปิดเผยฟังก์ชัน suspend ที่ถูกเรียกจากคอรูทีนของ viewModelScope
DataDAO / Apiดำเนินการคำขอจริง (Room, Retrofit)

ViewModel เริ่มคอรูทีนผ่าน viewModelScope ซึ่งภายในจะเรียก ฟังก์ชัน suspend ของ Repository ผลลัพธ์ถูกแปลงเป็น StateFlow ซึ่งถูกสังเกตโดยชั้น UI การออกแบบนี้รับประกันการแบ่งแยกหน้าที่ชัดเจนและความสามารถในการทดสอบอิสระของแต่ละชั้น

ทำไม viewModelScope ใน ViewModel และไม่ใช่ใน Fragment

หากคอรูทีนถูกเริ่มจาก Fragment พวกมันจะถูกยกเลิกเมื่อหมุนหน้าจอพร้อมกับการทำลาย Fragment ViewModel อยู่รอดการหมุน ดังนั้นคอรูทีนที่เริ่มใน scope ของมันจะดำเนินการต่อ นี่คือข้อได้เปรียบหลักของ viewModelScope เหนือ lifecycleScope เมื่อโหลดข้อมูล

ตัวอย่างการใช้งาน viewModelScope

มาดูสามสถานการณ์เชิงปฏิบัติของการใช้ viewModelScope ในแอปพลิเคชัน Android ด้วย Kotlin

ตัวอย่างที่ 1: การโหลดข้อมูลเมื่อสร้าง ViewModel

kotlin
class ProfileViewModel(
    private val repo: ProfileRepository
) : ViewModel() {

    private val _profile = MutableStateFlow<Profile?>(null)
    val profile: StateFlow<Profile?> = _profile

    init {
        loadProfile()
    }

    private fun loadProfile() {
        viewModelScope.launch {
            val result = repo.getProfile()
            _profile.value = result
        }
    }
}

ในบล็อก init การโหลดโปรไฟล์จะเริ่มทันที คอรูทีนทำงานบนเธรดหลัก (โดยค่าเริ่มต้น) พื้นที่เก็บข้อมูลใช้ withContext(Dispatchers.IO) สำหรับคำขอเครือข่ายภายในฟังก์ชัน suspend ดังนั้น ViewModel ไม่ต้องกังวลเกี่ยวกับการสลับเธรด

ตัวอย่างที่ 2: การจัดการข้อผิดพลาดผ่าน sealed class

kotlin
sealed class UiState {
    object Loading : UiState()
    data class Success(val data: List<Item>) : UiState()
    data class Error(val message: String) : UiState()
}
kotlin
fun fetchItems() {
    _state.value = UiState.Loading
    viewModelScope.launch {
        try {
            val items = repo.getItems()
            _state.value = UiState.Success(items)
        } catch (e: Exception) {
            _state.value = UiState.Error(e.message ?: "Unknown error")
        }
    }
}

สถานะ UI ถูกอธิบายผ่าน sealed class UiState ViewModel อัปเดตสถานะทุกครั้งที่มีการเปลี่ยนแปลง Fragment สมัครรับ StateFlow และตอบสนองต่อสถานะปัจจุบันเท่านั้น โดยไม่สนใจการเรียกที่ล้าสมัยจากการหมุนครั้งก่อน

ตัวอย่างที่ 3: การยกเลิกคอรูทีนก่อนหน้าเมื่อมีคำขอใหม่

kotlin
private var searchJob: Job? = null

fun search(query: String) {
    searchJob?.cancel()
    searchJob = viewModelScope.launch {
        delay(300)
        val results = repo.search(query)
        _searchResults.value = results
    }
}

ในแต่ละคำค้นหาใหม่ คอรูทีนก่อนหน้าจะถูกยกเลิก delay(300) ใช้ดีบาวซ์ — การค้นหาจะดำเนินการหลังจากไม่มีการเคลื่อนไหว 300 มิลลิวินาทีเท่านั้น ซึ่งลดภาระของเซิร์ฟเวอร์และป้องกันผลลัพธ์ที่ล้าสมัย

viewModelScope กับ lifecycleScope: เมื่อใดควรเลือกอะไร

ทั้งสอง scope ถูกให้โดยไลบรารี AndroidX Lifecycle แต่เชื่อมโยงกับวงจรชีวิตที่แตกต่างกัน การเลือกขึ้นอยู่กับประเภทของงาน

การเปรียบเทียบ scope

คุณลักษณะviewModelScopelifecycleScope
เจ้าของViewModelLifecycleOwner (Activity/Fragment)
ยกเลิกเมื่อหมุนไม่ (ViewModel อยู่รอด)ใช่ (Activity ถูกสร้างใหม่)
ตัวจัดส่งค่าเริ่มต้นDispatchers.Main.immediateDispatchers.Main.immediate
พร้อมใช้งานในViewModelActivity, Fragment, Service
กรณีใช้งานทั่วไปการโหลดข้อมูล ตรรกะทางธุรกิจการโต้ตอบกับ UI แอนิเมชัน

คำแนะนำของ Google

Google แนะนำให้ใช้ viewModelScope สำหรับงานโหลดและประมวลผลข้อมูลทั้งหมด ควรใช้ lifecycleScope สำหรับการดำเนินการที่เชื่อมโยงกับช่วงเวลาเฉพาะของวงจรชีวิต UI — ตัวอย่างเช่น เริ่มแอนิเมชันเมื่อปรากฏหน้าจอครั้งแรก หรือสมัครรับการอัปเดตตำแหน่งที่ควรหยุดเมื่อออกจากหน้าจอ

ข้อผิดพลาดทั่วไปเมื่อทำงานกับ viewModelScope

แม้ใน Android API ที่มีเอกสารครบถ้วน นักพัฒนาก็ยังทำข้อผิดพลาดทั่วไป มาดูสี่ปัญหาที่พบบ่อยที่สุด

ข้อผิดพลาดที่ 1: การอัปเดต UI หลังจากการยกเลิก scope

ข้อผิดพลาดที่ร้ายกาจที่สุดคือการพยายามอัปเดต StateFlow หรือ LiveData หลังจาก ViewModel ถูกล้าง แม้ว่า viewModelScope จะถูกยกเลิกที่ onCleared() คอรูทีนอาจดำเนินการโค้ดก่อนที่การยกเลิกจะมีผล ใช้ isActive เพื่อตรวจสอบหรือพึ่งพาการเสร็จสมบูรณ์ของบล็อก catch

ข้อผิดพลาดที่ 2: การเริ่มคอรูทีนโดยไม่พิจารณา SupervisorJob

viewModelScope ใช้ SupervisorJob ภายใน ซึ่งแยกข้อผิดพลาดระหว่างคอรูทีน อย่างไรก็ตาม หากคุณเริ่มคอรูทีนด้วย Job() ของตัวเองภายใน viewModelScope.launch คอรูทีนนั้นจะกลายเป็นลูกของ SupervisorJob แต่จะไม่ได้รับการป้องกันจากการยกเลิกที่เกิดจากข้อผิดพลาดในคอรูทีนอื่น

ข้อผิดพลาดที่ 3: คอรูทีนมากเกินไปใน scope เดียว

แม้ว่า viewModelScope จะไม่มีข้อจำกัดที่เข้มงวด แต่คอรูทีนที่ทำงานอยู่หลายพันตัวอาจทำให้ระบบช้าลง สำหรับรายการข้อมูลยาว ให้ใช้ Flow กับ collectLatest แทนการสร้างคอรูทีนแยกสำหรับแต่ละรายการ

ข้อผิดพลาดที่ 4: การใช้ GlobalScope แทน viewModelScope

หากนำเข้า GlobalScope โดยไม่ได้ตั้งใจแทน viewModelScope คอรูทีนจะไม่ถูกยกเลิกเมื่อ ViewModel ถูกล้าง ซึ่งนำไปสู่การรั่วไหลของหน่วยความจำและอาจทำให้แอปพลิเคชันล้มเหลว ตรวจสอบให้แน่ใจเสมอว่าคอรูทีนเริ่มผ่าน viewModelScope โดยเฉพาะในคลาสย่อยของ Fragment

คำถามที่พบบ่อย

ฉันสามารถเปลี่ยนตัวจัดส่งค่าเริ่มต้นของ viewModelScope ได้หรือไม่?

คุณไม่สามารถเปลี่ยนตัวจัดส่งของ viewModelScope ได้โดยตรง — มันถูกกำหนดตายตัวเป็น Dispatchers.Main.immediate อย่างไรก็ตาม ภายในคอรูทีนคุณสามารถสลับไปยังตัวจัดส่งอื่นผ่าน withContext หากต้องการเปลี่ยนตัวจัดส่งในการทดสอบ ให้ใช้ TestDispatcher ผ่าน Rule

ฉันจะส่ง viewModelScope ไปยัง Repository ได้อย่างไร?

อย่าส่ง scope ไปยัง Repository — ซึ่งละเมิดหลักการทางสถาปัตยกรรม Repository ควรเปิดเผย ฟังก์ชัน suspend และ ViewModel จัดการคอรูทีนผ่าน viewModelScope หาก Repository ต้องการ scope ให้พิจารณาสถาปัตยกรรมใหม่เป็น Clean Architecture

ทำไม viewModelScope ใช้ SupervisorJob?

SupervisorJob รับประกันว่าข้อยกเว้นในคอรูทีนหนึ่ง (เช่น ข้อผิดพลาดในการโหลดในหนึ่งในคำขออิสระหลายรายการ) จะไม่ยกเลิกคอรูทีนอื่น ซึ่งสอดคล้องกับสถานการณ์ ViewModel ที่หน้าจอต่างกันโหลดข้อมูลอิสระ

viewModelScope พร้อมใช้งานใน Jetpack Compose หรือไม่?

ใช่ viewModelScope พร้อมใช้งานใน ViewModel ใดๆ โดยไม่ขึ้นกับประเภท UI (View System หรือ Jetpack Compose) ใน Compose คอรูทีนก็เริ่มผ่าน viewModelScope เช่นกัน ในขณะที่เอฟเฟกต์ UI ใช้ LaunchedEffect และ rememberCoroutineScope

จะเกิดอะไรขึ้นกับคอรูทีนเมื่อเรียก viewModelScope.cancel()?

การเรียก viewModelScope.cancel() จะยกเลิก scope ทันที — คอรูทีนที่ทำงานอยู่ทั้งหมดสิ้นสุดด้วย CancellationException หากเรียก viewModelScope.launch หลังจากนั้น scope ใหม่จะถูกสร้างโดยอัตโนมัติเมื่อเข้าถึง getter ครั้งถัดไป

สรุป

  • viewModelScope — CoroutineScope ที่เชื่อมโยงกับวงจรชีวิตของ ViewModel ถูกยกเลิกโดยอัตโนมัติที่ onCleared()
  • SupervisorJob + Dispatchers.Main — การกำหนดค่าภายในที่รับประกันการแยกข้อผิดพลาดและการเข้าถึง UI อย่างปลอดภัย
  • การหมุนหน้าจอ — ViewModel อยู่รอด ดังนั้นคอรูทีนใน viewModelScope ดำเนินต่อไปโดยไม่ต้องเริ่มใหม่
  • สถาปัตยกรรม MVVM — viewModelScope เป็นองค์ประกอบหลักสำหรับการดำเนินการแบบอะซิงโครนัสในชั้น ViewModel
  • lifecycleScope — ทางเลือกสำหรับการดำเนินการที่เชื่อมโยงกับวงจรชีวิตของ Activity/Fragment ไม่ใช่ ViewModel
  • StateFlow — วิธีที่ต้องการในการส่งข้อมูลจากคอรูทีนของ viewModelScope ไปยัง UI ผ่าน sealed class
  • GlobalScope อันตราย — การแทนที่ viewModelScope ด้วย GlobalScope นำไปสู่การรั่วไหลของหน่วยความจำและแอปขัดข้อง

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม