이 글은 깡썜의 안드로이드 프로그래밍 책을 보고 작성하였습니다.
UI 스레드(메일 스레드)와 개발자가 정의한 스레드 간의 핸들러에 대한 연동을 예시로 들면서 Looper의 역할과 개념을 설명하겠습니다.
대략의 과정은 위와 같고, Looper와 MessageQueue의 개념은 다음과 같습니다.
Looper : 스레드에 대한 Message loop를 실행하는데 사용되는 클래스입니다. 기본적으로 스레드에는 메시지 루프가 연결되어 있지 않습니다. 메시지를 만들려면 실행할 스레드에서 prepare() 함수를 호출한 후 루프가 중지될 때까지 Message를 처리합니다.
MessageQueue : Looper에서 전송할 메시지 목록을 포함하는 클래스입니다. 메시지는 MessageQueue에 직접 추가되지 않고 Looper와 연결된 Handler 객체를 통해 추가됩니다.
정리하자면 Looper는 MessageQueue에 담겨져 있는 Message를 handleMessage() 함수의 매개변수로 전달하는 역할을 합니다. UI 스레드(메인 스레드)에는 Looper와 MessageQueue가 기본적으로 존재하여 UI 스레드와 개발자가 정의한 스레드를 핸들러를 통해 사용할 때는 Looper에 대해 선언 할 필요가 없습니다. 하지만 위의 개념에도 나와있듯이 개발자가 정의한 스레드에는 Looper와 MessageQueue가 존재하지 않습니다. 개발자가 정의한 스레드간의 연동을 핸들러 구조로 개발하려면 직접 코드에서 Looper를 준비해주어야 합니다.
class LooperThreadExample : Thread() {
var mHandler: Handler? = null
override fun run() {
// 현재의 스레드를 Looper로 초기화
Looper.prepare()
mHandler = object : Handler(Looper.myLooper()!!) {
override fun handleMessage(msg: Message) {
// process incoming messages here
}
}
// MessageQueue를 작동
Looper.loop()
}
}
Thread를 상속받은 개발자 스레드를 하나 정의합니다. 이 스레드는 UI 스레드(메인 스레드)가 아니므로 Looper와 MessageQueue가 존재하지 않습니다. 우선 Looper를 사용하기 위하여 Looper.prepare() 함수를 사용하여 Looper를 초기화합니다. 그 후 Looper.loop() 함수를 이용하여 MessageQueue를 작동하면 Looper가 무한루프로 돌아가게 됩니다. 따라서 더 이상 필요 없을 때 루퍼의 quit() 함수를 사용하여 종료시켜주어야 합니다.
아래 코드는 두 개의 개발자 스레드에서 Handler를 통해 연동하는 간단한 코드입니다.
class MainActivity : AppCompatActivity() {
lateinit var looperThread: LooperThread
lateinit var sendMessageThread: SendMessageThread
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
looperThread = LooperThread()
looperThread.start()
sendMessageThread = SendMessageThread()
sendMessageThread.start()
}
// Looper를 생성하고 전달되는 Message를 처리하기 위한 스레드
class LooperThread : Thread() {
var looperHandler: Handler? = null
override fun run() {
// 현재의 스레드를 Looper로 초기화한다
Looper.prepare()
// Handler 객체 선언
looperHandler = object : Handler(Looper.myLooper()!!) {
override fun handleMessage(msg: Message) {
SystemClock.sleep(1000)
// arg1 프로퍼티를 이용하여 간단한 int형 데이터 받기
val data = msg.arg1
// what 프로퍼티를 이용하여 분기
when(msg.what) {
0 -> Log.d("Looper Test", "even data : $data")
1 -> Log.d("Looper Test", "odd data : $data")
}
}
}
// 이 스레드에서 MessageQueue 작동
Looper.loop()
}
}
// Handler에게 Message를 보내는 스레드
inner class SendMessageThread : Thread() {
override fun run() {
for(i in 1..10) {
SystemClock.sleep(1000)
// Message 객체 생성
val message = Message()
if(i % 2 == 0) message.what = 0
if(i % 2 == 1) message.what = 1
message.arg1 = i
// sendMessage() 함수를 통해 MessageQueue에 메시지를 전달한다
looperThread.looperHandler?.sendMessage(message)
}
}
}
override fun onDestroy() {
super.onDestroy()
// 루퍼 종료
looperThread.looperHandler?.looper?.quit()
}
}
Handler에게 작업을 의뢰할 때는 위와 같이 sendMessage() 말고 post() 함수도 있었습니다. 이 post() 함수에 의한 요청을 처리하는 Looper를 내장한 HandlerThread 라이브러리 클래스도 존재합니다. 즉, 개발자가 정의한 스레드에서 Looper와 MessageQueue를 사용하기 위해 작성한 코드가 HandlerThread 클래스에서는 필요가 없습니다.