17. Handler Looper

Main Thread

안드로이드는 기본적으로 Text 변경 등에 대해서 UI업데이트 하기 위해서 Main Thread가 존재한다.

60fps를 기준으로 16ms마다 수행이 될 수 있어야 하고 그렇지 못할 경우 소위 버벅거리는 렉이 발생하게 된다.

만약 5초를 넘어갈 경우 ANR이라고 하는 Application이 응답하지 않는 현상으로 강제종료 메세지가 안내된다.

주의 사항은 크게 두가지가 있다.

  1. UI 작업은 오직 이 Main Thread에서만 가능하다.
  2. Main Thread외 다른 Thread에서 UI 작업에 접근하면 안된다

동시에 UI 작업을 처리하려고 할 때 갱신에 어려움이 생기기 때문이다.

MainThread는 기본적으로 Looper가 존재한다,

Looper

LooperMessageQueue를 감시하며 들어온 작업들을 꺼내어 처리할 수 있도록 Handler에게 전달한다.

LooperThread1:1 관계로 동작하면서 Handler를 통해서 MessageQueue를 감시한다

모든 ThreadLooper를 가지는 것은 아니며, 필요시에 Looper를 만들 수 있지만, 하나의 Thread에서 여러 Looper가 존재하는 것이 아니라 오직 하나만 존재하는 1:1 관계임에 주의하자

Message Queue

Message QueueQueue로 구현된 Message객체들을 담아두는 작업 대기열이다.

Handler에서 post, sendMessage를 통해 보낸 Message 객체들을 쌓아둔다.

MessageQueueLooper 내부에 존재하며 Looperprepare라는 메서드를 통해 생성될 때 만들어진다.

Handler

Handler는 다른 Thread에서 보낸 작업을 받아 MessageQueue에 전달하고, Looper가 꺼내었을 때 최종적으로 처리한다.

Thread

Thread는 기본적으로 run 메서드를 override하여 작업할 것들을 작성하고, 이것을 start 메서드로 실행시킴으로써 start를 호출하는 Thread와 별도의 Thread에서 run에 해당하는 작업들이 실행되도록 한다.

// file: "Thread.kt"
class LogThread : Thread() {
    override fun run() {
        super.run()
        Log.d("run", "run: ${currentThread().name}")
    }
}

~~실행부
val logThread = LogThread()
logThread.start()

이런 식으로 start를 호출할 때 run에 작성한 내용들이 실행되게 된다

기본적으로 Thread 객체의 start가 끝나면 소멸되지만, Looper가 있는 경우 보통 loop를 호출하고, 이를 통해 MessageQueue를 계속 감시하기 때문에 끝나지 않는다

  • run vs start

    Thread 객체를 만들면 runstart를 실행시킬 수 있는데, 차이점은 실행되는 Thread이다

    둘 다 run 내부에 작성된 코드가 실행되는 것은 동일하다

    하지만 run은 호출 시점의 Thread에서 그대로 실행되지만 start 메서드는 새로운 Thread에서 실행되게 된다

Looper 만들기

기본적으로 HandlerThread를 통해 이러한 Looper, Handler들이 구현되어 있는 Thread를 생성할 수 있지만 구조를 이해하기 위해 간단한 의사 코드를 가져왔다

// file: "MakeLooperThread.kt"
class LooperThread : Thread() {
    lateinit var mHandler: Handler

    override fun run() {
        Looper.prepare()
        mHandler = object : Handler(Looper.myLooper()!!) {
            override fun handleMessage(msg: Message) {
                // 메세지 처리
                super.handleMessage(msg)
            }
        }
        Looper.loop()
    }
}

이런 식으로 Thread 내부에 HandlerLooper를 정의하여 만들고 외부에서 handler를 통해 Thread에 보낼 수 있다

// file: "PostTask.kt"
val looperThread = LooperThread()
looperThread.mHandler.sendMessage(Message())
looperThread.mHandler.post{
    Log.d("post", "post: ${Thread.currentThread().name}")
}

이처럼 다른 외부 Thread에서 UI 관련 작업이 필요해졌을 때 관련 작업을 Main ThreadHandler로 보내어 처리할 수가 있다

Main Thread로 작업 전달하기

// file: "PostTaskToMain.kt"
// MainThread는 mainLooper를 통해 Looper를 가져올 수 있고, 이를 넣은 Handler를 만들어서 결과적으로 Main의 Looper에서 꺼내어서 Main Thread에서 처리되도록 한다.
Handler(mainLooper).post{
    Log.d("Main Thread로 작업 전달", "${Thread.currentThread()}로 작업 전달")
}

Main ThreadLoopermainLooper로 가져올 수 있기에 이를 새로 만들 Handler의 인자로 넣어서 초기화 한 Handler 객체에 post, sendMessage를 통해 보내주면 Main Thread에서 작업들을 받아와서 처리하게 된다


© 2025. Na2te All rights reserved.