Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

Handler ใน Android: การสื่อสารระหว่าง Thread


Handler คืออะไร?

Handler เป็นกลไกหลักใน Android สำหรับการส่งข้อความ (Message) และ Runnable ระหว่าง Thread โดยเฉพาะการสื่อสารจาก Background Thread ไปยัง Main Thread (UI Thread) เนื่องจาก Android ไม่อนุญาตให้เข้าถึง UI จาก Thread อื่นโดยตรง

หลักการทำงาน

  1. Looper: วนลูปรอ Message จาก MessageQueue
  2. MessageQueue: คิวเก็บ Message/Runnable แบบ FIFO
  3. Handler: ส่ง Message เข้า Queue และประมวลผลเมื่อ Looper ดึงออกมา

แต่ละ Thread มี Looper ได้สูงสุด 1 ตัว (Main Thread มี Looper อยู่แล้ว)

เมธอดหลักของ Handler

เมธอดคำอธิบาย
post(Runnable)ส่ง Runnable ไปรันบน Thread ที่ Handler ผูกอยู่
postDelayed(Runnable, long)ส่ง Runnable พร้อมหน่วงเวลา (ms)
postAtTime(Runnable, long)ส่ง Runnable รันในเวลาเฉพาะ (uptimeMillis)
sendMessage(Message)ส่ง Message object
sendEmptyMessage(int what)ส่ง Message เฉพาะรหัส what
obtainMessage()สร้าง Message จาก Pool (ประหยัดหน่วยความจำ)
handleMessage(Message)Override เพื่อประมวลผล Message (รันบน Thread ของ Handler)
removeCallbacksAndMessages(null)ล้างทุกอย่างใน Queue (สำคัญใน onDestroy)

ตัวอย่าง: Progress Dialog ด้วย Handler

Layout (activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btnStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="เริ่มดาวน์โหลด"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.java

import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private Button btnStart;
    private ProgressDialog progressDialog;
    private Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnStart = findViewById(R.id.btnStart);
        
        // สร้าง Handler ผูกกับ Main Looper (แนะนำสำหรับ API 30+)
        handler = new Handler(Looper.getMainLooper(), msg -> {
            if (msg.what == 1) {
                int progress = msg.arg1;
                progressDialog.setProgress(progress);
                if (progress >= 100) {
                    progressDialog.dismiss();
                }
            }
            return true;
        });

        btnStart.setOnClickListener(v -> startDownload());
    }

    private void startDownload() {
        progressDialog = new ProgressDialog(this);
        progressDialog.setTitle("กำลังดาวน์โหลด");
        progressDialog.setMessage("กรุณารอสักครู่...");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setMax(100);
        progressDialog.setCancelable(false);
        progressDialog.show();

        // จำลอง Background Thread
        new Thread(() -> {
            try {
                for (int i = 0; i <= 100; i++) {
                    Thread.sleep(50); // จำลองงานหนัก
                    
                    // ส่ง progress ไป UI Thread ผ่าน Handler
                    Message msg = handler.obtainMessage(1);
                    msg.arg1 = i;
                    handler.sendMessage(msg);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // กัน Memory Leak: ล้าง callbacks และ messages
        handler.removeCallbacksAndMessages(null);
        if (progressDialog != null && progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }
}

รูปแบบการใช้งานที่ทันสมัย (Modern Approach)

ปัจจุบันแนะนำให้ใช้ Coroutines (Kotlin) หรือ RxJava แทน Handler โดยตรง เพราะ:

  • โค้ดอ่านง่ายกว่า (Structured Concurrency)
  • จัดการ Error และ Cancellation ได้ดีกว่า
  • หลีกเลี่ยง Memory Leak ได้อัตโนมัติ

ตัวอย่าง Kotlin Coroutines

// ใน ViewModel หรือ Activity (ต้องมี lifecycle-runtime-ktx)
lifecycleScope.launch {
    progressDialog.show()
    for (i in 0..100) {
        delay(50) // non-blocking
        progressDialog.progress = i
    }
    progressDialog.dismiss()
}

สรุปและข้อควรระวัง

  • Memory Leak: Handler ระยะยาวอาจถือ reference ของ Activity ให้ล้างใน onDestroy()
  • Handler(Looper): API 30+ ใช้ Constructor ที่รับ Looper แทน Constructor ว่าง
  • Static Inner Class: ใช้ static class MyHandler extends Handler + WeakReference<Activity> เพื่อป้องกัน Leak
  • Alternative: ใช้ runOnUiThread(), View.post(), หรือ Coroutines สำหรับกรณีง่ายๆ