Handler คืออะไร?
Handler เป็นกลไกหลักใน Android สำหรับการส่งข้อความ (Message) และ Runnable ระหว่าง Thread โดยเฉพาะการสื่อสารจาก Background Thread ไปยัง Main Thread (UI Thread) เนื่องจาก Android ไม่อนุญาตให้เข้าถึง UI จาก Thread อื่นโดยตรง
หลักการทำงาน
- Looper: วนลูปรอ Message จาก MessageQueue
- MessageQueue: คิวเก็บ Message/Runnable แบบ FIFO
- 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 สำหรับกรณีง่ายๆ