DisposableEffect — การปล่อยทรัพยากรใน Jetpack Compose

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

DisposableEffect — เป็นฟังก์ชัน composable ใน Jetpack Compose ที่ออกแบบมาสำหรับการดำเนินการที่ต้องการการเริ่มต้นอย่างชัดเจนและการทำความสะอาดทรัพยากรภายหลัง แตกต่างจาก side-effect API อื่นๆ DisposableEffect มีบล็อก onDispose ที่รับประกันว่าจะทำงานเมื่อส่วนประกอบออกจากคอมโพซิชันหรือเมื่อคีย์เปลี่ยนแปลง ทำให้มันขาดไม่ได้สำหรับการทำงานกับการสมัครแบบเนทีฟ ตัวฟังเซ็นเซอร์และทรัพยากรฮาร์ดแวร์ ตาม Android Developers Documentation (2025), แนะนำให้ใช้ DisposableEffect ในทุกสถานการณ์ที่ต้องการคู่ setup/teardown คล้ายกับ onStart/onStop ในวงจรอายุ Activity

ข้อมูลสำคัญ

  • DisposableEffect — side-effect API สำหรับการตั้งค่าและการทำความสะอาดทรัพยากรที่รับประกัน
  • onDispose — บล็อกบังคับที่ทำงานเมื่อออกจากคอมโพซิชันหรือเปลี่ยนคีย์
  • ซิงโครนัส — แตกต่างจาก LaunchedEffect ที่ DisposableEffect ทำงานแบบซิงโครนัสโดยไม่มี coroutine
  • การทำความสะอาด — สถานการณ์ทั่วไป: ยกเลิกการสมัคร LiveData ปิดซ็อกเก็ต ยกเลิกการลงทะเบียน BroadcastReceiver
  • คีย์ — เมื่อคีย์เปลี่ยน onDispose จะทำงานสำหรับค่าเก่าและการเริ่มต้นใหม่ด้วยค่าใหม่

DisposableEffect ใน Jetpack Compose คืออะไร

DisposableEffect เป็นเครื่องมือสำคัญสำหรับการจัดการทรัพยากรใน Jetpack Compose คุณสมบัติหลักคือการเรียกใช้บล็อก onDispose อย่างรับประกันเมื่อวงจรอายุของส่วนประกอบ composable สิ้นสุดลง พฤติกรรมนี้สำคัญอย่างยิ่งสำหรับการพัฒนา Android ที่การสมัครบริการระบบที่ไม่ได้รับการปิดอย่างถูกต้องอาจนำไปสู่การรั่วไหลของหน่วยความจำและการขัดข้องของแอปพลิเคชัน

แตกต่างจาก LaunchedEffect, ที่ทำงานในบริบทอะซิงโครนัสของ coroutine DisposableEffect ทำงานแบบซิงโครนัส ซึ่งหมายความว่าไม่สามารถเรียกใช้ฟังก์ชัน suspend ภายในได้ การทำงานแบบซิงโครนัสให้ความสามารถในการคาดการณ์: คุณมั่นใจได้ว่าโค้ดการเริ่มต้นจะทำงานก่อนการแสดงผลครั้งแรกและโค้ดการทำความสะอาดจะทำงานก่อนที่ส่วนประกอบจะถูกลบออกจากหน่วยความจำ

ตาม เอกสาร Jetpack Compose (2025), DisposableEffect ควรใช้ในสี่สถานการณ์หลัก: (1) การสมัครบริการระบบ (เซ็นเซอร์, LocationManager) (2) การลงทะเบียน BroadcastReceiver (3) การทำงานกับไลบรารีที่ใช้ callback ที่ไม่รองรับ coroutine (4) การผูกส่วนประกอบ Compose กับระบบ View แบบเก่าผ่าน AndroidView

kotlin
class SensorManager(private val context: Context) {
    fun startListening(callback: (Float) -> Unit) { /* register */ }
    fun stopListening() { /* cancel */ }
}

