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

วิธีใช้ Handler ใน Android

ภาพรวม

ตัวอย่างนี้แสดงวิธีใช้ Handler ร่วมกับ Timer เพื่อทำงานบน UI Thread จาก Background Thread โดยเฉพาะการอัปเดต UI ทุกๆ 1 วินาที

ขั้นตอนการสร้าง

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

เปิด Android Studio ไปที่ File → New Project และกรอกข้อมูลที่จำเป็น

ขั้นตอนที่ 2: Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/my_button"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_x="0dp"
        android:layout_y="0dp"
        android:text="Moving Button" />
</LinearLayout>

ขั้นตอนที่ 3: โค้ดหลัก (src/MainActivity.java)

package com.example.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import androidx.appcompat.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.widget.Button;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {
    private Handler handler;
    private Timer timer;
    private Button button;
    private DisplayMetrics displayMetrics;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 1. สร้าง Handler ผูกกับ Main Looper (UI Thread)
        handler = new Handler(Looper.getMainLooper());
        
        button = findViewById(R.id.my_button);
        displayMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        
        // 2. เริ่ม Timer
        startTimer();
    }

    private void startTimer() {
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // 3. Post งานไปที่ UI Thread ผ่าน Handler
                handler.post(() -> moveButtonRandomly());
            }
        }, 0, 1000); // เริ่มทันที ทุก 1000ms
    }

    private void moveButtonRandomly() {
        Random random = new Random();
        float dx = random.nextFloat() * (displayMetrics.widthPixels - button.getWidth());
        float dy = random.nextFloat() * (displayMetrics.heightPixels - button.getHeight());
        
        button.animate()
            .x(dx)
            .y(dy)
            .setDuration(500)
            .start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 4. หยุด Timer และล้าง Handler (ป้องกัน Memory Leak)
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
        if (handler != null) {
            handler.removeCallbacksAndMessages(null);
        }
    }
}

หลักการทำงานของ Handler

  • Handler ผูกกับ Looper (Message Queue) ของ Thread
  • Main Thread (UI Thread) มี Looper อยู่แล้ว ใช้ Looper.getMainLooper()
  • post(Runnable) - ใส่งานใน Queue รอประมวลผลบน Thread นั้น
  • postDelayed(Runnable, delayMs) - รอแล้วค่อยทำ

วิธีสร้าง Handler (API 30+ Deprecated Constructor)

วิธีAPI Levelคำอธิบาย
new Handler(Looper.getMainLooper())Allแนะนำ ใช้ได้ทุก Version
new Handler(Looper.myLooper())Allสำหรับ Thread ปัจจุบัน
Handler.createAsync(looper)API 30+สำหรับ Async Task (Non-blocking)
new Handler()Deprecated API 30ใช้ Looper ของ Thread ปัจจุบัน (อาจไม่ใช่ Main)

Handler Use Cases ทั่วไป

1. Delayed Execution (เลื่อนเวลาทำงาน)

handler.postDelayed(() -> {
    // ทำงานหลัง 2 วินาที
    textView.setText("Delayed!");
}, 2000);

2. Periodic Task (ทำงานซ้ำ) - ใช้ Handler แทน Timer

private Runnable periodicTask = new Runnable() {
    @Override
    public void run() {
        // ทำงานที่นี่
        updateUI();
        
        // Schedule ครั้งถัดไป
        handler.postDelayed(this, 1000);
    }
};

// เริ่ม
handler.post(periodicTask);

// หยุด
handler.removeCallbacks(periodicTask);

3. Communicate from Background Thread

new Thread(() -> {
    // Background work
    String result = doHeavyWork();
    
    // Post กลับ UI Thread
    handler.post(() -> {
        textView.setText(result);
    });
}).start();

4. Message + Handler (สำหรับข้อมูลซับซ้อน)

Handler handler = new Handler(Looper.getMainLooper(), msg -> {
    if (msg.what == 1) {
        String data = (String) msg.obj;
        textView.setText(data);
    }
    return true;
});

// ส่งจาก Background
Message msg = handler.obtainMessage(1, "Hello from background");
handler.sendMessage(msg);

⚠️ ข้อควรระวัง (Common Pitfalls)

  • Memory Leak: Handler เก็บ Reference ไปยัง Activity/View → ใช้ removeCallbacksAndMessages(null) ใน onDestroy
  • Leak กับ Anonymous Inner Class: ใช้ static class หรือ WeakReference
  • Timer + Handler: Timer รันบน Thread แยก ต้อง post ผ่าน Handler เสมอ
  • Handler ใน doze mode: อาจ Delay มากกว่าที่ตั้งไว้

ทางเลือกสมัยใหม่

  • Coroutines + Flow: delay(1000), flow { while(true) { emit(); delay(1000) } }
  • WorkManager: สำหรับ Background Task ที่ต้องทำงานแม้แอปปิด
  • RxJava: Observable.interval(1, TimeUnit.SECONDS)