CAFE

16. Activity에 대해서 - Thread 와 Android Main Thread

작성자슈퍼성근|작성시간11.07.04|조회수13,031 목록 댓글 28

슈퍼드로이드 카페의 안드로이드 강좌가 책으로 나왔습니다.

도서명 : 이것이 안드로이드다.

도서링크 : http://www.yes24.com//24/goods/13950202

================================================================================================


여러분들은 Thread에 대해서 알고 있는가?

Thread에 대해서 설명하기 앞서 Thread는 정말 간단하다.

그렇게 간단함에도 불구하고 Thead를 어렵다고 생각하는 사람이 참 많을 것이다.

왜일까?

 

Thread의 사용방법을 아주 간단하나, Thread를 관리하고 예외처리를 하는 것들이

많기 때문이다. Thread를 잘못 사용하면 여러가지 문제가 들쑥날쑥 예측하기 어렵게

튀어 나온다. 또한 나온 문제들을 해결하기가 쉽기 않을 것이다.

그러므로 Thread는 최초부터 잘 설계하고 Thread의 생명주기를 확실히 결정해야 한다.

뿐만아니라 비동기적으로 동작하는 코드들에 대한 부분적인 데이터 동기화를

꼬옥 신경써 주어야 한다.

 

서론부터 너무 무거운 얘기인것 같다. ^^;

 

일단 Thread가 왜 필요한지 부터 이해해 나가자.

 

 

1. Thread의 필요성

 

아래의 그림을 보자.

하나의 Process에서는 하나의 Thread가 기본적으로 동작한다.

즉 Thread는 하나의 일을 처리하는 job 단위라고도 볼 수 있다.

하지만 동시에 여러가지 작업을 해야하는 경우 하나의 Thread로 처리하기 힘들다.

위와 같이 네트워크에서 파일을 다운로드 함과 동시에 화면에 다운로드 받는 양을

표시하고자 하는 경우 두가지의 Thread가 필요한 것이다.

 

즉 아래와 같다.

 

또 추가적으로 한 Process에서는 모든 Memory를 공유할 수 있다.

워낙 당연한 말이라서 간단히 설명하겠다.

메모리를 공유한다는 말은

public으로 정의 된 멤번 변수를 예로 들어 보자.

 

class a{

public int mint = 10;

}

 

이라고 정의 하였다.

해당 객체가 생성된 Process 내에 모든 Thread에서

위의 a 객체를 instance화 하였다면

접근하여 사용할 수 있다.

 

Thread 또한 Process안에 포함되었으므로

모든 객체(메모리)를 접근할 수 있는 것이다.

이 것을 Thread의 최고의 강점이라고 할 수도 있고,

Thread를 복잡하다고 생각하게 하는 부분이기도 하다.

 

복잡하다는 이유는 여러 Thread는 비동기 적으로 동시에 돌고 있는 상태에서

서로 특정 참조 변수를 변경하고 참조하되면,

해당 변수의 유효성을 보장받지 못하기 때문이다.

 

(워낙 원론적이 얘기 아닌가? 왜 당연한 것을 계속 설명하려고 할까?

 나의 성격 때문이 아닐까? 생각이 든다. 아는 분들은 빨리 넘어가자. ^^;;)

 

2. Android의 Main Thread에 대해서 

 

하나의 Process는 기본적으로 최소 하나의 Thread가 존재한다고 하였다.

android 패키지는 해당 Thread의 용도를 UI Thread로 활용을 하고 있다.

다른 Thread를 생성하여 UI 작업을 하는 것을 허용하지 않는다.

UI 작업처리를 하는 Thread를 Main Thread라고도 한다.

어쨌든 아래의 그림을 살펴보자.

Main Thread에서 그림을 그리고 다른 Thread에서는 그것을 허용하지 않는다.

 

왜 Main Thread에서만 UI 작업을 가능토록 할까?

아래를 보고 이해하자.

여러 Thread에서 그림을 그릴수 있다고 가정하고,

Thread 1에서는 배경을 그리고,

