UI 스레드

안드로이드에서 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는 오직 하나의 스레드와 메시지큐에 연결된다.

스레드 통신 과정

스레드 통신 과정 (메인 스레드←작업 스레드)

  1. 작업 스레드에서 결과물을 담은 Message를 생성한다.
  2. 메인 스레드에 연결된 Handler의 sendMeesage(Message)로 메인 스레드에 메시지를 전송한다
  3. Handler가 메인 스레드의 MessageQueue에 Message를 적재한다.
  4. 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): 메인 스레드에 실행 블록을 전송한다.

참조

+ Recent posts