본문 바로가기

Android

Android | Looper & Handler

Android

안녕하세요.

오늘은 Looper와 Handler에 대해 알아보겠습니다.


개요

Looper와 Handler는 안드로이드에서 스레드 간 통신을 관리하고,

메시지를 처리하는데 사용되는 클래스 입니다.

 

Main Thread와 Background Thread 간의 작업을

안전하고 효율적으로 관리할 수 있습니다.


Looper

Looper는 스레드의 메시지 루프를 관리하는 역할을 합니다.

메시지 큐가 메시지를 받을 수 있게 자신의 생명 주기 안에서 계속 돌아가게 해줍니다.

스레드 ↔ Looper는 1 : 1 매칭입니다.

메시지 루프
메시지 큐에 들어오는 메시지를 처리할 수 있도록
반복해서 메시지를 받아오는 구조
(메시지 : 하나의 작업 단위)

 

안드로이드의 메인 스레드는 자동으로 Looper를 생성하지만,

다른 스레드에서는 직접 Looper를 생성하고 관리해야 합니다.

 

주요 메서드

  • Looper.prepare() : 현재 스레드에 Looper를 생성
  • Looper.loop() : 메시지 큐에서 메시지를 꺼내서 반복적으로 처리하는 루프를 시작
  • Looper.myLooper() : 현재 스레드의 Looper 객체를 반환
  • Looper.getMainLooper() : 메인 스레드의 Looper를 반환

 

Looper 사용 예시

val thread = Thread {
	Looper.prepare()
	val hanlder = Handler(Looper.myLooper()!!)
	Looper.loop()
}
thread.start()

 

Looper 반환 예시

1) Main Looper 반환

val mainLooper = Looper.getMainLooper()
println(mainLooper)

 

2) 현재 스레드 Looper 반환

val thread = Thread {
	Looper.prepare()
	val looper = Looper.myLooper()
	println(looper)
	Looper.loop()
}
thread.start()

 

3) 루퍼가 없는 스레드에서 호출

Looper가 없는 경우 null을 반환합니다.

val thread = Thread {
	val looper = Looper.myLooper()
	println(looper)
}
thread.start()

Handler

특정 스레드의 메시지 큐에서 메시지를 보내거나 처리하는 역할을 합니다.

메시지 큐에 전달된 메시지나 작업을 받아 처리하는 기능을 통해 스레드 간 통신을 도웁니다.

 

주요 메서드

  • sendMessage(Message msg) : 메시지 큐에 메시지를 보냄
  • post(Runnable r) : 메시지 큐에 실행할 Runnable 객체를 추가
  • handleMessage(Message msg) : 큐에 있는 메시지가 도착했을 때 호출되며, 이를 처리하는 로직을 정의
val handler = object : Handler(Looper.getMainLooper()) {
	override fun handleMessage(msg: Message) {
		...
	}
}

handler.sendMessage(Message.obtain().apply { what = 1 })
handler.post {
	...
}

Handler Thread

안드로이드 Thread는 기본적으로 Looper를 가지고 있지 않습니다.

이를 개선하기 위해 나온 것이 Handler Thread 입니다.

  • Looper를 자동으로 보유한 클래스

버튼을 누르면 HandlerThread에서 메인 스레드로 메시지를 전달해보겠습니다.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MyApplicationTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }

    companion object {
        const val TAG = "tistory - kdr06006"
    }
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    // 상태 변수 정의
    var count by remember { mutableStateOf(0) }

    // Main Handler 정의
    val mainHandler = Handler(Looper.getMainLooper()) { msg ->
        when (msg.what) {
            1 -> {
                val result = msg.obj as String
                Log.i(TAG, "메인 스레드에서 받은 응답 : $result")
            }
        }
        true
    }

    fun startThread() {
        Log.i(TAG, "Button Clicked!")
        count++

        val workerThread = HandlerThread("WorkerThread").apply { start() }
        val workerHandler = Handler(workerThread.looper)

        workerHandler.post {
            Log.i(TAG, "workerThread에서 작업 시작")
            Thread.sleep(2000)
            val result = "작업 완료"

            val message = Message.obtain()
            message.what = 1
            message.obj = result
            mainHandler.sendMessage(message)
        }
    }

    Column(
        modifier = modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center
    ) {
        // 텍스트 표시
        Text(
            text = "Hello $count!",
            modifier = Modifier.padding(bottom = 16.dp)
        )

        // 버튼 추가
        Button(onClick = { startThread() }) {
            Text("make thread")
        }
    }
}

 

의도한대로 2초 뒤에 메인 스레드에서 정보를 받았습니다.

TID가 다른 것도 확인이 됩니다.

다른 스레드에서 작업한 것도 알 수 있습니다.

  • PID : 19139
  • Main TID : 19139
  • Worker TID : 20445


순차성

Handler에 여러 작업이 들어오면, FIFO로 일 처리를 합니다. 

=> 순차성 보장


이를 알아보기 위해 다음과 같은 예제를 만들었습니다.

  • HandlerThread를 생성해 Message를 전송하는 과정을 빠르게 5번 보내고,
  • 메인 핸들러에서는 각 Message를 3초간 대기 후에 처리하도록 했습니다.
val mainHandler = Handler(Looper.getMainLooper()) { msg ->
    Thread.sleep(3000)
    
    when (msg.what) {
        1 -> {
            val result = msg.obj as String
            Log.i(TAG, "메인 스레드에서 받은 응답 : $result")
        }
    }
    true
}

...

workerHandler.post {
    Log.i(TAG, "workerThread에서 작업 시작 $count")
    val result = "작업 완료 $count"
    Thread.sleep(2000)
    
    val message = Message.obtain()
    message.what = 1
    message.obj = result
    mainHandler.sendMessage(message)
}

 

3초 간의 텀을 두고, 먼저 들어온 데이터부터 처리하는 것을 알 수 있습니다.