Main Thread
안드로이드는 기본적으로 Text 변경 등에 대해서 UI를 업데이트 하기 위해서 Main Thread가 존재한다.
60fps를 기준으로 16ms마다 수행이 될 수 있어야 하고 그렇지 못할 경우 소위 버벅거리는 렉이 발생하게 된다.
만약 5초를 넘어갈 경우 ANR이라고 하는 Application이 응답하지 않는 현상으로 강제종료 메세지가 안내된다.
주의 사항은 크게 두가지가 있다.
UI작업은 오직 이 Main Thread에서만 가능하다.Main Thread외 다른 Thread에서 UI 작업에 접근하면 안된다
동시에 UI 작업을 처리하려고 할 때 갱신에 어려움이 생기기 때문이다.
MainThread는 기본적으로 Looper가 존재한다,
Looper
Looper는 MessageQueue를 감시하며 들어온 작업들을 꺼내어 처리할 수 있도록 Handler에게 전달한다.
Looper는 Thread와 1:1 관계로 동작하면서 Handler를 통해서 MessageQueue를 감시한다
모든 Thread가 Looper를 가지는 것은 아니며, 필요시에 Looper를 만들 수 있지만, 하나의 Thread에서 여러 Looper가 존재하는 것이 아니라 오직 하나만 존재하는 1:1 관계임에 주의하자
Message Queue
Message Queue는 Queue로 구현된 Message객체들을 담아두는 작업 대기열이다.
Handler에서 post, sendMessage를 통해 보낸 Message 객체들을 쌓아둔다.
MessageQueue는 Looper 내부에 존재하며 Looper가 prepare라는 메서드를 통해 생성될 때 만들어진다.
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객체를 만들면run과start를 실행시킬 수 있는데, 차이점은 실행되는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 내부에 Handler와 Looper를 정의하여 만들고 외부에서 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 Thread의 Handler로 보내어 처리할 수가 있다
Main Thread로 작업 전달하기
// file: "PostTaskToMain.kt"
// MainThread는 mainLooper를 통해 Looper를 가져올 수 있고, 이를 넣은 Handler를 만들어서 결과적으로 Main의 Looper에서 꺼내어서 Main Thread에서 처리되도록 한다.
Handler(mainLooper).post{
Log.d("Main Thread로 작업 전달", "${Thread.currentThread()}로 작업 전달")
}
Main Thread의 Looper를 mainLooper로 가져올 수 있기에 이를 새로 만들 Handler의 인자로 넣어서 초기화 한 Handler 객체에 post, sendMessage를 통해 보내주면 Main Thread에서 작업들을 받아와서 처리하게 된다