สรุปความแตกต่างเชิงเทคนิคของการเขียน 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:

  1. UI Freeze: หน้าจอค้าง ตอบสนองไม่ได้
  2. 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

MechanismMinimum IntervalLifecycle ScopeExecution GuaranteeBest Use Case
Coroutine (delay)MillisecondsForeground (App Active)None (Killable)UI Animation, Polling, Countdown
WorkManager15 MinutesBackground (Persisted)GuaranteedSync Data, Database Backup, Logs Upload
AlarmManagerExact TimeSystem (Wake up)GuaranteedAlarm Clock, Calendar Reminder

5. Android Java Limitations

สถานะปัจจุบันของ Java Concurrency บน Android:

  1. Project Loom (Virtual Threads): Feature ใหม่ของ Java 21+ ที่ทำงานคล้าย Coroutines (Lightweight Threads)
  2. Android Support: ปัจจุบัน Android Runtime (ART) ยังไม่รองรับ Virtual Threads
  3. 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 ปกติ