Thread 2에서는 태양을 그리고,

Thread 3에서는 산을 그린다.

그리는 순서는 꼭 1,2,3 번 순서로 그려야 한다.

 

만일 아래의 순서로 그리면 어떻게 될까?

1번과 2번 과정에서 그렸던 그림이 3번의 그림으로 인해 덮어 버린다.

이 것을 UI가 꼬였다고들 한다.

그렇다. 여러 Thread에서 그림을 그릴때 어떤 동기화가 이뤄지지 않으면

여러가지 야기치 못한 문제가 많이 발생한다.

다른 Platform에서도 이런 문제가 많아 개발자들이 신중히 소스 작업을 해야 한다.

즉 개발자의 책임이 너무 커지게 되는 것이다.

 

Android에서는 이러한 기존 문제점을 Platform에서 해결하고자,

Main Thead에서만 허용한다.

즉 개발자의 부담을 구조적으로 없애버리는 것이다.

 

아래의 특정 apk를 실행한 경우를 살펴 보자.

1,2,3,4,5,6 번 과정을 거치면

해당 Process에 생성된 모든 Thread를 볼 수 있다.

일단 다른 Thread는 생략하고

6번과 같이 main이라는 Thread를 볼 수 있다.

이것이 바로 UI Thread 이고, 즉 Main Thread이다.

우리가 하나의 Activity를 생성하고 활성화 하면 Main Thread에서 돌아간다.

Activity 생명주기에 호출되는 함수들 조차 모두 Main Thread에서 돌아가는 것이다.

 

아래를 테스트 패키지를 작성해 볼 것이다.

위에서 1번을 누르면 TextView의 "Count : 0"이라는 글을

2번과 같이 "Count : 50"이라고 다시 그리게 되는 패키지 이다.

 

패키지 구성은 아래와 같다.

 

A Activity의 소스 구조는 아래와 같다.

즉 버튼을 클릭하면 아래의 1번과 같이 "Count : 50" 이라고 화면에 다시 그릴 것이다.

 

자 실행해 보면 정상적으로 그려짐을 알 수 있다.

 

자 이제 코드를 새로운 Thread를 생성하고 생성된 Thread에서 그려 보자.

1번과 같이 수정될 수 있다.

 

자 다시 실행해 보자.

다른 Thread에서 그림을 그릴 수 없다고 하였다.

그로 인해 에러가 발생한다.

3번과 같이 CalledFromWrongThreadException 이라는 exception을 발생 시킨다.

즉 생성된 Thread에서 호출할 수 없는 함수를 사용했다는 것이다.

그 함수는 당연히 화면에 그리는 함수 있다.

이렇게 안드로이드에서는 화면에 그리는 모든 함수를 Main Thread가 아닌 다른 Thread에서

사용할때 정책적으로 금지한다.

 

아래는 해당 테스트 패키지이다. 참고하자.

첨부파일 ThreadTest.zip

 

그렇다면 정말 Main Thread가 아닌곳에서 화면에 그리는 작업을 할 수 없을까?

정답은 그렇다.

하지만 다른 Thread에서 Main Thread가 그림을 그리도록 요청할 수 있다.

Main Thread는 구조적으로 그러한 것을 지원한다.

 

그것을 HandlerThread 라고 부르고

Main Thread는 HandlerThread 구조이다.

HandlerThread는 내부적으로 Queue를 가진다.

그 Queue안에는 처리 해야하는 일을 해당 Thread에서나 혹은 다른 Thread에서

넣을 수 있다.

그러므로 다른 Thread에서 그리는 작업을 시키지 위해

HandlerThread의 Queue에 Job를 넣을 수 있는 것이다.

 

 

3. Android의 Main Thread 구조

 

3.1 일반적인 Thread에 대해서

 

혹시 일반적인 Thread에 대해서 모르는 사람을 위해 Thread 구현 방법에 대해

먼저 설명토록 하겠다.

 

아래를 그림을 보자.

1번과 같이 Thread라는 객체를 생성함으로써 Thread를 구현할 수 있다.

