안드로이드에서는 싱글 스레드 모델로 UI 업데이트가 이루어집니다. 이 모델에 따라 UI는 메인 스레드에서만 업데이트할 수 있으며, 다른 스레드에서 UI를 수정하려고 하면 버그가 발생할 수 있습니다.
이런 싱글 스레드 모델은 UI의 무결성을 보장하기 위한 것으로, 메인 스레드만이 UI 작업을 처리할 수 있도록 함으로써 스레드 간의 충돌이나 데이터 일관성 문제를 방지합니다. 따라서, 메인 스레드를 무거운 작업으로 블로킹하는 경우 ANR(Application Not Responding) 오류가 발생해 앱이 강제 종료될 수 있으므로 주의해야 합니다. 네트워크 작업과 같이 시간이 오래 걸리는 작업은 별도의 백그라운드 스레드에서 처리해야 합니다.
하지만, 백그라운드 스레드에서 UI를 업데이트할 필요가 생길 때가 있는데, 이를 위해 안드로이드에서는 Looper와 Handler를 사용해 스레드 간 통신을 할 수 있도록 지원합니다.
Looper와 Handler의 원리
- 하나의 스레드는 오직 하나의 Looper를 가질 수 있으며, Looper는 해당 스레드에 연결되어 메시지 큐를 관리합니다. 안드로이드에서는 메인 스레드가 시작될 때 기본적으로 Looper가 실행되어, 메인 스레드의 이벤트 루프를 관리합니다.
- Looper 내부의 메시지 큐에는 해당 스레드가 처리해야 할 작업이 메시지 형태로 쌓이고, 이를 순차적으로 처리하는 역할을 합니다. Handler는 특정 메시지를 메시지 큐에 넣거나, 큐에서 꺼내어 처리하는 역할을 담당합니다.
class MainActivity : AppCompatActivity() {
private lateinit var textView: TextView
private var count = 0
// 메인 스레드 핸들러
private val uiHandler = Handler(Looper.getMainLooper()) { message ->
textView.text = "Count: ${message.what}"
true
}
// 백그라운드 스레드와 Looper를 초기화하는 스레드
private lateinit var workerThread: Thread
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.textView)
// 백그라운드 스레드에서 Looper와 Handler 설정
workerThread = Thread {
Looper.prepare() // Looper를 설정
val workerHandler = Handler(Looper.myLooper()!!) {
// 메시지를 받을 때마다 count를 증가시킴
count++
// 메인 스레드로 메시지 전달
uiHandler.sendEmptyMessage(count)
true
}
// 1초마다 반복하여 workerHandler에 메시지 전송
while (!Thread.currentThread().isInterrupted) {
workerHandler.sendEmptyMessage(0)
Thread.sleep(1000)
}
Looper.loop() // Looper 시작
}
workerThread.start()
}
override fun onDestroy() {
super.onDestroy()
workerThread.interrupt() // 스레드를 중지시킴
}
}
코루틴을 사용하여 UI 업데이트 간소화
최근에는 코루틴이 도입되면서 Handler의 사용 빈도가 줄어들었습니다. 코루틴은 객체이므로 어느 스레드에서든 시작할 수 있으며, Dispatchers.Main을 통해 UI 조작이 가능합니다. 예를 들어, Dispatchers.Main를 지정하면 해당 스코프 내에서 UI를 안전하게 조작할 수 있습니다.
class MainActivity : AppCompatActivity() {
private lateinit var textView: TextView
private var count = 0
// 코루틴 스코프 생성 (Main 스레드에서 동작)
private val coroutineScope = CoroutineScope(Dispatchers.Main + Job())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.textView)
// 반복 작업을 실행하는 코루틴 시작
coroutineScope.launch {
while (isActive) {
count++
textView.text = "Count: $count"
delay(1000) // 1초 대기
}
}
}
override fun onDestroy() {
super.onDestroy()
coroutineScope.cancel() // 액티비티 종료 시 코루틴 취소
}
}
코루틴과 Dispatchers.Main의 내부 동작
Dispatchers.Main은 안드로이드 UI 스레드에서 코루틴을 실행할 수 있도록 설계된 디스패처입니다. 안드로이드 프레임워크는 Dispatchers.Main의 UI 작업 요청을 핸들러와 메시지 큐를 통해 메인 스레드에 전달하며, 이를 통해 코루틴이 안전하게 UI를 업데이트할 수 있습니다.
- UI 스레드는 메인 스레드의 메시지 큐에서 이벤트를 처리하기 때문에, 코루틴이 Dispatchers.Main에서 실행될 때에도 메시지 큐에 작업이 게시되어 UI 작업이 차례로 실행됩니다.
따라서, 코루틴이 Dispatchers.Main에서 UI 작업을 요청하면, 안드로이드의 이벤트 루프 시스템이 이를 메시지 큐와 핸들러로 관리하여 UI가 안전하게 업데이트되도록 보장합니다.
참고
https://velog.io/@haero_kim/Android-Looper-Handler-%EA%B8%B0%EC%B4%88-%EA%B0%9C%EB%85%90
'안드로이드 > 안드로이드' 카테고리의 다른 글
[Android/Kotlin] provides 메서드 (0) | 2024.11.14 |
---|---|
안드로이드 MVVM ViewModel과 AAC ViewModel (4) | 2024.11.09 |
안드로이드 MVVM 카카오 로그인 이슈 (0) | 2024.11.01 |
CNA(Chirag Note App) 프로젝트하면서 생긴 이슈 및 해결 ,기술 정리 (0) | 2024.10.02 |
CNA(Chirag Note App) 클론코딩 & 리펙토링 회고 (1) | 2024.10.01 |