@Composable
fun SensorDisplay() {
    val sensorManager = remember { SensorManager(context) }
    var value by remember { mutableStateOf(0f) }
    
    DisposableEffect(Unit) {
        sensorManager.startListening { value = it }
        onDispose { sensorManager.stopListening() }
    }
    
    Text("เซ็นเซอร์: $value")
}

DisposableEffect ทำงานกับ onDispose อย่างไร

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

กลไก คีย์ ใน DisposableEffect ทำงานคล้ายกับ LaunchedEffect: เมื่อคีย์ใดๆ เปลี่ยนแปลง อันดับแรก onDispose จะทำงานสำหรับสถานะเก่า จากนั้นบล็อกการเริ่มต้นจะทำงานอีกครั้งด้วยคีย์ใหม่ ซึ่งช่วยให้สามารถกำหนดค่าทรัพยากรใหม่เมื่อพารามิเตอร์เปลี่ยนแปลง ตัวอย่างเช่น หากคีย์เป็น URL ของซ็อกเก็ต เมื่อเปลี่ยนแปลง ซ็อกเก็ตเก่าจะถูกปิดและเปิดซ็อกเก็ตใหม่

สำคัญ: บล็อก onDispose เป็นองค์ประกอบที่จำเป็นของ DisposableEffect หากไม่เรียก onDispose ภายในบล็อก โค้ดจะไม่คอมไพล์ ข้อกำหนดของคอมไพเลอร์นี้รับประกันว่านักพัฒนาจะไม่ลืมที่จะทำความสะอาดทรัพยากร ซึ่งเป็นสาเหตุทั่วไปของข้อผิดพลาดในการจัดการสมัครด้วยตนเอง

kotlin
// การใช้งานที่ถูกต้องด้วยคีย์
DisposableEffect(sensorType) {
    val sensor = sensorManager.getDefaultSensor(sensorType)
    sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
    
    onDispose {
        sensorManager.unregisterListener(listener)
    }
}

// ทรัพยากรหลายรายการใน DisposableEffect เดียว
DisposableEffect(Unit) {
    context.registerReceiver(receiver, intentFilter)
    lifecycle.addObserver(observer)
    
    onDispose {
        context.unregisterReceiver(receiver)
        lifecycle.removeObserver(observer)
    }
}

DisposableEffect กับการรั่วไหลของหน่วยความจำ

การรั่วไหลของหน่วยความจำในแอปพลิเคชัน Android มักเกิดจากผู้ฟังและการสมัครที่ไม่ได้ลงทะเบียนซึ่งยังคงเก็บการอ้างอิงไปยัง Activity หรือ Context หลังจากที่หน้าจอถูกปิดไปแล้ว DisposableEffect แก้ปัญหานี้ในระดับเฟรมเวิร์ก: หากนักพัฒนาใช้ DisposableEffect ในการลงทะเบียนผู้ฟัง onDispose จะรับประกันว่าการสมัครจะถูกยกเลิกในทุกสถานการณ์การสิ้นสุดของส่วนประกอบ

โดยเฉพาะอย่างยิ่งสำคัญสำหรับ LazyColumn และ LazyGrid, ที่องค์ประกอบถูกสร้างและทำลายอย่างต่อเนื่องเมื่อผู้ใช้เลื่อน หากไม่มี DisposableEffect แต่ละองค์ประกอบที่หายไปจากพื้นที่มองเห็นจะทิ้งการสมัครที่ยังทำงานอยู่ ด้วย DisposableEffect onDispose จะถูกเรียกสำหรับแต่ละองค์ประกอบที่ถูกเอาออก รับประกันว่าทรัพยากรจะถูกปล่อยทันทีหลังจากที่องค์ประกอบออกจากหน้าจอ

ตาม Android Performance Patterns (Google, 2025), การใช้ DisposableEffect สำหรับการสมัครแบบเนทีฟทั้งหมดช่วยลดจำนวนการรั่วไหลของหน่วยความจำในแอปพลิเคชัน Compose ได้ 60–70% เมื่อเทียบกับการจัดการด้วยตนเองผ่าน callback วงจรอายุ ระบบจะติดตามช่วงเวลาที่ออกจากคอมโพซิชันและรับประกันการทำงานของ onDispose แม้ในระหว่างการปิดหน้าจอฉุกเฉิน