여기서 2번과 같이 Thread에서 처리할 일을 구현하기만 하면 된다.

그것은 run () 이라는 함수를 구현함으로써 가능하고 구현이 되었다면

3번과 같이 생성되 Thread 객체를 start() 함수를 통해 실행하면

Thread가 동작하고 처리할 일에 대한 내용이 실행된다.

이렇게 처리할 일을 구현하도록 유도하기 위해 Runnable interface를 Thread가 상속 받고 있는 것이다.

Runnable interface는 내부적으로 한가지 함수만 구현하도록 정의 되어 있다.

그 것이 바로 run()함수인 것이다.

 

아래의 소스를 보자.

Thread를 생성하는 것은 아주 간단하다.

첫번째 방법은 기존 Thread가 상속받아 구현한 run()함수를 사용자가 재 정의 하여 구현하는 방법과

두번째 방법은 개발자가 Runnable interface를 생성과 동시에 run()을 구현하여

생성한 Thread에 넣어 주어 실행을 유도하는 방법이 있다.

사용하기 편한 방법대로 구현하라.

 

어쨌는 Thread의 구현과 사용은 정말 쉽지 않은가?

 

물론 Thread에 대해서 알아야 할 것은 많이 있다.

지금은 Android에 대한 강좌이므로 자세한 Thread에 대한 설명을 생략한다.

(Thread에 대해서 모르는 사람은 꼭 Java의 Thread에 대해서 공부하길 부탁한다.)

 

3.2 HandlerThread에 대해서

 

Main Thread는 HandlerThread 구조를 가진다고 하였다.

그것이 왜 필요한지 부터 설명토록 하겠다.

 

자 Thread의 생명주기는 run() 함수내에서 끝난다.

아래의 그림을 보자.

위 처럼 run() 진입이 Thread의 생성이며,

run()함수의 끝이 Thread 종료이다.

만일 Main Thread가 위처럼 run()함수에서 끝나버리면

Android Appliction이 유지 될 수 없지 않겠는가?

 

바로 아래를 보자.

위와 같이 run() 함수에서 어떤 일을 계속 처리하기 위해서

while(true) 와 같이 계속 loop 를 돌게 된다.

 

계속 바로 아래를 보자.

위와 같이 마냥 loop만 돌고 있는 것이 아니라,

looper는 처리해야 할 일을 쌓아둘 Queue를 하나 가지고 있다.

 

계속 아래를 보자.

Queue에는 처리해야할 일 즉 job들을 어떤 누군가가 넣게 된다.

 

계속 아래를 보자.

looper는 계속 looping하면서 queue에 들어가 있는 job들을 하나씩 꺼내서

처리하는 것이다.

 

위와 같은 구조는 모든 platform에서 일반적인 구조 이다.

즉 Android에서도 위와 같은 구조를 Main Thread가 가지고 있다.

 

아래의 Class들이 그런 일들을 하고 있는 것이다.

 

위의 과정을 상기하면서 하나씩 Class의 역할을 알아가 보자.

 

자 HandlerThread Class의 역할은 다음과 같다.

즉 계속 반복해서 loop를 돌고 있는 Looper를 하나 가지며,

Looper 안에는 MessageQueue라고 하는 Queue를 가진다.

 

Message Class는 무엇일까?

위와 같이 Queue에 들어갈 Job 단위 이다.

Message에는 처리해야할 코드들이 들어가 있다.

(참고로 Message Class는 Parcelable을 상속 받았다. 이전에 직렬화 강좌에서

 이 것을 상속 받은면 다른 Process 혹은 네트워크 등으로 객체 전달이 가능하다고 했다.

 그렇다면 이 Message는 바로 다른 Process로 전달이 가능한 데이터 덩어리인 것을 알수 있을 것이다.)

 

Handler Class는 무엇일까?

Queue에 Message를 집어 넣는 역할을 한다.

위에 1~8번의 Handler Class의 함수를 사용하여 Queue에 메시지를 넣게 된다.

참고로 메시지에는 메시지가 처리해야할 시간을 설정할 수도 있다.

