오늘은  멀티Thread와 관련된 Runnable과 Looper 그리고 Handler에 대해서 정리해 보겠습니다.



1. 멀티 Thread


Java의 JVM에서는, 하나의 Process에서, 

Thread 라고 하는 코드를 실행시키는 객체를 여러개 가질 수 있도록 허용하고 있습니다.

이를 활용하여, 복잡한 계산이라든가, 네트워크 등의 실행을 다른 쓰레드에서 효율적으로 실행할 수 있도록 하고 있는데요.

멀티 Thread를 실행하는데 관련된 요소들을 정리해 보도록 하겠습니다.



1-1. Process 와 Thread


먼저 쓰레드가 속해있는 Process에 대해서 정리해 보겠습니다.

Process는 실행중인 프로그램의 객체를 의미하는데요.

이를 Android를 기준으로 생각해보겟습니다. 

Android에서 앱을 실행할때, 먼저 MainActivity의 코드와 데이터를 메모리에 올려서,

실행을 할 수 있도록 하나의 Process를 시작합니다.


이러한 Process는 독립적인 메모리 공간을 부여받는데요.

코드를 실행시키는 Thread와 이 메모리 공간을 공유합니다.

멀티 Thread환경이라면, 여러개의 Thread가 Process의 메모리를 공유하게 되겠지요.




1-2. Main Thread와 Worker Thread


안드로이드에서 Process를 시작하고, 최초로 가지게 되는 Thread가 MainThread인데요. 

유저와의 인터랙션을 하는 UI와 컴포넌트들을 연결시켜 주어서 UI쓰레드라고 부르기도 합니다.


MainThread와는 다르게, 코드에서 새롭게 생성하여, 사용하는 쓰레드들을 WokerThread라고 합니다.



1-2. 멀티 Thread의 필요성


안드로이드 시스템은, 네트워크작업을 Main쓰레드에서 할 경우, Exception이 나도록 하고 있습니다.

네트워크 뿐만이 아니라, 복잡한 데이터 계산이나, DB 쿼리작업등은 Worker Thread를 생성해서 작업 하도록 권고하고 있구요.


MainThread에서의 과다한 작업으로, UI가 정지하는 시간이 길어질 경우에는, ANR(Appication Not Responding) 에러를 주고 있는데요.

ANR에러란, 일정시간이상 화면이 정지되면, 안드로이드 시스템이 앱을 shutdown시키는 것인데요.

서비스에 매우 치명적이라고 할 수 있습니다.


ANR이라는 극단적인 경우를 피하려면, 멀티 Thread는 선택이 아니라, 필수라고 할 수 있겠네요.


Android 공식문서에서도, UI쓰레드에서 데이터베이스 쿼리를 하거나 네트워크 호출등의 복잡한 작업을 UI작업과 동시에 하게되면, 

Performance가 좋지 않기 때문에, 멀티쓰레드를 사용하도록 권고하고 있구요.


게다가, 요즘 나온 휴대폰들이, 8개의 Core를 가진 OctaCore도 나오고 있다는 점을 감안하면, 

멀티 Thread를 잘 활용하는 것이 앱의 성능향상에 도움이 될 수 있겠습니다.



2. Runnable


위에서 Thread가 일련의 작업을 하는 코드를 실행한다는 것은 말씀드렸는데요.

Thread가 어떻게 run(실행)할지 정의해놓은 것(코드)을 Runnable이라고 합니다.


Thread의 실행계획표라고 할 수 있을 것 같은데요.

이 실행계획표는 interface이고, 구현해야할 메소드는 run()입니다.

이 run()에 실행할 일들을 코드로 구현하는 거죠.



3. Thread.start()


Thread에 start()메소드를 실행하면,

Process의 메모리와 Cpu를 Thread의 스케쥴러가 할당해주고, 실행이 되는 것인데요.

(Thread객체만 생성하면 않되고, 꼭 start()메소드를 실행해 주어야 합니다.)


