안드로이드에서는 UI처리는 싱글 스레드 모델로 동작한다.
즉, 메인 스레드가 아닌 다른 스레드에서 UI를 업데이트하는 등의 행위는 불가능하다.
그래서 메인 스레드를 UI 스레드라고 부르기도 한다.
멀티 스레드 환경에서 동작한다고 가정한다면, 여러 스레드에서 UI 업데이트를 동시에 진행한다고 하면 과연 어떻게 될지 상상해보면 왜 싱글 스레드 모델로 동작해야하는지 알 수 있다.
UI 동작의 무결성을 보장하기 위해 다른 스레드에서는 UI를 건드릴 수 없고, 오로지 메인 스레드에서만 UI 관련 동작을 허용한다.
안드로이드에서는 UI 가 5초 이상 반응이 없을 경우 ANR(Android Not Responding)이 발생하며 앱이 죽어버리는 현상이 생긴다. ANR 발생을 막기 위해서는 메인 스레드에서 시간이 오래 걸리는 동작은 피하는 것이 좋다.
시간이 오래걸리는 DB 또는 통신 같은 작업은 메인 스레드가 아닌 다른 스레드에서 동작하도록 하고 UI 업데이트를 진행하고 싶을 경우 메인 스레드로 결과를 전송해야한다. 즉, 스레드 간의 통신을 구현해야하는데 이때 필요한 것이 Looper 와 Handler 이다.
하나의 스레드에는 오직 하나의 looper를 가지며, looper는 오직 하나의 스레드를 담당한다. 안드로이드 에서는 기본적으로 MainActivity 가 실행됨과 동시에 자동으로 메인 스레드의 looper가 돌기 시작한다.
각 스레드의 looper 내부에는 MessageQueue가 존재한다. 이 큐안에는 해당 스레드가 처리해야 할 동작들이 '메세지'라는 형태로 쌓이게 된다. (FIFO)
여기서 looper는 MessageQueue에 들어오는 메세지들을 하나씩 꺼내어 이를 handler로 전달하는 역할을 한다.
'하나의 작업 단위'
MessageQueue에는 이러한 작업 단위를 하나씩 적재해두고, Looper가 무한루프를 돌면서 하나씩 차례대로 처리한다.
Message 객체의 값은 Runnable 객체, Message 객체 두가지 종류로 이루어진다.
Runnable 객체일 경우 Handler 에 메세지를 전달하지 않고 run()을 수행한다.
Runnable 객체가 아닐 경우 Message 객체 내부에 명시되어 있는 Handler의 handleMessage를 수행하여 처리한다.
Message를 Looper의 MessageQueue에 넣거나, Looper가 MessageQueue에서 특정 메세지를 꺼내어 전달하면 이를 처리하는 기능을 수행한다.
1. 다른 스레드에서 특정 스레드 Handler의 sendMessage()를 활용하여 Looper의 MessageQueue에 메세지를 전달함
2. 해당 스레드의 Looper는 MessageQueue에서 loop()를 통해, 메세지를 하나씩 Handler에 저달함
3. handler에서 handleMessage()를 통해 메세지를 처리함
여기서 looper를 메인 스레드의 Looper로 등록한다면 메인스레드에서 다른 스레드의 결과를 활용한 UI 작업이 가능해진다.
해당 코드는 kotlin 을 기준으로 작성합니다.
먼저 handler를 생성한다. Handler는 looper 와 MessageQueue가 있어야 하는 상당히 의존적인 객체이므로, Looper도 같이 설정한다.
var handler: Handler? = null
val thread = Thread {
Looper.prepare()
handler = Handler(Looper.myLooper())
Looper.loop()
}
thread.start()
Looper.prepare()를 통해 해당 스레드에 종속되는 Looper와 MessageQueue를 준비해주고, Handler를 생성해준다. 이 작업을 통해 handler와 looper가 서로 연결된다고 볼 수 있다. 그리고 마지막에 Looper.loop()를 호출함으로 Message 전달을 기다리는 동작을 시작한다.
안드로이드에서는 연결할 Looper를 명시하여 Handler를 생성하는 방법을 권장한다. 위의 방법은 현재 스레드와 연관있는 Looper 객체를 Handler에 명시해 주고 있다.
아래 코드에서는 메인 스레드의 Looper를 명시해 메인 스레드와 통신을 할 수 있도록 하겠다.
var handler: Handler? = null
val thraed = Thread {
handler = Handler(Looper.getMainLooper())
}
thread.start()
이렇게 구현하면 메인 스레드의 MessageQueue에 메세지가 쌓이게 되고, 이를 메인 스레드의 Looper가 하나씩 꺼내서 처리하게 된다.
참고한 내용
https://velog.io/@haero_kim/Android-Looper-Handler-%EA%B8%B0%EC%B4%88-%EA%B0%9C%EB%85%90