그 것이 바로 4,5,7,8번 함수에 들어가는 uptimeMillis와 delayMillis 이다.

uptimeMillis 는 실행될 절대적 시간이며,

delayMillis는 현재 시간을 기준으로 해당 시간이후에 실행된다는 의미이다.

어쨌는 사용 방법은 나중에 해당 객체를 사용할때 확인하도록 하자.

 

마지막으로 Looper Class는 무엇일까?

Looper는 while()문 그 자체이다.

이렇게 반복적으로 Looping 하면서

Queue에 메시지를 하나 꺼내 실행시켜 주는 역할을 하는 것이다.

 

자 쉽게 이해가 되었을 것이다.

 

자 그렇다면 Job 단위라고 하는 Message에 대해서 살펴 보자.

Message가 처리되는 방법은 두가지가 존재한다.

첫번째는 위의 두가지 정보를 설정함으로써 가능하다.

when이라는 변수를 통해 자신이 실행되는 시점의 시간을 설정할 수 있다.

실행되어야 할 시간이 되면 실행되는 코드가 바로

callback이다.

callback는 Runnable이다.

Runnable은 Interface이며, 내부에 구현해야할 함수는 run()이다.

즉 run()함수를 Message객체를 생성할때 구현해 주면

looper가 실행해 주는 것이다.

 

자 두번째를 살펴보자.

두번째는 위의 7가지 값이 사용된다.

when 변수는 위에서 설명하였다.

target은 Handler이다.

Handler는 Queue에 Message를 넣어주는 객체라고 하였다.

그 밖에 기능으로 Handler를 생성할때 위와 같이

public void handleMessage() 함수를 overriding 하여 구현해 줄 수 있다.

첫번째 방법에서 callback 즉 Runnable 객체의 run()을 구현해 주고 실행이 되었다.

두번째 방법은 바로 handleMessage() 함수를 구현하여 실행하게 되는 것이다.

what는 handleMessage에서 작업을 구분할 명이다.

위의 예제는 "0"이라고 하였지만 당연히 구현시에는 "static int  DRAW_RECT = 0" 과 같이

정의 하여 사용해야 한다.

arg1, arg2, obj, data 등은 handleMessage함수로 전달할 데이터 들이다.

arg1, arg2는 int 형이므로 정수 데이터를 쉽게 전달할 것이고

obj는 Object 형이므로 여러가지 객체를 전달할 것이다.

data는 Bundle 형이므로 여러가지 데이터 덩어리를 넣어서 전달할 수 있다.

(Bundle에 대해서는 이전 강좌에서 설명하였다. 모르면 바로 참조하기 바란다.)

 

위의 sample 코드를 살펴보면 쉽게 이해 될 것이다.

 

자 전체전으로 처리되는 과정을 다시 살펴 보자.

1번에서 HandleThread를 생성하면,

2,3,4 번과 같이 MessageQueue가 생성되고, Looper가 생성되고 결국 HandlerThread가 생성이 될 것이다.

5번에서 해당 Thread를 실행하기 위해 HandlerThread를 start하게 되면

6번과 같이 Thread의 run()이 실행되고

7번과 같이 Looper가 계속 반복하여 MessageQueue에 메시지를 꺼내가려고 할 것이다.

               (Message가 없으면 잠시 wait하게 된다.)

8번에서 Handler를 생성하면

9번과 같이 MessageQueue에 메시지를 넣기 위하여

         Handler는 Looper를 참조하게 된다.

         (Looper는 MessageQueue를 참조하므로 결국 메시지를 넣을 수 있는 참조가 된다.)

 

10번에서 처리해야할 Job 단위인 Message를 하나 생성한뒤

        처리해야할 코드를 넣어준다.

        코드를 넣는 방법은 위에서 두가지라고 했다.

        (callback : Runnable 객체의 run()을 구현해서 넣는 방법과,

         target : Handler의 handleMessage()를 구현하는 방법이 있다.)

        두가지 방법을 모두 넣게 되면 callback이 실행된다.