스케쥴러는 Cpu를 할당할 쓰레드들을 Queue에 넣어서 관리를 하는데,

wait()가 호출 되면, 다시 할당되지 않도록 하고,
notify()가 호출되면, 다시 할당되어서 Thread가 구동되도록 합니다.



3. EventLoop을 이용한 Thread 간 통신


Android에서 Worker Thread를 생성해서 작업을 처리하고 나면,

Main Thread에게 계산된 값을 넘겨주거나, UI에 반영되도록 하는 경우가 있습니다.


이 때, 필요한 것이 EventLoop객체인, looper와 

메시지나 이벤트를 전달하는 Handler인데요.


먼저 EventLoop이 무엇인지 정리해보고,

안드로이드의 Looper객체와 Handler를 사용하는 방법을 정리해 보겠습니다.



3-1. Event Loop(Message Loop)


이벤트나 메시지가 들어오면, 이를 담아놓을 수 있는 Queue라는 자료구조를 사용합니다.

Queue에 처리할 이벤트나 메시지가 있으면 처리를 하고, 

없으면 wait()하다가, 이벤트(메세지)가 들어오면, notify()를 호출해서 다시 구동합니다.


이와 같은 방식으로 Exit()를 만나기 전까지는,

wait()와 notify()를 반복해서 loop을 돌게 됩니다.



3-2. Looper 와 Handler를 이용한 Thread간 통신


안드로이드에서의 EventLoop이 Looper이구요.

Looper.prepare()를 하면, queue를 생성합니다.


Handler는 prepare()를 통해 만들어진 Queue에 들어가는, 메세지(또는 이벤트) 객체인데요.

이것을 이용하면, 다른 Thread에서 해당 Thread에 작업을 전달할 수도 있게 됩니다.


즉, 쓰레드의 EventLoop(메세지loop)에다 Event(메세지)를 넣어주면, 해당 작업을 처리하는 것입니다.

예를들면, 워커Thread에서 계산한 값을, 메인 쓰레드에게 전달하려고 하려고 한다고 가정해보겠습니다.

이럴때는, Handler객체의 인자로, 메시지를 넘겨주어서,

미리 정의해 놓은, 할일을 받은 메시지에 따라 하도록 하거나,

할일을 정의한 Runnable객체를 인자로 넘겨주면 되는 것 이죠.


Looper.loop()을 이용해서 아래와 같이, Eventloop을 돌릴 수 있습니다.

여기서는 메세지를 받으면, TextView에다 텍스트가 나오도록 하였습니다.




4. Handler 좀 더 알아보기


쓰레드마다 하나의 Event Loop(Message Loop)을 가질 수 있는데요.

하나의 쓰레드에 있는 Loop는 여러개의 Handler를 가질 수 있습니다.


실제로 아래 코드와 같이 Handler를 생성하면, 해당 코드가 실행되는 쓰레드에 Handler가 생성되어 집니다.


참고로 코드의 첫번째 줄은 해당쓰레드의 핸들러를 생성하는 것이구요.

인자로 특정 looper의 핸들러 객체를 생성하도록 할 수 있는데,

두번째 줄은 main쓰레드의 looper를 가져오는 형식입니다.

(Main쓰레드의 핸들러를 생성한다는 것은, Main쓰레드에 메시지나 Event를 전달하겠다는 것이죠)





5. MainThread와 WorkerThread의 통신


MainThread에는 안드로이드 시스템이 EventLoop을 생성시켜 놓는데요.

따라서 Event Queue(메세지 Queue)를 가지고 있습니다.


이 EventQueue에 단순히 메세지만을 넘겨줄 수 도 있고,

이벤트를 받으면 어떻게 처리할지를 정의한 Runnable객체를 넘겨줄 수도 있어요.


화면 UI는 MainThread만 해야하는 안드로이드에서는,

네트워크 작업등을 WorkerThread에서 하고,

