GDSC HUFS 3기/Android with Kotlin Team 1

[1팀] Thread, Handler, Looper

jaesungLeee 2021. 11. 29. 15:36

이 글은 이것이 안드로이드다 with 코틀린(개정판)를 참고하여 작성하였습니다.

작성자 : 이재성

개발환경은 Windows, Android Studio입니다.

 

1. Main Thread (UI Thread)

안드로이드 구성요소 (Activity, Service, Broadcast Receiver, Content Provider)가 시작되고 애플리케이션에 실행 중인 다른 구성요소가 없으면 Android System은 단일 Thread로 애플리케이션의 Linux 기반 Process를 시작한다. 기본적으로 같은 애플리케이션의 모든 구성요소는 같은 Process와 Thread에서 실행된다. 즉, 안드로이드는 기본적으로 UI를 처리하는 Main Thread를 가진 Single Thread System으로 동작한다.

 

Single Thread System은 아래와 같이 두 가지 원칙이 있다.

1. MainThread를 Block(중지)하지 않는다.

2. 안드로이드 UI ToolKit (TextView, ImageView, LinearLayout 등..) 들은 모두 MainThread에서만 접근이 가능하다.

 

위와 같은 이유로 긴 시간이 걸리는 작업을 MainThread에서 동작하게 하면 UI 처리가 미뤄지게 되며, 이것이 지속되면 ANR(Application Not Responding = 응답 없음) 오류가 발생된다. ANR은 흔히 웹페이지에 응답 없음이 뜨고 멈춰버리는 것과 같다고 볼 수 있다.

 

 

위에 설명한 것 처럼 긴 시간이 걸리는 작업 (네트워크 작업, File I/O, 이미지, 데이터 관련 기능) 은 짧은 시간 안에 끝난다고 하더라도 처리 시간을 미리 예측하거나 계산할 수 없다. 이 경우 Background Thread에서 처리하는 것을 권장하고 있다. 

2. Background Thread 생성 방법

Thread 객체 (람다식으로 Runnable 익명 객체 구현)

Thread {
    var i = 0
    while (i < 10) {
        i += 1
        Log.e("Thread : $i")
    }
}.start()

 

 

Thread 클래스를 사용하기 위해서는 Runnable 인터페이스를 상속한 클래스의 인스턴스를 생성자로 넘겨주어야 한다. 하지만 Kotlin은 Lambda를 사용하여 위처럼 간편하게 구현할 수 있다.

 

Kotlin thread( ) & runOnUiThread - Background Thread는 View에 접근할 수 없다.

thread(start = true) {
    var i = 0
    while (i < 10) {
        i += 1
		
        runOnUiThread {
            binding.textView.text = "$i"
        }
        
        Thread.sleep(1000)
}

Kotlin에서는 Thread 생성을 위해 thread( ) method를 제공한다. Background Thread는 View에 접근할 수 없기 때문에 runOnUiThread를 이용하여 View에 접근할 수 있다.

 

3. Handler & Looper

MainThread에서만 UI처리가 가능하다는 것은 우리가 이제 알고있는 지식이다. Android System에서는 제공하는 MainThread와 Background Thread 간의 통신을 지원한다. 

 

Handler

Handler는 Message Queue로부터 받은 작업 (Message, Runnable 객체)을 sendMessage( ), post( )를 이용하여 다른 Thread에 보내거나 Looper로부터 받은 작업을 처리한다. (handleMessage( ))

 

var mHandler = Handler(Handler.Callback{ msg-> 
    // do something
    false 
}) 
mHandler.sendMessage(msg) 

var mRunnable = Runnable{ 
    // do something
} 

var mHandler2 = Hander() 
mHandler2.post(mRunnable)

 

Message의 경우 Handler에서 MessageQueue에 전달한 후 전달한 Handler의 Callback (Java에서 handleMessage( ))를 통해 처리한다. Runnable의 경우도 Handler를 통해 MessageQueue에 전달한 후 Runnable의 run( )에서 동작한다. 

 

Looper

Thread 내에서 주기적으로 MessageQueue를 탐색하며 작업이 생기면 차례대로 (선입선출 : FIFO) Handler에 전달하는 역할을 한다. Thread 당 하나만 존재하며 MainThread를 제외하고 새로 생성된 Thread는 Looper를 가지지 않고 run만 존재해, run의 동작이 끝날 경우 Thread는 종료된다. 따라서, 새로 생성한 Thread에서는 Looper.prepare( )를 통해 Looper를 생성하고 Looper.loop( )로 무한히 돌면서 작업을 Handler에 전달한다. 종료는 quit( ) 또는 quitSafely( )로 종료할 수 있다.