11번에서 Handle.sendMessage()함수 등을 이용해서

         MessageQueue에 메시지를 넣으면

12번에서 Looper가 넣은 메시지를 하나꺼내서

13번과 같이 callback이 구현되어 있으면 callback의 run()함수를 실행해 주고

                 target이 구현되어 있으면 target의 handleMessage()함수를 실행해 준다.

 

이 것이 전체적인 구조이며, MainThread는 위와 같은 구조를 가진다.

 

아래는 그 증거를 보여 주고 있다.

내가 테스트로 com.test.ThreadTest 라는 패키지가 실행된 상태인 경우,

(아무 패키지를 실행해도 된다.)

1번과 같이 DDMS를 선택하고

2번에서 Devices 탭의 해당 패키지 Process를

3번과 같이 선택한다.

4번과 같이 Threads라는 탭을 선택하고

5번과 같이 "update Threads"버튼을 누른다.

이렇게 하면 해당 Process 내 생성된 Thread를 모두 보여준다.

6번에 main Thread를 선택하면

7번과 같이 해당 Thread에서 생성된 객체들을 모두 보여준다.

자세히 보면 MessageQueue 객체와 Looper 객체가 생성된 것을 볼 수 있다.

 

Main Thread에서만 HandlerThread를 사용하는 것이 아니다.

Android 전역전으로 HandlerThread 구조를 적절히 사용한다.

이런 구조는 활용할 범위가 많다는 것을 기억하자.

 

3.2 다른 Thread에서 화면에 그림을 그려 보자.

 

내가 처음에 다른 Thread에서 화면에 그리는 작업을 할 수 없다고 하였다.

만일 그러한 동작을 시도하려고 하면

위와 같이 exception이 발생될 것이다.

 

하지만 Main Thread는 HandlerThread구조이며,

다른 Thread에서 그리는 작업을 MainThread에 시키면 가능하다.

다른 Thread에서 Handler를 하나 만들고 Handler.sendMessage()함수를 통해

그리는 Job을 넣어주면 되는 것이다.

 

자 위에서 작성한 패키지 소스를 수정해 보자.

 

Message를 작성할때 두가지 방법이 있다고 하였다.

1. callback : Runnable 객체의 run()을 구현해서 넣는 방법과,

2. target : Handler의 handleMessage()를 구현하는 방법이 있다.

 

우선 Runnable 객체의 run() 함수를 이용하여 작성해 보자.

1번에서 Main Thread의 MessageQueue에 Message를 넣기 위해

Handler 객체를 하나 생성한다.

2번에서 버튼이 클릭되었을때 Thread 객체를 하나 만든다.

3번에서 Runnable 객체를 하나 생성과 동시에

           실행될 내용을 담을 run() 함수를 구현한다.

           그 내용은 화면에 "Count : 50" 이라고 그림는 작업이다.

4번에서 Message 객체를 생성하고

            객체 내에 runnable 객체를 참조시키기 위해 Message 생성자에 넣어준다.

5번에서 MessageQueue에 Message를 넣는다.

 

자 실행해 보자.

                    

정상적으로 화면에 출력되었다. ^^/

본 패키지는 아래를 참조하자.

첨부파일 ThreadTest_runnable.zip

 

자 두번째 방법인 Handler의 handleMessage()를 이용하여 수정해 보자.

1번에서 Handler 객체를 생성과 동시에

            처리할 내용을 담을 handleMessage() 함수를 구현해 준다.

2번에서 화면에 그리는 작업 요청(msg.what)이 오면

           화면에 그리는 코드를 작성한다.

3번에서 버튼이 클릭되면 Thread를 하나 생성한다.

4번에서 Message를 하나 생성한다.

           여기서 Message.obtain() 함수의 두번째 인자가

           msg.what에 해당하고 요청 코드에 해당한다.

           작성된 메시지를 MessageQueue에 넣는다.

 

자 실행해 보자.

 

정상적으로 화면에 출력되었다. ^^/

본 패키지는 아래를 참조하자.