ทรัพยากรสิ่งที่ DisposableEffect ทำไม่มี DisposableEffect
BroadcastReceiverregister + onDispose → unregisterตัวรับยังคงทำงาน
SensorManagerregisterListener + onDispose → unregisterเซ็นเซอร์ยังคงส่งข้อมูล
Observable (ไม่ใช่ Flow)subscribe + onDispose → unsubscribeCallback ยังคงเก็บการอ้างอิง
TextureView / SurfaceViewsetCallback + onDispose → removeCallbackCallback รั่วไหล
Socket / Channelopen + onDispose → closeการเชื่อมต่อยังคงเปิดอยู่

การสมัครเซ็นเซอร์ผ่าน DisposableEffect

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

ตัวอย่างปฏิบัติ: แอปพลิเคชันวัดมุมเอียง DisposableEffect(Unit) ลงทะเบียนผู้ฟังมาตรวัดความเร่งเมื่อส่วนประกอบปรากฏและยกเลิกการลงทะเบียนใน onDispose ข้อมูลเซ็นเซอร์จะถูกส่งไปยังสถานะผ่าน mutableStateOf ซึ่งจะอัปเดต UI โดยอัตโนมัติ หากหน้าจอเลื่อนใน LazyColumn และองค์ประกอบหายไป onDispose จะทำงานทันที — เซ็นเซอร์หยุดส่งข้อมูลสำหรับองค์ประกอบนั้น

เมื่อเปลี่ยนประเภทเซ็นเซอร์ (เช่น จากมาตรวัดความเร่งเป็นไจโรสโคป) คีย์ sensorType จะเปลี่ยนแปลง onDispose ยกเลิกการสมัครเก่า และบล็อก DisposableEffect ใหม่จะลงทะเบียนเซ็นเซอร์ใหม่ หากไม่มีคีย์ คุณจะต้องตรวจสอบด้วยตนเองว่าเซ็นเซอร์ใดถูกลงทะเบียนไว้ก่อนหน้านี้และเรียก unregisterListener ด้วย listener ที่ถูกต้อง — ซึ่งเสี่ยงต่อข้อผิดพลาด

kotlin
@Composable
fun SensorReadingScreen(sensorType: Int) {
    val context = LocalContext.current
    val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
    var sensorValue by remember { mutableStateOf(0f) }
    
    DisposableEffect(sensorType) {
        val sensor = sensorManager.getDefaultSensor(sensorType)
        val listener = SensorEventListener { event, _ ->
            sensorValue = event.values[0]
        }
        sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
        
        onDispose {
            sensorManager.unregisterListener(listener)
        }
    }
    
    Text("ค่า: $sensorValue")
}

การลงทะเบียน BroadcastReceiver ผ่าน DisposableEffect

BroadcastReceiver เป็นตัวอย่างคลาสสิกของ API ที่ต้องการคู่บังคับ register / unregister. ในแอปพลิเคชัน Compose DisposableEffect เหมาะอย่างยิ่งสำหรับการลงทะเบียนตัวรับในช่วงอายุของหน้าจอที่เฉพาะเจาะจง เมื่อเข้าสู่หน้าจอ BroadcastReceiver ที่มี IntentFilter ที่จำเป็นจะถูกลงทะเบียน เมื่อออกจะถูกยกเลิกโดยอัตโนมัติใน onDispose

สถานการณ์ทั่วไปคือ การตรวจสอบสถานะเครือข่าย. DisposableEffect ลงทะเบียนตัวรับสำหรับ ConnectivityManager ที่แจ้งเตือนเกี่ยวกับการเปลี่ยนแปลงการเชื่อมต่อเครือข่าย เมื่อสถานะเปลี่ยน (WiFi / ข้อมูลมือถือ / ไม่มีเครือข่าย) สถานะของ composable จะถูกอัปเดตและ UI จะแสดงตัวบ่งชี้ที่เกี่ยวข้อง เมื่อหน้าจอปิด onDispose จะรับประกันว่าการลงทะเบียนจะถูกยกเลิก — แม้ว่าแอปพลิเคชันจะเข้าสู่พื้นหลัง