MainThread의 Handler를 얻어서 작업한 결과등을 전달하는 것입니다.


메시지를 보내는 방법은 다음과 같은데요.

먼저, 아래와 같이, obtainMessage()메소드를 이용해서, 메시지를 보낼 객체를 생성하구요.

인자에는 정의한 메세지 타입을과 넘겨주고자 하는 int값 그리고 Object를 넘겨줄수 있습니다.


또한 인자로 보낼 값이 없다면, sendEmptyMessage()의 인자로 int값의 타입만 넘겨줄 수도 있구요.




잠시, obtainMessage메소드의 소스코드를 보고 가겠습니다. 가장 인자를 많이 가지고 있는 타입을 보도록 할께요.

인자로 메세지의 타입을 나타낼수 있는 what과, int값들을 넘겨줄 때 사용하는 arg1, arg2, 그리고 캐스팅해서 받아서 쓸 수 있는 Object타입까지 준비가 되어 있습니다.




메세지를  받는 방법은 아래와 같은데요.

이렇게 handleMessage()메소드를 사용하면, 메세지 Queue에 메세지를 넣어주고, 처리가 되도록 합니다.




sendMessage는 보내고자 하는 Thread에서 처리되고,

handleMessage는 받는 Thread에서 처리됩니다.


[3-2. Looper 와 Handler를 이용한 Thread간 통신]에서도 잠시 언급했었지만,

Main Thread와 Worker Thread간에 통신을 할 때는,

핸들러에게 메세지를 전달해서, 이에 따라 동작하는 방법이외에도,

아에 어떻게 동작할지 할일을 정의한 Runnable객체를 인자로 주어서, 실행할 코드를 넘겨주는 방법도 있는데요.

이 때 사용하는 것이 post()메소드입니다. 




실행할 코드, 즉 이벤트를 넘겨주거나, 메세지를 핸들할 핸들러에서 값을 받아 실행하거나 똑같이,

핸들러에서 메세지나 이벤트를 받아서, 메세지 Queue(이벤트 Queue)에 넣어주고,

Looper에서 실행되는 것은 똑 같습니다.


실행코드가 어디있느냐의 차이일텐데요.

아래에서 코드를 보면서 좀더 정리해보겠습니다.



6. Thread 사용 예


카운트를 0부터 30까지 세고, 다 세면 카운트 완료라고 텍스트뷰를 업데이트 하는 워커 쓰레드를 생성하고, 메인스레드에 메세지를 전달해보겠습니다.


먼저, 메세지 큐에 메세지를 넣는 Handler부터 생성하구요.

(Looper.getMainLooper()를 인자로 주고, 메인쓰레드의 핸들러라고 명확하게 하도록 하겠습니다.)

이 때, handleMessage()를 오버라이드 해줍니다.

전달해오는 메세지가, MESSAGE_TEST_TYPE과 같으면, TextView를 "카운트 완료"로 보이도록 UI를 업데이트 하도록 하였습니다.




다음으로, 클릭시에 인자로 어떤 작업을 할지를 정의하는 Runnable객체를 정의한후,

Thread에 인자로 전달을하고, start()메소드를 실행하여 Thread를 구동시키겠습니다.


정리해보면, for문 안에서, 0~30까지는 Handler에 실행할 코드를 정의한 Runnable을 post메소드를 이용하여 넘겨 주었구요. 

for문을 다 돌고나서는, Handler에 메세지를 전달하는 방식을 통해서, MESSAGE_TEST_TYPE을 전달하고, handleMessage를 오버라이드 한 코드가 실행되도록 하여서,

최종적으로는 카운트 완료가 출력되도록 하였습니다.

Handler에 넘겨줄 수 있는 두가지를 모두 써 본 것이죠.




이상없이 잘 동작하고, 카운트 완료를 TextView로 출력하였습니다.




다음 글에서는 Future Class와 Callable에 대해서 정리해 보겠습니다.

+ Recent posts