[안드로이드] 스레드와 핸들러

hee09·2021년 10월 26일
0

안드로이드

목록 보기
12/20
post-custom-banner

이 글은 깡쌤의 안드로이드 프로그래밍 책을 보고 작성하였습니다.

스레드

ANR(Application Not Responding)을 방지하기 위해서 스레드를 작성한다고 하였습니다. 이 글에서 그 스레드 작성법을 알아보겠습니다.

안드로이드에서 스레드는 자바의 Thread API를 그대로 이용합니다.
스레드를 만드는 방법은 다음과 같습니다.

  • Thread 클래스를 상속받아 작성하는 방법
  • Runnable 인터페이스를 구현하여 작성하는 방법
// Thread 클래스를 상속받아 작성
class ThreadExample1: Thread() {
    // run 함수에 업무 처리를 위한 코드를 작성
    override fun run() {
        for(i in 1..10)
            Log.d("ThreadNumber", i.toString())
    }
}

// 객체 생성
val threadExample1 = ThreadExample1()
// start를 통해 스레드를 시작하면, run 함수가 자동으로 실행
threadExample1.start()

Thread 클래스를 상속받아 작성하는 방법입니다.

Thread를 상속받은 클래스에 run() 함수를 오버라이드하면 되는데 그 안에 업무 처리를 위한 코드를 작성하면 됩니다. 그 후 start() 함수를 통해 Thread를 시작하면 run() 함수가 자동으로 실행되며 run() 함수의 수행이 끝나면 스레드는 자동으로 종료됩니다.


// Runnable 인터페이스를 구현하여 작성
class ThreadExample2: Runnable {
    // run 함수에 업무 처리를 위한 코드를 작성
    override fun run() {
        for(i in 1..10)
            Log.d("ThreadNumber", i.toString())
    }
}

// 객체 생성
val threadExample2 = ThreadExample2()
// Thread 클래스의 생성자로 Runnable 인터페이스를 구현한 클래스의 객체를 전달
val thread = Thread(threadExample2)
// start를 통해 스레드를 시작하면, Runnable의 run() 함수가 자동으로 실행
thread.start()

Runnable 인터페이스를 구현하여 작성하는 방법입니다.

Runnable 인터페이스의 run() 함수를 작성하고 스레드 클래스의 생성자 매개변수로 Runnable을 구현한 객체를 주면 스레드가 시작할 때 Runnable의 run() 함수를 자동으로 실행합니다.


스레드를 위한 함수로는 sleep(), wait(), notify() 함수 등 다양합니다.

  • sleep() : 실행 중인 스레드를 Sleep Pool로 보내어 지정된 시간동안 대기 상태가 되게 하는 방법
  • wait() : 스레드가 여러 개 존재하면서 서로 간의 작업을 차례로 또는 일정한 순서에 따라 진행하고 싶을 때 사용하는 방법으로, 스레드를 대기 상태로 만드는 함수입니다.
  • notify() : wait() 함수에 의해 대기 상태가 된 스레드는 스스로 수행 상태가 될 수 없으며 다른 스레드에서 notify() 함수를 이용해서 깨워줘야 다시 동작합니다.

핸들러

1. 스레드 - 핸들러 구조

안드로이드 시스템에서 액티비티를 수행하면서 발생시킨 UI 스레드(메인스레드)가 아닌, 개발자가 정의한 스레드에서 런타임 때 액티비티의 화면 출력 요소인 뷰에 접근하면 에러가 발생합니다.

// Thread 클래스를 상속받아 작성
class ThreadExample1: Thread() {
    // run 함수에 업무 처리를 위한 코드를 작성
    override fun run() {
        var sum: Int = 0
        for(i in 1..10){
            sum += i
            mainTextView.text = sum.toString()
        }
    }
}

만약 위와 같이 개발자가 정의한 스레드가 수행되면서 화면 출력 요소인 뷰에 접근하는 순간 에러가 발생하며 앱이 중지됩니다. 이는 동기화의 문제인데, 여러 스레드에서 동시에 화면 객체를 접근하면 문제가 발생할 수 있어서 구조적으로 스레드 간의 역할을 분리해서 원척적으로 동기화 문제 부분을 해결하고자 하는 방법입니다.

개발자가 정의한 스레드에서 뷰를 업데이트 하기 위해서는 Handler 클래스를 이용해야 합니다.
개발자 스레드에서는 Handler 클래스를 이용해 UI 스레드에 뷰와 관련된 작업을 의뢰하는 방식으로 개발해야 합니다.


1.2 핸들러에 작업 의뢰

개발자가 정의한 스레드가 수행되다가 UI 스레드에게 뷰에 대한 작업을 의뢰하는 방법 다음과 같습니다.

  • post() 함수를 이용한 방법
  • sendMessage() 함수를 이용하는 방법

