UI 스레드
안드로이드에서 UI의 조작은 메인 스레드에서만 가능하게 되어있다. 왜냐면 여러 스레드에서 동시에 UI를 변경하려고 하면, 결과를 예측할 수 없기 때문이다. 그래서 메인 스레드를 UI 스레드라고 부르기도 한다.
스레드 통신
멀티 스레드 환경에서 작업 스레드의 결과를 UI에 반영하려면 어떻게 해야 할까? 메인 스레드로 작업을 전달하는 것을 생각해볼 수 있다. 즉, 스레드 간 통신이 필요한데, 이것을 Looper와 Handler를 통해서 해결할 수 있다.
Looper
Looper는 하나의 스레드에 연결되어, 스레드의 MessageQueue에 들어오는 Message를 관리하는 객체를 말한다.
Message는 또 뭘까? Message는 스레드가 주고 받는 데이터 조각으로, 작업 내용과 필요한 데이터를 담을 수 있다.
Looper는 이름처럼 반복문을 돌며 스레드의 MessageQueue에 Message가 들어오는지 확인한다. Message가 들어오면, Message의 처리를 누군가에게 맡기는데, 그 누군가를 Handler라고 한다. Handler는 말 그대로 Message를 다루는 객체이다.
Handler
Handler는 스레드의 메시지큐에 Message를 적재하고, Looper의 호출로 Message를 처리하는 역할을 한다. Handler는 오직 하나의 스레드와 메시지큐에 연결된다.
스레드 통신 과정 (메인 스레드←작업 스레드)
- 작업 스레드에서 결과물을 담은 Message를 생성한다.
- 메인 스레드에 연결된 Handler의 sendMeesage(Message)로 메인 스레드에 메시지를 전송한다
- Handler가 메인 스레드의 MessageQueue에 Message를 적재한다.
- Looper가 Message를 확인한다. Handler.handleMessage(Message)를 호출하여 Handler에게 메시지 처리를 맡긴다.
Message와 Runnable
스레드 간 통신은 Message로 이뤄진다고 했는데, Runnable을 통해서도 가능하다. 즉, Message 전송, Runnable 전송 2가지 방식이 있다. Message는 Handler.sendMessage(Message), Runnable은 Handler.post(Runnable)로 전송한다.
Thread + Looper 생성 방법
1. 기본 Thread 생성, Handler에 Looper 암시적 연결
var handler: Handler? = null
val thread1 = Thread { // Runnable 익명 객체 구현
Looper.prepare()
handler = Handler() // 생성한 스레드의 Looper와 MessageQueue에 암시적으로 연결된다
Looper.loop()
}
thread.start()
- 스레드를 생성하고, Looper.prepare()로 Looper와 MessageQueue를 생성한다.
- handler = Handler()를 실행하면 Handler를 생성한 스레드의 looper와 MessageQueue에 Handler가 연결된다.
- Looper.loop()를 실행하면 looper가 MessageQueue를 돌기 시작한다.
이렇게 하면 외부 스레드에서 handler를 통해 thread1에게 메시지를 보낼 수 있다.
하지만 위 Handler->Looper 연결법은 암시적인 방법이라 deprecated 되었다.
2. HandlerThread 생성, Handler에 Looper 명시적 연결
val thread2 = HandlerThread("Handler Thread2")
var handler = Handler(thread2.looper)
- HandlerThread는 Looper를 기본적으로 탑재하고 있는 스레드이다.
- Thread는 Looper를 갖지 않을 수도 있다. 통신이 필요 없는 스레드도 있기 때문이다. 1번 예제의 Looper.prepare()가 Looper를 생성하는 과정이다.
- 외부에서 Handler를 만들 때 명시적으로 thread2의 Looper를 전달하여 명시적으로 Handler를 Thread2에 연결하고 있다.
3. 작업 스레드에서 메인 스레드용 Handler 생성
val thread3 = Thread {
val handler = Handler(Looper.getMainLooper())
handler.post {
// Runnable
}
}
thread3.start()
- 작업 스레드에서 메인 스레드에 작업을 전달하기 위한 Handler를 생성했다.
- Looper.getMainLooper(): 메인 스레드의 Looper를 가져온다.
- handler.post(Runnable): 메인 스레드에 실행 블록을 전송한다.
참조
'TIL > 안드로이드' 카테고리의 다른 글
[Android] Context란? (feat. 메모리 누수) (0) | 2023.04.12 |
---|---|
[Android] Serializable vs Parcelable (0) | 2023.03.19 |
[Android] 비트맵 크기 최적화 로딩하기 (0) | 2023.03.18 |
[TIL] 안드로이드 - Java와 Kotlin이 호환되는 이유 (0) | 2022.03.12 |