티스토리 뷰

📱 Android의 UI 동작

안드로이드의 UI 처리는 싱글 쓰레드 모델로 동작한다. 즉 메인 쓰레드가 아닌 다른 쓰레드에서 UI 관련 작업을 하면 안된다. 따라서 안드로이드에서는 메인쓰레드 = UI 쓰레드이다.

❓왜 싱글 쓰레드일까?

안드로이드 뿐만 아니라 ios, Swing 등 대부분 UI 프레임워크는 UI 쓰레드 하나만 사용한다. 그 이유는 동시 접근 문제를 방지하기 위해서이다.

 

1. Thread-Safe하지 않다.

Button, TextView, RecyclerView 등 UI 요소는 멀티 쓰레드에서 동시에 접근하면 상태가 꼬이거나 앱이 크래시 날 수 있다.

위 코드와 같이 두 개의 쓰레드에서 TextView의 값을 병렬적으로 동시에 바꾼하고 가정하면 어느 값이 최종적으로 표시될지 예측이 불가하다. 즉, UI 객체는 여러 쓰레드가 안전하게 수정할 수 있도록 만들어진 구조가 아니다.

 

2. Context Switching 비용 방지

UI는 사용자의 입력, 화면 그리기 등 실시간성이 중요하다. 사용자가 ‘즉각적이다’ 라고 느끼려면 UI를 60fps 기준으로 16ms안에 화면을 그려야 버벅임이 없다. 하지만 여러 쓰레드에서 UI를 동시에 조작하게 되면 Context Switching이 늘어난다. 이렇게 스위칭이 많아질수록 UI 쓰레드가 화면을 그릴 시간을 뺏기고 시간 안에 렌더링 하지 못해 프레임이 떨어진다. 즉, 사용자는 ‘렉이 걸린다’ 라고 느낀다.

🤙 싱글 쓰레드 모델에서 지켜야할 점

1. 메인쓰레드를 블로킹 시키면 안된다.

메인 쓰레드를 블로킹하게 되면 사용자에게 보여지는 UI 동작을 멈춘다. 이렇게 되면 사용자는 앱이 멈춘 것 처럼 느끼게 된다.

또한 5초 이상 이벤트 처리를 하지 못하면 ANR이 발생하며 앱이 응답하지 않게 된다. 이는 사용자가 앱을 강제종료하거나 삭제하게 만드는 이유가 될 수 있다.

2. 시간이 오래 걸리는 작업은 다른 쓰레드에서 수행해야한다.

위의 이유로 인해 무거운 동작들은 메인쓰레드를 블로킹하기 때문에 메인 쓰레드가 아닌 다른 쓰레드를 생성하여 수행해야한다.

 

📲 쓰레드 간의 통신 방법

다음과 같이 새로운 쓰레드에서 서버 API를 호출하여 햄스터 사진주소를 받은 후, 메인쓰레드로 이동시켜 화면에 보여주고 싶다.다른 쓰레드에서 작업한 내용을 메인쓰레드로 어떻게 전달할 수 있을까?

전통적인 방법으로는 Looper와 Handler를 사용하여 쓰레드 간의 통신을 구현할 수 있다.

 

🔁 Looper

쓰레드에 메시지 루프를 붙여주는 클래스이다. 즉, Looper는 그 쓰레드가 작업이 끝나도 계속 살아있으면서 할 일을 기다리도록 만들어준다. 말 그대로 빙빙 도는 친구이다.

하지만 모든 쓰레드가 Looper를 가지고 있지는 않다. 메인 쓰레드만 기본적으로 Looper가 붙어 있고, 다른 쓰레드에서 Looper를 쓰고 싶으면 직접 준비해주어야한다. 하나의 쓰레드에는 한 개의 Looper를 가질 수 있다.

Thread {
    Looper.prepare() //Looper 준비
    .
    .
    .
    Looper.loop() // 메시지 루프 시작
}.start()

 

📮 MessageQueue

