สรุปความแตกต่างเชิงเทคนิคของการเขียน Loop และ Recurring Task ใน Android ระหว่างยุค Java (Blocking Model) และ Kotlin (Suspending Model) รวมถึงข้อจำกัดของ Android Runtime (ART) ในปัจจุบัน
1. Problem: Blocking the Main Thread
ใน Android Main Thread (UI Thread) มีหน้าที่รับผิดชอบการวาด UI และ Touch Event Loop หากมีการเขียน while loop ที่ใช้เวลานานหรือทำงานตลอดเวลา (Infinite Loop) บน Main Thread จะส่งผลโดยตรงต่อประสิทธิภาพ
Java Code (Anti-Pattern):
// Anti-Pattern: Blocking UI Loop
public void onCreate() {
// UI Thread จะติดอยู่ใน Loop ไม่สามารถออกไปวาดหน้าจอได้
while (true) {
if (checkStatus() == READY) break;
// Thread.sleep() จะหยุดการทำงานของ Thread ทั้งเส้น
Thread.sleep(1000);
}
}
Consequences:
- UI Freeze: หน้าจอค้าง ตอบสนองไม่ได้
- ANR: เกิด Application Not Responding หาก Main Thread ถูกบล็อกเกิน 5 วินาที
2. Legacy Solution: Recursive Handler (Java)
ใน Java บน Android การทำ loop โดยไม่บล็อก UI จำเป็นต้องใช้ Handler หรือ Timer เพื่อสร้าง Loop เสมือน (Recursion via Scheduling)
// Legacy Pattern: Recursive Handler
Handler handler = new Handler(Looper.getMainLooper());
Runnable runnableCode = new Runnable() {
@Override
public void run() {
// 1. Execute task
checkStatus();
// 2. Schedule next run in 1000ms (Non-blocking)
handler.postDelayed(this, 1000);
}
};
// Start Loop
handler.post(runnableCode);
Technical Trade-offs:
- Boilerplate: ต้องประกาศ Handler/Runnable แยกชัดเจน
- Lifecycle Management: ต้องเรียก
removeCallbacks()ด้วยตนเองเมื่อ Activity ถูกทำลาย (onDestroy) เพื่อป้องกัน Memory Leak
3. Modern Solution: Suspending Loop (Kotlin)
ใน Kotlin Coroutines สามารถใช้ while loop ปกติได้ เนื่องจากกลไก Suspension ที่แตกต่างจากการ Blocking
Mechanism: delay() vs Thread.sleep()
Thread.sleep(1000): สั่ง OS Thread ให้หยุดทำงาน (Block)delay(1000): เป็น Suspend Function สั่งให้ Coroutine หยุดชั่วคราวและ คืน Thread ให้ระบบนำไปใช้งานอื่น (Non-blocking)
Visualizing the Difference:
1. Java Blocking Pattern (Thread.sleep)
Main Thread ถูกยึดครองโดยคำสั่ง Sleep ทำให้ไม่สามารถไปวาดหน้าจอได้
Timeline: ----------------------------------------->
[Main Thread] [ Work ] [ Sleep........... ] [ Work ]
( ❌ Blocked: UI Freeze )
2. Kotlin Suspending Pattern (delay)
Coroutine จะกระโดดออกจาก Main Thread ชั่วคราว (Suspend) ทำให้ Main Thread ว่างสำหรับงาน UI
Timeline: ----------------------------------------->
[Main Thread] [ Work ] [ Draw UI / Handle Touch ] [ Work ]
| ( ✅ Active: Any Job ) ^
v |
(Suspend) . . . . (Timer 1s) . . . . (Resume)
Kotlin Code:
// Modern Pattern: Coroutine Loop
viewModelScope.launch {
// 'isActive' เช็คสถานะของ Coroutine (ออกจาก Loop อัตโนมัติเมื่อ ViewModel ตาย)
while (isActive) {
checkStatus()
// ไม่ Block Main Thread: UI ยังคงทำงานได้ปกติ
delay(1000)
}
}
Handling Heavy Tasks & UI Updates
หากงานใน Loop มีการใช้ CPU หรือ I/O หนัก ต้องมีการสลับ Dispatchers ให้เหมาะสม:
viewModelScope.launch { // Default: Main Thread
while (isActive) {
// 1. Offload Heavy Task to IO Thread
val data = withContext(Dispatchers.IO) {
api.getDataFromNetwork()
} // Returns to Main Thread automatically
// 2. Update UI (Safe on Main Thread)
textView.text = data.title
delay(5000)
}
}
Explicit Thread Switching (IO to Main):
หาก Coroutine เริ่มต้นที่ Dispatchers.IO ต้องใช้ withContext(Dispatchers.Main) เพื่ออัปเดต UI
CoroutineScope(Dispatchers.IO).launch {
while(isActive) {
val stockPrice = api.getPrice() // Run on IO
// Switch to Main for UI update
withContext(Dispatchers.Main) {
tvPrice.text = stockPrice
}
delay(1000)
}
}
4. Alternatives for Recurring Tasks
ตารางเปรียบเทียบเครื่องมือสำหรับงานประเภท “ทำซ้ำทุกๆ X เวลา” (Recurring Tasks) บน Android
| Mechanism | Minimum Interval | Lifecycle Scope | Execution Guarantee | Best Use Case |
|---|---|---|---|---|
| Coroutine (delay) | Milliseconds | Foreground (App Active) | None (Killable) | UI Animation, Polling, Countdown |
| WorkManager | 15 Minutes | Background (Persisted) | Guaranteed | Sync Data, Database Backup, Logs Upload |
| AlarmManager | Exact Time | System (Wake up) | Guaranteed | Alarm Clock, Calendar Reminder |
5. Android Java Limitations
สถานะปัจจุบันของ Java Concurrency บน Android:
- Project Loom (Virtual Threads): Feature ใหม่ของ Java 21+ ที่ทำงานคล้าย Coroutines (Lightweight Threads)
- Android Support: ปัจจุบัน Android Runtime (ART) ยังไม่รองรับ Virtual Threads
- Result: ผู้ที่พัฒนาด้วย Java บน Android ยังคงติดข้อจำกัดเดิม คือต้องใช้
Handler,ExecutorServiceหรือRxJavaไม่สามารถเขียน Loop แบบ Synchronous style ได้เหมือน Kotlin
สรุป
- Java: ห้ามใช้
whileบน Main Thread ต้องใช้HandlerหรือTimerแทน - Kotlin: ใช้
whileคู่กับdelay()ได้ เพราะเป็น Non-blocking mechanism - Background Tasks: หากต้องการทำงานระยะยาว (เมื่อปิดแอพ) ควรใช้ WorkManager แทน Loop ปกติ