안드로이드 앱 개발에서 UI 스레드가 단일 스레드 모델을 사용하기 때문에 비동기 작업 처리가 필수적이다.
메시지 기반의 비동기 처리를 위해 안드로이드에서는 Looper와 Handler라는 핵심 컴포넌트를 제공한다.
이 글에서는 Looper와 Handler의 개념부터 사용 방법, 동작 흐름, 실전 활용 사례 및 비교와 주의사항을 정리해보려 한다.
안드로이드 애플리케이션은 메인(UI) 스레드를 통해 화면 갱신 및 사용자 이벤트를 처리한다.
단일 스레드 모델이기 때문에 긴 작업이 UI 스레드에서 실행되면 앱이 멈춘 것처럼 보이는 ANR(Application Not Responding) 현상이 발생한다.
이를 방지하기 위해 메시지 큐(MessageQueue)에 작업을 등록하고 순차적으로 처리하는 메시지 루프(Message Loop) 구조를 사용한다.
메시지 큐에 담긴 메시지 또는 Runnable 객체를 꺼내어 실행하는 반복 구조를 메시지 루프라고 한다.
Looper는 이 메시지 루프를 관리하여 대기 중인 작업을 하나씩 처리한다.
메인 스레드에는 시스템에서 자동으로 Looper가 준비되고 루프가 실행된다.
Looper는 특정 스레드의 메시지 큐를 지속적으로 감시하면서 메시지나 Runnable 작업을 꺼내어 실행하는 클래스로 정의된다.
메인 스레드뿐만 아니라 백그라운드 스레드에서도 직접 Looper를 생성하여 메시지 루프를 운영할 수 있다.
백그라운드 스레드에서 Looper를 사용하려면 먼저 Looper.prepare()로 메시지 큐를 생성한다.
이후 Handler를 생성하거나 작업 등록을 마친 뒤 Looper.loop()를 호출하여 메시지 루프를 시작한다.
작업 처리가 끝나면 Looper.quit() 또는 Looper.quitSafely()로 루프를 종료할 수 있다.
Thread thread = new Thread(() -> {
Looper.prepare(); // 메시지 큐 준비
Handler handler = new Handler(); // 현재 스레드의 Looper와 연결된 Handler 생성
Looper.loop(); // 메시지 루프 시작
// 루프 종료 시점 이후 코드 실행
});
thread.start();
Handler는 특정 Looper(스레드)의 메시지 큐에 Message 또는 Runnable 작업을 등록하는 도구로 동작한다.
이를 통해 스레드 간 안전한 통신과 UI 업데이트 예약이 가능하다.
post(Runnable r): Runnable을 즉시 메시지 큐에 등록한다. postDelayed(Runnable r, long delayMillis): 지정 시간 후 Runnable을 실행하도록 등록한다. sendMessage(Message msg): Message 객체를 메시지 큐에 등록한다. sendMessageDelayed(Message msg, long delayMillis): 지연 후 Message를 처리하도록 등록한다. handleMessage(Message msg): Message가 도착했을 때 로직을 구현하는 콜백 메서드이다. 메인 스레드 UI 업데이트용 Handler 생성 예제이다.
val mainHandler = Handler(Looper.getMainLooper())
mainHandler.post {
// UI 업데이트 작업 실행
}
백그라운드 스레드에서 작업 후 UI 업데이트를 위해 메인 스레드 Handler를 활용한다.
Thread {
// 백그라운드 연산 수행
mainHandler.post {
// 백그라운드 결과를 UI에 반영
}
}.start()
handleMessage() 또는 Runnable.run()을 호출한다. Message는 안드로이드 프레임워크에서 제공하는 데이터 컨테이너이다. what, arg1, arg2, obj 등의 필드에 정수나 객체를 담아 전송할 수 있다. Handler.sendMessage(Message msg)로 메시지 큐에 등록한 뒤, Handler의 handleMessage(Message msg) 메서드에서 처리한다. val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
1 -> {
// 메시지 처리 로직
}
}
}
}
val msg = Message.obtain().apply {
what = 1
obj = "데이터"
}
handler.sendMessage(msg)
Runnable은 자바 표준 인터페이스로, run() 메서드에 실행할 코드를 담는다. Handler.post(Runnable r) 또는 postDelayed(Runnable r, delay)로 메시지 큐에 등록하면, 메시지 큐 내부에서 Runnable.run()이 호출된다. val handler = Handler(Looper.getMainLooper())
handler.post {
// UI 업데이트 또는 실행할 코드
}
| 구분 | 용도 |
|---|---|
| Message | 여러 종류의 메시지 구분, 데이터 전달, 재사용 오버헤드 절감 |
| Runnable | 즉시 실행할 코드 블록 전달, 간단한 UI 작업 스케줄링 |
이처럼 Handler는 Message 객체 또는 Runnable 객체를 메시지 큐에 등록하여, 등록된 작업이 순차적으로 처리되도록 한다.
postDelayed()와 sendMessageDelayed()를 활용해 특정 간격으로 작업을 반복 실행한다. | 방식 | 사용 시점 | 장점 | 단점 |
|---|---|---|---|
| Handler(Looper.getMainLooper()) | 정밀 스케줄링, 레거시 코드 | 지연/반복 제어 가능, 직접 메시지 큐 제어 | 코드 복잡, 메모리 누수 위험 |
| view.post | 특정 View와 연계된 간단 UI 업데이트 | 별도 Handler 생성 불필요, 간편 | View detach 시 실행되지 않을 수 있음 |
| CoroutineScope(Dispatchers.Main) | 최신 비동기 처리 | 코드 가독성 우수, structured concurrency 지원 | 코루틴 학습 필요 |
static inner 클래스와 WeakReference를 활용해 Handler를 정의한다. HandlerThread를 활용하면 Looper 준비와 스레드 관리를 간편하게 처리할 수 있다. Looper와 Handler는 안드로이드에서 메시지 기반 비동기 처리를 가능하게 하는 핵심 컴포넌트이다.
메인 스레드의 안정적 UI 처리와 백그라운드 스레드 간 통신을 위해 Looper와 Handler의 동작 원리를 정확히 이해하는 것이 중요하다.
실제 프로젝트에서 다양한 활용 사례와 주의사항을 참고하여 안전하고 효율적인 비동기 처리를 구현한다.