สำหรับตัวรับที่ใช้ ContextCompat.registerReceiver และแฟล็ก RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+) การใช้ DisposableEffect กลายเป็นสิ่งจำเป็นเนื่องจากระบบต้องการการระบุขอบเขตของตัวรับอย่างชัดเจน DisposableEffect รับประกันว่าขอบเขตถูกจำกัดอยู่ที่อายุของหน้าจอ ซึ่งสอดคล้องกับข้อกำหนดด้านความปลอดภัยของ Android เวอร์ชันใหม่

kotlin
@Composable
fun NetworkStatusBanner() {
    val context = LocalContext.current
    var isConnected by remember { mutableStateOf(true) }
    
    DisposableEffect(Unit) {
        val receiver = BroadcastReceiver { _, _ ->
            val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            isConnected = cm.getActiveNetwork() != null
        }
        IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).let { filter ->
            context.registerReceiver(receiver, filter)
        }
        
        onDispose {
            context.unregisterReceiver(receiver)
        }
    }
    
    if (!isConnected) { ... }
}

ข้อผิดพลาดทั่วไปกับ DisposableEffect

ข้อผิดพลาดร้ายแรงแรกคือ การขาดการเรียก onDispose. โค้ดภายในบล็อก DisposableEffect ต้องเรียก onDispose มิฉะนั้นจะเกิดข้อผิดพลาดในการคอมไพล์ อย่างไรก็ตาม นักพัฒนาบางครั้งพยายามหลีกเลี่ยงนี้โดยวาง onDispose ในเงื่อนไข: if (condition) { onDispose { ... } } โค้ดดังกล่าวจะคอมไพล์ แต่ onDispose จะไม่ถูกลงทะเบียนหากเงื่อนไขไม่เป็นจริง — ทรัพยากรจะไม่มีวันถูกปล่อย

ข้อผิดพลาดที่สองคือ การใช้ DisposableEffect สำหรับการดำเนินการแบบอะซิงโครนัส. เนื่องจาก DisposableEffect เป็นแบบซิงโครนัส จึงไม่สามารถเขียนการเรียก delay() หรือ await() ภายในได้ หากต้องการการเริ่มต้นแบบอะซิงโครนัสด้วยการทำความสะอา�ะดภายหลัง ให้ใช้การผสมผสานระหว่าง LaunchedEffect (สำหรับโหลดข้อมูล) และ DisposableEffect (สำหรับการตั้งค่า/ทำความสะอาดทรัพยากรเนทีฟ) หรือใช้กลไกแยกกับ rememberCoroutineScope

ข้อผิดพลาดที่สามคือ การสร้างอ็อบเจกต์ใหม่ภายใน DisposableEffect โดยไม่มี remember. หากอ็อบเจกต์ (เซ็นเซอร์ listener receiver) ถูกสร้างภายในเอฟเฟกต์ทุกครั้งที่เรียกและคีย์เปลี่ยนแปลงบ่อยครั้ง จะนำไปสู่การสร้างอ็อบเจกต์มากเกินไปและการเก็บขยะ ควรย้ายการสร้างอ็อบเจกต์ไปยัง remember หรือ remember { ... } นอก DisposableEffect และภายในเอฟเฟกต์ให้ลงทะเบียนและยกเลิกเท่านั้น

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

ความแตกต่างระหว่าง DisposableEffect และ LaunchedEffect คืออะไร?

DisposableEffect ทำงานแบบซิงโครนัสและให้ onDispose สำหรับการทำความสะอาดทรัพยากรอย่างชัดเจน LaunchedEffect ทำงานแบบอะซิงโครนัสใน coroutine และยกเลิกโดยอัตโนมัติเมื่อคีย์เปลี่ยนหรือออกจากคอมโพซิชัน หากทรัพยากรต้องการเรียกเมธอดทำความสะอาด (close unregister dispose) — ให้ใช้ DisposableEffect หากการดำเนินการเป็นฟังก์ชัน suspend — ให้ใช้ LaunchedEffect