첨부파일 ThreadTest_handleMessage.zip

 

자 그렇다면 왜 이렇게 두가지 방법을 제공할까?

 

Runnable 객체의 run()를 구현하는 방법은

간단히 한가지일을 처리할때 많이 사용한다.

소스를 보면 알겠지만 매우 간단하다.

 

Handler 객체의 handleMessage()를 구현하는 방법은

한 가지 요청이 아니라 여러가지 요청을 처리할때 사용한다.

handleMessage()에 다양한 요청(msg.what)에 대한 코드를 미리 구성하고

특정 요청이 필요할때 간단히 Message를 하나 만들어서 Handler.sendMessage()함수를

호출하면 된다.

개인적으로 참 코드가 깔끔해 지는 것 같다.

또한 handleMessage()의 인자로 Message 객체를 전달함으로써

다양한 데이터를 전달할 수 있다.

 

 

3.3 AsyncTask

 

다른 Thread에서 그림을 그리는 작업을 하기위해

Main Thread의 MessageQueue에 그리는 작업에 대한 Message를

넣는 방법을 배웠다.

특정 동시 작업을 위해 Thread를 하나 만들고

그리는 작업을 처리하기 위해 Main Thread에게 메시지를 보내는

작업이 참 번거롭고 복잡하지 않을까?

 

이에 대한 도움을 주는 Class가 존재한다.

이 Class는 이렇게 번거롭고 어떻게 보면 복잡한 내용을 구조적으로 정리해 준다.

쉽게 말하면 해당 Class를 이용하면

Message 를 생성하고 Handler를 이용해서 sendMessage() 처리할 필요가 없다.

 

해당 Class는 그림을 그리는 작업을 위한 Callback 함수를 제공한다.

우리는 그냥 그림을 그리는 작업을 할때 해당 함수를 overriding하여 구현하면 끝이다.

(뭐 -  _-a Class 내부적으로는 Handler를 사용하지만 우리는 신경 쓰지 않아도 된다.)

 

이해를 위해 잠시 아래를 보자.

AsyncTask는 객체 내부적으로 여러가지 Callback 함수를 지원한다.

그리 어렵지 않다.

위에서 1,5,7번 의 CallBack 함수는 Main Thread에서 처리된다.

이 말은 즉 UI 작업을 할 수 있는 함수라는 것이다.

2번의 경우는 AsyncTask 내부에서 Thread를 하나 만들어서 별도의 Thread에서 동작된다.

그러므로 UI 작업은 할 수 없다.

일단 예로 파일을 다운받는 모듈을 구현했다고 보자.

화면에는 진행바를 출력할 것이다.

 

일단 AsyncTask 객체를 상속받아 내부를 구현하고

AsyncTask.execute() 함수를 실행하면 원하는 동작이 시작된다.

 

1번의 경우는 화면에 진행바의 초기 모습을 그린다. (onPrepareExecute() 콜백함수를 구현)

2번에서는 파일을 다운로드한다. (doInBackground() 함수 구현)

              다운로드 받을 파일일 여러개인 경우 반복적으로 반복문을 돌면서 처리한다.

              만일 파일하나를 다운받고 화면에 진행정도를 그리고자 한다면

              publishProgress()함수를 호출하면

5번과 같이 onProgressUpdate()함수가 호출된다.

              우리는 이곳에서 현재 진행정도를 화면에 그리면 된다. (onProgressUpdate()  함수구현)

6번과 같이 doInBackground()함수의 처리가 끝나고 그 결과를

                리턴하면

7번과 같이 onPostExecute()함수가 호출된다. (onPostExecute() 콜백함수 구현)

              이 곳에서 진행화면을 종료관련 화면이든 완료 결과 화면이든 그리면 되는 것이다.

 

너무나 구조적으로 깔끔하게 구분되어 코딩되도록 유도하고 있지 않은가?

이 객체의 가장 장점은 구조에 볼 수 있는 것이다.

 

 

다시 상세히 아래를 보자.

위의 2,3,5,7 함수가 모두 AsyncTask를 상속받아 구현해 주어야 할 부분이며