우선 post() 함수를 이용하는 방법은 다음과 같습니다.

// Handler 클래스의 객체를 생성
val handler = Handler()

// UI 스레드에 의해 호출되는 클래스
class UIUpdate: Runnable {
    override fun run() {
        mainTextView.text = sum.toString()
    }
}

// Thread 클래스를 상속받아 작성
class ThreadExample1: Thread() {
    // run 함수에 업무 처리를 위한 코드를 작성
    override fun run() {
        for(i in 1..10){
            sum += i
            // post() 함수를 호출해 UI 스레드에게 작업을 의뢰
            handler.post(UIUpdate())
            try {
                sleep(1000)
            } catch(e: InterruptedException) {

            }
        }
    }
}

뷰에 접근해야 하는 상황이 발생한 순간 handler.post() 함수를 이용해서 UI 스레드에게 의뢰하고, 이 의뢰가 발생한 순간 UI 스레드에서는 post() 함수의 매개변수로 지정한 Runnable을 구현한 객체의 run 함수를 자동으로 호출하는 구조입니다.


다음은 sendMessage() 함수를 이용하는 방법입니다.

val handler = object: Handler() {
    // sendMessage가 실행되면 UI 스레드가 handlerMessage 함수 호출
    override fun handleMessage(msg: Message) {
        // 매개변수로 넘어온 Message 객체를 사용
        // what 프로퍼티를 이용하여 분기
        when(msg.what) {
            1 -> {
                mainTextView.text = msg.arg1.toString()
            }
            2 -> {
                mainTextView.text = msg.obj as String
            }
        }
    }
}

// Thread 클래스를 상속받아 작성
class ThreadExample1: Thread() {
    // run 함수에 업무 처리를 위한 코드를 작성
    override fun run() {
        try {
            // 10초동안 count 값을 보내며 10초후 종료되는 스레드 
            var count: Int = 10
            while(loopFlag) {
                count--
                // Handler에게 message를 보내기 위한 Message 객체 생성
                var message = Message()
                // what 설정으로 메시지 분기
                message.what = 1
                // 간단한 int값은 arg 프로퍼티로 설정 가능
                message.arg1 = count
                // handerl.sendMessage()를 통해 message 전달
                handler.sendMessage(message)
                if(count == 0) {
                    // Message 객체 생성
                   message = Message()
                    // what 프로퍼티를 이용해 message 분기
                   message.what = 2
                    // obj 프로퍼티를 사용해 여러 객체 설정 가능
                   message.obj = "Finish!!"
                    // sendMessage를 통해 message 전달
                   handler.sendMessage(message)
                   loopFlag = false
                }
            }
        } catch(e: Exception) {
            
        }

    }
}

개발자가 정의한 스레드 안에서 Message 객체를 생성하고 그 객체에 what, arg1, obj 프로퍼티를 이용하여 값을 셋팅합니다. 그 후 handler.sendMessage()에서 매개변수로 보내려는 Message 객체를 넣고 실행하면 UI 스레드(메인 스레드)에서 handler의 handleMessage 함수를 자동으로 실행합니다. 이 핸들러 클래스 안에서 UI를 업데이트하는 코드를 작성하면 됩니다.


handler에게 Message를 보내는 함수는 sendMessage()외에 여러 메서드가 존재합니다.

  • sendMessage(Message msg) : UI 스레드에 의뢰
  • sendMessageAtFrontOfQueue(Message msg) : 뷰 작업에 대한 의뢰가 반복해서 발생한 경우 UI 스레드에서 차례로 처리하는데, 이번 의뢰를 가장 먼저 처리하라는 요청
  • sendMessageAtTime(Message msg, long uptimeMillis) : 의뢰를 지정된 시간에 수행해 달라는 요청
  • sendMessageDelayed(Message msg, long delayMillis) : 의뢰를 바로 처리하지 말고 지정 시간 후에 수행해 달라는 요청
  • sendEmptyMessage(int what) : 데이터 전달 없이 의뢰하는 경우

sendMessage() 함수를 사용할 때 Message 객체가 매개변수로 사용된는데, Message 클래스는 뷰 작업을 의뢰할 때 UI 스레드에 넘기는 데이터를 담는 객체입니다. Message 객체의 what과 obj, arg1, arg2 프로퍼티에 필요한 만큼 데이터를 담아 UI 스레드에 전달하면 됩니다.

  • what : int 형 변수로 구분자, 개발자가 임의의 숫자로 요청을 구분하기 위해 사용
  • obj : UI 스레드에 넘길 데이터, Object 타입의 변수
  • arg1, arg2 : UI 스레드에 넘길 데이터, int 형으로 간단한 숫자를 셋팅하기 위해 사용
profile
되새기기 위해 기록
post-custom-banner

0개의 댓글