Looper 내부에는 MessageQueue가 존재한다. Looper가 생성됨과 동시에 MessageQueue도 생성된다. 이 안에는 해당 쓰레드가 할 일들이 ‘메시지’ 형태로 쌓이게 된다. 말 그대로 메시지를 저장하는 큐(대기열)이다.

메시지는 크게 두 가지 종류로 나눌 수 있다.

  • Runnable 기반 메시지: MessageQueue에서 Runnable 메시지가 담겨 있으면 Handler에 메시지를 전달하지 않고 run()을 수행하여 해당 Runnable 작업을 바로 수행한다.
    • 전달할 데이터가 없이 실행할 동작을 담은 메시지이다.
  • data 기반 메시지: Runnable 기반의 메시지가 아니면 Message 객체 내부에 명시되어있는 Handler의 handleMessage()를 수행하여 처리한다.
    • 데이터를 전달하고 핸들러에서 처리할 메시지이다.

🚘 Handler

메시지를 MessageQueue에 넣고 나중에 그 메시지를 꺼내 실행하는 역할을 한다.

  • 다른 쓰레드로 메시지를 전달하는 경우
    • sendMessage() 를 통해 data 기반의 메시지를 전달한다.
    • post{} 를 통해 Runnable 기반의 메시지를 전달한다.
  • 현재 쓰레드에서 Looper로부터 메시지를 전달 받은 경우
    • Runnable 기반의 메시지인 경우 run()을 호출하여 작업을 수행한다.
    • 데이터 기반의 메시지인 경우 handleMessage()를 호출하여 해당 Handler가 메시지를 전달 받는다.

사진 출처: https://velog.io/@buna1592/Android-Thread간-통신-using-Handler-Looper

🧑‍💻 코드 예시

 //ViewModel
 private val _hamsterUrl = MutableLiveData<String>()
 val hamsterUrl: LiveData<String> get() = _hamsterUrl
    
    init {
        loadHamsterUrl()
    }

    private val mainHandler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            if (msg.what == MSG_UPDATE) {
                val url = msg.obj as String
                _hamsterUrl.value = url
            }
        }
    }

    private fun loadHamsterUrl() {
        Thread {
            val url = repository.fetchHamsterUrl()

            val msg = Message.obtain().apply {
                what = MSG_UPDATE
                obj = url
            }
            mainHandler.sendMessage(msg)
        }.start()
    }
    
    companion object {
        private const val MSG_UPDATE = 1
    }

위 내용을 바탕으로 햄스터 사진을 화면에 띄우는 코드는 다음과 같다.

  1. 익명 객체를 사용하여 handleMessage를 오버라이딩해준다. 오버라이딩 해주지 않아도 Handler 객체 생성은 가능하지만, sendMessage 함수를 통해 메시지를 전송했을 때 handleMessage()가 없다면 Looper로 부터 메시지를 받은 핸들러는 아무 작업도 하지 않는다. 하지만 post{} 를 통한 Runnable 기반의 메시지라면 handleMessage를 오버라이딩 해주지 않아도 된다.
  2. 핸들러의 Looper를 명시해준다. 핸들러가 Looper를 암시적으로 선택하게 되지만, 이 과정에서 버그가 많이 발생하여 현재는 deprecated되고 Looper를 직접 지정해주는 것을 권장한다.
  3. 메인 쓰레드로 보낼 Message 객체를 obtain을 통해 생성한다. obtain은 Message객체를 효율적으로 생성하는 팩토리 메서드이다. Message Pool에 재활용 가능한 Message가 있다면 활용하고, 없으면 새로 생성해준다.
  4. 생성한 메시지를 sendMessage를 통해 전송한다.

'안드로이드' 카테고리의 다른 글

Lottie  (0) 2025.11.05
LifeCycle Of ViewModel  (1) 2025.10.05
DataBinding, BindingAdapter를 사용할 때 무한루프  (2) 2025.08.02
ViewModel Test  (7) 2025.07.27
RecyclerView와 Adapter  (0) 2025.04.28
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함