AsyncTask 객체 자체이다.

위에서 보는 바와 같이 AsyncTask를 상태 정보를 가진다.

PENDING, RUNNING, FINSHED 정보이다.

우리는 AsyncTask.getStatus() 함수를 통해 해당 상태 정보를 얻을 수 있다.

 

추가로 만일 중간에 AsyncTask의 동작을 취소하고자 한다면

간단히 AsyncTask.cancel() 함수를 호출하면 끝난다.

다면 현재 진행 내용에 대한 정리가 필요할 것이다.

그러므로 AsyncTask는 cancel() 함수가 호출될때,

사용자에게 정리를 유도하는 onCancelled() callback이 호출된다.

우리는 onCancelled() 콜백을 적절히 구현해 주면된다.

 

자 구현시 주의할 점은 AsyncTask를 제어하는 함수(execute(), cancel()..)등은

꼭 Main Thread에서 제어해야 하고, 한번 이상 함수를 호출하면 안된다.

 

아래는 여러가지 파일을 다운로드 한다고 가정하고

패키지를 하나 작성하였다. 패키지 구성과 AndroidManifest.xml 내용은 아래와 같다.

 

화면에 다운로드의 진행정도를 표시하는 TextView를 Layout에 추가하였다.

 

아래는 Activity의 구현부이다.

1번에서 구현된 AsyncTask를 상속받은 DownloadTask를 하나 생성하였다.

이어서 execute()함수를 호출하면서 해당 AsyncTask는 동작하게 된다.

2번은 AsyncTask를 상속받은 DownloadTask 의 구현 내용이다.

3번에서 파일의 개수 만큼 반복하면서 파일을 다운로드한다.

            위에서 말했지만 해당 함수는 별도의 Thread에서 동작한다.

            그러므로 UI 작업을 하지 않는다.

            중간 중간에 진행정도를 표시하기 위해 publishProgress()함수를 호출해 준다.

4번은 초기 화면을 그린다.

5번은 3번에서 publishProgress()함수가 호출할때마다 호출되는 함수이며,

         진행정도를 화면에 갱신한다.

6번은 3번의 처리가 끝나서 리턴되면 호출되며,

         진행완료 결과에 대한 그림을 화면에 그리게 되는 것이다.

 

위의 구현 내용에 대해서 다시 살펴 볼 부분이 있다.

그것은 바로 데이터들과 각 함수의 인자이다.

먼저 1,1,1 번을 보자. 

제너릭스(Generics)가 사용되고 있다. (제너릭스는 Java 1.5 부터 추가된 문법이다.)

즉 객체 내에서 사용될 Type 지정한다.

이렇게 제너릭스를 사용하면 AsyncTask 내부에서 사용될 Type을

변경하지 않고 AsyncTask 를 상속받아서 구현할때 지정할 수 있어서 아주 편리하다.

(기타 제너릭스의 장점은 많다. 단 가독성을 저해하는 단점도 있다.)

 

3가지 형은 각각 어떻게 전달이 되는지 색상별로 확인해 보자.

먼저 첫번째 인자는

AsyncTask를 활성화하는 execute()함수의 인자로 들어간다.

대부분 처리해야할 목록을 전달하는 용도로 사용된다.

두번째 인자는

진행과정의 정보를 전달하는 용도로 사용된다.

이 정보를 보고 대부분 진행정도를 화면에 그리게 된다.

세번째 인자는

doInBackground의 결과

즉, 진행하고자 하는 작업의 결과값이다.

이 값은 화면에 최종적으로 그려지는 함수 onPostExecute() 인자로

전달되고 성공, 실패 등의 결과를 화면에 출력하게 된다.

 

 

자세히 보면 정말 복잡할 수 있는 내용을

간단히 구현할 수 있다는 것을 알 수 있다.

 

실행해 보면 아래와 같이 화면에 표시된다.

 

 

위의 테스트 코드는 아래를 참조하자.

첨부파일 AsyncTaskTest.zip

 

 





