Thread란 동시에 여러 작업을 수행하기 위해 사용되는 개념이다
Android는 앱 구성하는 여러 컴포넌트들이 각자의 프로세스를 생성할 수 있고, 어느 프로세스에서나 메인 스레드 외의 추가 스레드를 생성할 수 있다.
우리가 일상생활에서 빨래를 돌려 놓고 외출을 하거나 배달을 시켜놓고 게임을 하듯이, 프로세스 내에서도 여러 작업을 동시에 가능케 하는게 Thread이다.
Main Thread는 프로세스가 실행 후 기본 실행을 담당하는 최초의 스레드라고 할 수 있다.
또한 Main thread는 UI작업을 담당하는 스레드이다. 오직 Main thread에서만 UI와 상호작용할 수 있다.
UI작업이 Main thread에서만 이루어지는 이유는 우리가 앱과 상호작용할 때는 순서대로 진행되어야 하기 때문이다. 예를 들어 우리가 이미지 생성 버튼을 클릭하고 이미지를 그리고 다시 완료 버튼을 클릭했다고 치자. 이 작업들이 여러 다른 스레드로 작업을 한다면 순서가 보장되지 않고 실행되어지기 때문에 우리가 원하는 이미지를 그릴 수 없을 것 이다. 따라서 오직 Main Thread에서만 UI와 상호작용한다.
Main Thread에서 서버 통신이나 파일I/O, 데이터베이스 쿼리를 실행한다면 이 작업들이 끝날 때까지 전체 UI가 차단이 된다. 따라서 다른 스레드에서 이 작업들을 처리하는 데 이를 Worker Thread라고 한다. Main Thread에서 UI작업을 처리하고 Worker Thread에선 다른 일을 하도록 하여 UI와 차단을 방지한다.
class A: Thread(){
override fun run() {
super.run()
for (i in 1..1000){
Log.d("test", "first : $i")
}
}
}
val a = A()
a.start()
Thread 클래스를 상속하여 run함수 안에 실행할 코드를 적는다.
Handler는 스레드간 통신을 가능케 하는 클래스이다.
주로 메인 스레드와 백그라운드 스레드 사이에서 작업을 조정하고 특정 작업을 예약하거나 지연시키는 데 사용된다.
만약 Worker Thread에서 UI작업을 해야한다면??
백그라운드 스레드에서는 UI를 직접 조작할 수 없으므로 Handler를 사용하여 Main Thread에 작업을 전달하여 UI업데이트를 수행한다. 또한 Handler를 사용하여 작업을 예약하거나 지연시킬 수 있으므로 앱의 응답성을 향상시킬 수 있다.
val handler = Handler(Looper.getMainLooper())
Thread{
for (image in imageList){
handler.post{
binding.iv.setImageResource(image)
}
Thread.sleep(2000)
}
}.start()
worker thread에서 UI를 업데이트 시킬 때 handler를 사용해 메인 스레드로 전달해 UI 업데이트를 시킬 수 있다
Thread{
for(i in 30 downTo 0){
runOnUiThread{
binding.timer.text = "$i"
}
Thread.sleep(1000)
}
}.start()
handler를 사용하지 않고 runOnUiThread를 사용하여 UI를 업데이트할 수도 있다.
Handler를 통해 다른 스레드로 전달되는 작업 단위이다. Message 객체에는 핸들러가 처리해야 할 작업에 대한 정보가 포함되어 있다. 이 정보에는 Runnable 객체나 작업을 식별하는 정수 상수 등이 포함될 수 있다.
스레드의 메시지 큐를 루프로 실행하고, 큐에 있는 메시지들을 처리하는 역할을 한다.
기본적으로 안드로이드 앱은 스레드마다 한 개의 Looper가 존재하여 Looper는 스레드의 메시지 큐를 계속 확인하고 이를 처리한다.
코루틴이란 비동기 프로그래밍을 위한 방식으로 일반적인 스레드보다 가벼우면서도 비동기 작업을 효율적으로 처리할 수 있는 기능이다.
안드로이드 앱에서 비동기 작업은 필수적이다. 비동기 프로그래밍이란 요청과 그 결과가 동시에 일어나지 않을 거라는 약속이다. 서버에게 데이터를 요청한 후 요청에 따른 응답을 계속 기다리지 않고 다른 액션을 취하거나 서버에게 다른 요청사항을 보내는 것도 가능하다. 네트워크 요청, 파일 입출력, 데이터베이스 액세스등 시간이 오래 걸리는 작업을 처리할 때 코루틴을 사용하는게 효율적이다.
lifecycleScope.launch(Dispatchers.IO) {
delay(3000)
Log.d(TAG,"코루틴1 : ${Thread.currentThread().name}")
withContext(Dispatchers.Main){
Log.d(TAG,"코루틴2 : ${Thread.currentThread().name}")
}
}
Log.d(TAG,"onCreate : ${Thread.currentThread().name}")
//logcat
onCreate : main
코루틴 1 : DefaultDispatcher-worker-3
코루틴 2 : main
scope : 코루틴 블락을 생성
Dispatcher : 어떤 thread에서 실행시킬지 정함(Default, Main, IO)
withContext : 실행환경을 바꾸고 싶을 때 (위의 예로는 IO에서 Main으로 실행환경을 바꿈)