Giới thiệu
Handler cho phép gửi và xử lý Message hoặc Runnable trên thread liên kết (thường là Main Thread/UI Thread). Dùng cho: trì hoãn thực thi, lặp lại định kỳ, giao tiếp background→UI thread.
Các trường hợp sử dụng phổ biến
- Delay thực thi:
postDelayed(runnable, delayMillis) - Lặp lại định kỳ: tự schedule lại trong runnable.
- Cập nhật UI từ background thread:
handler.post(runnable). - Xử lý Message phức tạp:
sendMessage(msg),handleMessage(msg).
Ví dụ: Di chuyển Button ngẫu nhiên mỗi giây (Cải tiến từ ví dụ gốc)
Layout (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btnMoving"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:text="Bắt tôi!"
android:layout_gravity="top|start" />
</FrameLayout>
MainActivity.java
package com.example.myapplication;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.Button;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
private Button btnMoving;
private Handler handler = new Handler(); // Gắn với Main Looper
private Random random = new Random();
private int screenWidth, screenHeight;
private boolean isRunning = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnMoving = findViewById(R.id.btnMoving);
// Lấy kích thước màn hình
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
btnMoving.post(() -> {
// Trừ kích thước button
screenWidth -= btnMoving.getWidth();
screenHeight -= btnMoving.getHeight();
startMoving();
});
}
private void startMoving() {
isRunning = true;
moveRandomly();
}
private void moveRandomly() {
if (!isRunning || screenWidth <= 0 || screenHeight <= 0) return;
float x = random.nextInt(screenWidth);
float y = random.nextInt(screenHeight);
// Animation mượt mà
btnMoving.animate()
.x(x)
.y(y)
.setDuration(800)
.withEndAction(() -> {
// Schedule lần sau sau 1 giây
if (isRunning) {
handler.postDelayed(this::moveRandomly, 1000);
}
})
.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
isRunning = false;
handler.removeCallbacksAndMessages(null); // Hủy tất cả pending tasks
}
}
Handler vs Thread vs Executor vs Coroutines
| Cơ chế | Dùng cho | Ưu điểm | Nhược điểm |
|---|---|---|---|
| Handler | UI thread tasks, delay, message loop | Đơn giản, gắn liền Looper | Dễ leak memory nếu không remove callbacks |
| Thread/Executor | Background work, CPU-intensive | Tách khỏi UI thread | Không thể cập nhật UI trực tiếp |
| AsyncTask | Deprecated (API 30) | - | Không dùng nữa |
| Coroutines/Flow | Async modern, structured concurrency | Type-safe, cancellation, testable | Cần Kotlin, setup |
| RxJava | Reactive streams, complex async | Powerful operators | Verbose, memory leak risk |
Memory Leak Prevention
- Luôn
handler.removeCallbacksAndMessages(null)trongonDestroy()/onStop(). - Dùng
static class MyHandler extends Handler+WeakReference<Activity>nếu Handler là inner class. - Ưu tiên
View.postDelayed()/View.removeCallbacks()- tự hủy khi View detach.
Chạy ứng dụng
Nhấn Run. Button nhảy liên tục đến vị trí ngẫu nhiên mỗi giây với animation mượt.

Handler hiện đại (API 30+)
// Tạo Handler gắn với Main Looper rõ ràng
Handler handler = new Handler(Looper.getMainLooper());
// Hoặc dùng Executor cho background
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
// Background work
new Handler(Looper.getMainLooper()).post(() -> {
// Update UI
});
});