!!! 위의 주제에 해당하는 적당한 예를 댓글로 남겨 주세요. ^^

    활용 방안의 예는 다른 개발자들에게 많은 도움이 됩니다. 


!!! 카페의 활성화를 위해 추천 버튼을 눌러 주세요. 


 

다음검색
현재 게시글 추가 기능 열기

댓글

댓글 리스트
  • 답댓글 작성자슈퍼성근 작성자 본인 여부 작성자 | 작성시간 14.03.12 안녕하세요. ^^

    둘의 장단점을 제시할 수는 없습니다.
    그 이유는 thread + handler 를 좀더 편하게 사용할 수 있도록 나온것이 AsyncTask이기 때문입니다.
    따라서 개발하시는 프로젝트에 맞게 사용하시면 되겠네요.

    단 AsyncTask는 UI Thread와 작업 Thread에서 처리해야 할 일들을
    함수로 명확히 구분해 주기 때문에 개발자의 실수를 많이 줄어줍니다.
    또한 사용도 편하죠.
    AsyncTask 구조를 사용할 수 없는 경우가 아니라면
    최대한 AsyncTask를 활용해 보세요.
  • 답댓글 작성자juniano | 작성시간 15.01.06 슈퍼성근 제가 얼핏 주워들은 얘기로는 AsyncTask 내부적으로는 Thread를 쓰는데 생성 갯수에 제한을 두고 있다고 들었습니다. 즉, 아무리 많은 AsyncTask를 생성해서 수행해도 내부적으로는 지정된 Thread개수를 초과하지 않게 수행이 되어 process overhead (소위, 죽는..)가 나는 경우를 방지한다고 들었습니다.
  • 답댓글 작성자슈퍼성근 작성자 본인 여부 작성자 | 작성시간 15.01.06 juniano 좋은 정보 감사합니다. 덧붙입니다.

    AsyncTask는 내부적으로 Thread pool을 가집니다.
    쉽게 말하자면 동시에 스레드를 생성해서 돌리는 최대 개수가 있습니다.
    공식은 아래와 같습니다.

    단말기의 CPU 개수 * 2 + 1
    즉 단말기에 CPU 개수가 4개라면 9개의 스레드가 동시에 돌아갑니다.
    만일 초과하면 다른 스레드가 끝날때까지 대기하게 됩니다.

    사실 Thread를 무작정 많이 생성하여 돌린다고 속도가 빨라지는 것이 아닙니다.
    오히려 스레드 개수가 많으면 overhead가 생기죠.
    따라서 스레드 개수는 적정선을 두고 돌리는 것이 이상적입니다.
    그래서 위와 같은 스레드 생성 개수 제한 공식이 필요한 것입니다.
  • 답댓글 작성자슈퍼성근 작성자 본인 여부 작성자 | 작성시간 15.01.06 슈퍼성근 하지만 사용자 입장에서는 내부 구조를 무시하시고 사용해도 됩니다.
    내부적으로 적절히 스레드를 생성하고 동작시켜 주기 때문입니다.

    여기서 오해할 수 있는 내용이 있는데요.
    AsyncTask 하나는 Thread 하나입니다.
    하나의 AsyncTask 객체가 여러개의 Thread를 생성하는 것이 아닙니다.
    위에서 말한 것은 AsyncTask 객체를 100개 생성하고 동시에 돌릴때
    각 객체만큼 Thread가 막 생기는게 아니라는 말입니다.

    수고하세요.
  • 답댓글 작성자juniano | 작성시간 15.01.06 슈퍼성근 역시 대단하십니다. 사실 윗 답글을 적을 때 슈퍼성근님께 자세한 내용 알려주십사 부탁드리려 했는데 저도 주워들은 내용이라 제 답변 자체에 확신이 없어서 (혹시 틀린 내용일까봐서요 ㅎㅎ) 그 내용은 안 적었었습니다. 하지만 역시 기대를 저버리지 않으시는군요.. 감사합니다.
댓글 전체보기
맨위로

카페 검색

카페 검색어 입력폼