이 글은 깡쌤의 안드로이드 프로그래밍 책을 보고 작성하였습니다.
ANR(Application Not Responding)을 방지하기 위해서 스레드를 작성한다고 하였습니다. 이 글에서 그 스레드 작성법을 알아보겠습니다.
안드로이드에서 스레드는 자바의 Thread API를 그대로 이용합니다.
스레드를 만드는 방법은 다음과 같습니다.
// 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() 함수 등 다양합니다.
안드로이드 시스템에서 액티비티를 수행하면서 발생시킨 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 스레드에 뷰와 관련된 작업을 의뢰하는 방식으로 개발해야 합니다.
개발자가 정의한 스레드가 수행되다가 UI 스레드에게 뷰에 대한 작업을 의뢰하는 방법 다음과 같습니다.
우선 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 객체가 매개변수로 사용된는데, Message 클래스는 뷰 작업을 의뢰할 때 UI 스레드에 넘기는 데이터를 담는 객체입니다. Message 객체의 what과 obj, arg1, arg2 프로퍼티에 필요한 만큼 데이터를 담아 UI 스레드에 전달하면 됩니다.