บล็อก onDispose ใน DisposableEffect จำเป็นหรือไม่?

ใช่ onDispose จำเป็น — คอมไพเลอร์ Kotlin ต้องการให้เรียกภายในบล็อก DisposableEffect. หากไม่เรียก onDispose โค้ดจะไม่คอมไพล์ สิ่งนี้ทำโดยเจตนาเพื่อป้องกันการลืมของนักพัฒนาและรับประกันว่าทรัพยากรที่เปิดทุกตัวจะถูกปิดอย่างรับประกันเมื่อออกจากคอมโพซิชัน

จะจัดการข้อผิดพลาดภายใน DisposableEffect อย่างไร?

ใช้ try-catch ภายในบล็อก DisposableEffect หากการลงทะเบียนทรัพยากรอาจโยนข้อยกเว้น (เช่น ไม่พบเซ็นเซอร์) ให้ห่อด้วย try และจัดการข้อผิดพลาดใน UI ผ่านสถานะแยก onDispose ควรถูกเรียกโดยไม่ขึ้นกับความสำเร็จของการเริ่มต้น — วางไว้ในบล็อก finally หรือที่ส่วนท้ายของส่วน try

สามารถใช้ DisposableEffect สำหรับการสมัคร Flow ได้หรือไม่?

ไม่แนะนำ สำหรับ Flow ควรใช้ LaunchedEffect กับ collectLatest หรือเมธอด .collectAsState() กับ Lifecycle.repeatOnLifecycle. DisposableEffect ไม่รองรับฟังก์ชัน suspend ดังนั้นการสมัคร Flow ภายในจะต้องเริ่ม coroutine แยกผ่าน CoroutineScope ซึ่งทำให้โค้ดซับซ้อนและเพิ่มความเสี่ยงในการรั่วไหล

ในหนึ่ง composable สามารถมี DisposableEffect ได้กี่ตัว?

ไม่มีข้อจำกัด แต่แนะนำให้จัดกลุ่มทรัพยากรที่เกี่ยวข้องในหนึ่ง DisposableEffect ที่มีหลายการดำเนินการภายในและ onDispose หนึ่งตัว หากทรัพยากรเป็นอิสระ (เช่น เซ็นเซอร์และ BroadcastReceiver) ควรแบ่งเป็น DisposableEffects แยกต่างหากที่มีคีย์ต่างกัน — ซึ่งช่วยให้การแก้ไขโค้ดง่ายขึ้นและป้องกันการสร้างทรัพยากรทั้งหมดใหม่โดยไม่ต้องการเมื่อคีย์หนึ่งเปลี่ยน

สรุป

  • DisposableEffect — side-effect API Jetpack Compose สำหรับการเริ่มต้นแบบซิงโครนัสด้วยการทำความสะอาดที่รับประกันผ่าน onDispose.
  • onDispose — บล็อกบังคับที่ทำงานเมื่อออกจากคอมโพซิชันหรือเปลี่ยนคีย์ ป้องกันการรั่วไหลของหน่วยความจำ.
  • Key Points — เมื่อคีย์เปลี่ยน อันดับแรก onDispose ทำงานสำหรับค่าเก่า จากนั้นเริ่มต้นใหม่ด้วยค่าใหม่
  • ซิงโครนัส — DisposableEffect ทำงานแบบซิงโครนัส ฟังก์ชัน suspend ไม่สามารถใช้ภายในได้
  • สถานการณ์ทั่วไป — BroadcastReceiver, เซ็นเซอร์ ผู้ฟังเนทีฟ ไลบรารีที่ใช้ callback การรวม AndroidView
  • การรั่วไหล — DisposableEffect ลดจำนวนการรั่วไหลลง 60–70% เมื่อเทียบกับการจัดการด้วยตนเอง ด้วย callback วงจรอายุ
  • ข้อผิดพลาด — ความเสี่ยงหลัก: การเรียก onDispose แบบมีเงื่อนไข การใช้สำหรับการดำเนินการ async การสร้างอ็อบเจกต์โดยไม่มี remember ภายในเอฟเฟกต์

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

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

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

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