(Android/안드로이드) Handler(Looper.getMainLooper()) 완전 정리

✨ 개요


1. 요약


2. Handler와 Looper를 한 문장으로 설명하면?

즉, Handler(Looper.getMainLooper())는:

“메인 스레드(=UI 스레드)의 이벤트 큐에 작업을 넣어서, UI 스레드에서 실행되게 하겠다”


3. Looper.getMainLooper() vs Looper.myLooper()


4. 가장 많이 쓰는 패턴 4가지

4.1 메인 스레드로 전환(즉시 실행)

백그라운드 스레드에서 UI를 갱신해야 할 때:

val mainHandler = Handler(Looper.getMainLooper())

fun updateUiFromBg() {
    mainHandler.post {
        // UI 업데이트는 여기서
        binding.textView.text = "Updated"
    }
}

4.2 1초 지연 실행

val mainHandler = Handler(Looper.getMainLooper())

mainHandler.postDelayed({
    // 1초 후 실행
}, 1000L)

4.3 디바운스(Debounce) 연속 호출 중 마지막만 실행

검색창 입력, 다중 클릭 등:

private val mainHandler = Handler(Looper.getMainLooper())
private var debounceRunnable: Runnable? = null

fun debounceSearch(query: String) {
    debounceRunnable?.let { mainHandler.removeCallbacks(it) }

    debounceRunnable = Runnable {
        // 마지막 입력 이후 300ms 지나면 실행
        performSearch(query)
    }.also {
        mainHandler.postDelayed(it, 300L)
    }
}

4.4 특정 Runnable을 취소(removeCallbacks)

val mainHandler = Handler(Looper.getMainLooper())
val r = Runnable { /* 실행 */ }

mainHandler.postDelayed(r, 1000L)

// 필요 시 취소
mainHandler.removeCallbacks(r)

5. 내부 동작

Coroutine delay는 어떤 Dispatcher에서도 사용할 수 있다.

5-1. Main에서 delay 후 UI 갱신


6. 메모리 누수/크래시를 피하는 핵심 규칙

6.1 Activity/Fragment 참조를 오래 잡지 않기

Handler로 딜레이를 걸면, Runnable이 실행될 때까지 외부 참조가 유지될 수 있습니다.

안전한 습관:

Fragment 예시:

private val mainHandler = Handler(Looper.getMainLooper())
private var pending: Runnable? = null

override fun onDestroyView() {
    pending?.let { mainHandler.removeCallbacks(it) }
    pending = null
    super.onDestroyView()
}

6-2. View가 이미 사라졌는데 UI 갱신하려는 문제


7. Coroutine / View.postDelayed와 비교(언제 Handler가 맞나)


8. 결론

Handler(Looper.getMainLooper())는 “메인 스레드 큐에 작업을 예약”하는 가장 전통적인 방식이며, 지금도 실무에서 충분히 유효합니다.



Related Posts