슈퍼드로이드 카페의 안드로이드 강좌가 책으로 나왔습니다.
도서명 : 이것이 안드로이드다.
도서링크 : 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에서
사용할때 정책적으로 금지한다.
아래는 해당 테스트 패키지이다. 참고하자.
그렇다면 정말 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를 넣는다.
자 실행해 보자.
정상적으로 화면에 출력되었다. ^^/
본 패키지는 아래를 참조하자.
자 두번째 방법인 Handler의 handleMessage()를 이용하여 수정해 보자.
1번에서 Handler 객체를 생성과 동시에
처리할 내용을 담을 handleMessage() 함수를 구현해 준다.
2번에서 화면에 그리는 작업 요청(msg.what)이 오면
화면에 그리는 코드를 작성한다.
3번에서 버튼이 클릭되면 Thread를 하나 생성한다.
4번에서 Message를 하나 생성한다.
여기서 Message.obtain() 함수의 두번째 인자가
msg.what에 해당하고 요청 코드에 해당한다.
작성된 메시지를 MessageQueue에 넣는다.
자 실행해 보자.
정상적으로 화면에 출력되었다. ^^/
본 패키지는 아래를 참조하자.
자 그렇다면 왜 이렇게 두가지 방법을 제공할까?
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() 인자로
전달되고 성공, 실패 등의 결과를 화면에 출력하게 된다.
자세히 보면 정말 복잡할 수 있는 내용을
간단히 구현할 수 있다는 것을 알 수 있다.
실행해 보면 아래와 같이 화면에 표시된다.
위의 테스트 코드는 아래를 참조하자.
!!! 위의 주제에 해당하는 적당한 예를 댓글로 남겨 주세요. ^^
활용 방안의 예는 다른 개발자들에게 많은 도움이 됩니다.
!!! 카페의 활성화를 위해 추천 버튼을 눌러 주세요.
첫댓글 스레드와 핸들러의 구분을 조금이나마 명확하게 이해를 했습니다. 감사합니다. ^^
내용을 명확히 이해하지 못해서 이런 질문을 하는지는 모르겠습니다. thread와 thread간의 통신을 위해서 messageque와 handler가 필요할까요? 기본적으로 process내의 thread와 main thread는 서로의 영역의 접근과 data공유가 가능하지 않았나요? 만약 그렇다면 굳이 메시지를 보낼 필요가 있는지 모르겠어요. 메시지의 type이 parcelable이란건 다른 process에 있는 thread들끼리 통신할 수 있다는 말로 들리는데요. 좀 햇갈리네요.
안녕하세요.
Thread 간의 통신을 위해 message queue를 말씀드린것이 아닙니다.
위에 좀더 자세히 보시면 Main Thread에 무엇을 시킬때 handler를 사용하면 편리하다는 것이고
따로 메모리가 Thread간은 공유하기 때문에 변수등을 사용하셔도 됩니다.
Handler의 편리함은 꼭한번 위의 내용을 확인해 주셨으면 좋겠네요.
또한 Message 객체가 parcelable인 이유가 있습니다. 다른 Process에서
해당 Process의 Thread로 Message를 전송하여 Queue에 집어 넣을 수도 있습니다.
그러므로 Message 객체는 Process 통신이 되어야 합니다. ^^
아직 다 읽진 못했는데요. 구글링좀 하고 이해를 해봤는데요. 우선 뒤에 message부터, 말씀하신데로 다른 프로세스의 thread가 다른 process의 thread와 통신하는(예를들면 채팅같이) 경우는 당연히 parcelable이 아니면 채팅할 수 없으니까..parcelable이 되야 하는게 쏙들어오네요. 그리고 main thread는 기본적으로 message que가 제공이 되네요..그리고 working thread는 message queue가 제공이 안되서 loop를 만들면 안에서 message que가 생겨서 main thread와 message를 주고 받을수가 있네요. 즉 말씀의 주안점은 main thread(UI thread)의 message que와 일반 working thread의 메시지 교환방식이 갖는 편리함을 쓰신거같아요.맞나요?
네 정확히 전달하고자 한 사항은
1. Main Thread에서는 화면에 그리는 작업이 가능하다는 점
2. 다른 Thread에서는 그리는 작업을 할 수 없다는 점
(왜 다른 Thread에서는 그리는 작업을 할 수 없는지는
위에 설명을 참조하세요.)
3. 다른 Thread에서 그리는 작업을 할 수 없으니
Main Thread에서 그 작업을 하도록 시켜야 한다는 점
4. Main Thread에서 작업을 시키기 위한 좋은 객체가 존재하며,
바로 그 객체가 HandlerThread라는 점.
(그러므로 MainThread에서는 HandlerThread 가 존재한다)
!!! 참고로 HandlerThread는 Thread를 상속 받았습니다.
그러므로 HandlerThread도 Thread입니다.
일반적인 Thread를 확장해서 만든 HandlerThread의 강력한 기능이
바로 Looper와 MessageQueue입니다.
작업을 순차적으로 처리할 수 있는 스케쥴 기능이 있으니까요.
그러므로 해당 기능의 장점이 필요한 경우
어느 곳에서나 적용하여 사용하시면 됩니다.
즉 내 코드에서 HandlerThread의 스케쥴 개념이 필요하시다면
객체를 생성하여 사용하시면 됩니다.
이 장점을 선택한 것이 바로 MainThread인 것이죠.
저의 강좌가 직관적이지 못했나 봅니다. ^^ 죄송합니다.
슈퍼성근님의 강좌는 제가 생각하는 방식으로 공부를 하는 사람중에 한분이세요. 그래서 대부분의 강의는 술술 잘읽히고 본질을 놓치고 있지 않다고 생각해요. 저도 그렇게 공부를 하고 있거든요. 근데..이 강좌는 좀 어렵다는 느낌이 들었어요. 기존 강의처럼 친절한 설명과 구체적으로 설명하셨어요. 어쩌면 다른강좌는 제가 background가 있는데 이강좌는 아니였는지 모르겠어요. 강좌시작전에 UI thread와 thread의 차이를 설명하셨으면 이해가 더 쉬웠을꺼 같아요. UI thread는 messageque(in-looper) 와 그냥 working thread에 대한 차이가 있고, working thread안에 looper를 다는것 그리고 Handler를 추가한다는...
어떻게 보면 슈퍼성근님입장에선 당연한 내용이지만, 첨 접할때는 햇갈릴수가 있거든요. 저는..Handler 그냥 project 생성하고 activity안에 만들면 어떻게 되는거지? looper도 따로 만들어야 하나...만들면 연결을 어떻게 해야 하나...looper하고 handler는 다만들어주는건가 아니면 안드로이드에서 제공이 되는건가? 이런 고민했는데요.물론 성근님 강좌를 읽으면 다 나오는 얘기지만 첨 부분에서 혼란이 생기니까..강좌를 쫒아가지 않고 구글링을 하고 다시 왔습니다. 강좌는 역시 최고...
많은 조언 감사드립니다.
더욱 많은 조언 부탁드리며, 질 좋은 강좌로 보답해 드리겠습니다.
감사합니다.
3.3 AsyncTask 부분에
이 말은 즉 UI 작업을 함수 있는 함수라는 것이다.
오타발견이요 ~ 할수 있는 함수
Message 객체에 target, callback 필드는 버전이 올라가면서 없어진 것인가요? 이것에 대한 질문을 한참을 고민하며 작성할려고 하려던 찰나 안드로이드 api 문서의 Message 부분에는 저 필드를 찾을 수가 없네요
제 강좌에서 설명이 많이 부족했던것 같습니다.
먼저 callback과 target은 Message.obtain() 함수를 통해서 사용할 수 있습니다.
원형은 아래와 같습니다.
public static Message obtain(Handler h, Runnable callback) {
Message m = obtain();
m.target = h;
m.callback = callback;
return m;
}
위의 framework 원형 함수에서 인자 callback, h 가 바로 그 값을 설정하는 것입니다.
그 다음 Target만 설정하는
원형함수는
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
와 같이 추가할 수 있습니다.
위에 인자에서 Handler h 가 바로 target이 됩니다.
뿐만아니라 obtain의 오버로딩 함수들을 보시면
해당 값들을 설정하는 다양한 함수를 제공하고 있습니다.
감사합니다.
AsyncTask와 Handler를 생각없이 쓰는 일이 많았는데, 본질적인 부분을 정리해주셨네요. 감사합니다.
AsyncTask는 웹 API를 호출해서 결과값을 가져오는 처리에서 정말 많이 쓰곤하죠. ㅎㅎ
감사합니다. ^^
Thread와 Handler에 대해서는 알고 있었지만 HandlerThread의 존재에 대해서는 전혀 모르고 있었네요^^;;
좋은 강좌 정말 정말 감사합니다~~
오타가 있네요 ㅎㅎ OnCancelled 콜백이 OnCacelled 라고 두번이나! ^^;
감사합니다. ^^; 수정되었습니다.
슈퍼성근님.. 존경합니다.
대단하시네요...
과찬이십니다 ^^; 감사합니다.
"그리는 순서는 꼿 1,2,3 번 순서로 그려야 한다."
라는 매우 귀여운 오타를 발견했습니다 ㅋㅋㅋㅋㅋ
'꼿'이라는 글자가 왜이리 귀여워보일까요
읽는데 아무 지장이 없는 오타지만 꼿 이 눈에 딱 보여서 댓글남깁니다~~~
ps. 암기하듯이 알고있던부분을 원리를 이해하게 되었습니당 역시 슈퍼성근님!! 짱입니다乃
^^ 대단히 감사드립니다.
그냥 귀찮아서 지나칠 수 있지만, 이렇게 글을 남겨 주시는 것을 보면
카페를 매우 아껴 주시는 분임이 분명한 것 같습니다.
수정하였습니다. 다시 한번 감사드립니다. ^^b
thread 와 handler로 구성하는것보다
asynctask로 구성하는게 쉬워보이는데요
장단점이 있을까요?
asynctask 로만 구성해도 될듯한데요
참고로 제어플에서 thread 와 handler로 구성했더니 가끔 죽는경우가 많이 생겨서 당황스럽습니다.
안녕하세요. ^^
둘의 장단점을 제시할 수는 없습니다.
그 이유는 thread + handler 를 좀더 편하게 사용할 수 있도록 나온것이 AsyncTask이기 때문입니다.
따라서 개발하시는 프로젝트에 맞게 사용하시면 되겠네요.
단 AsyncTask는 UI Thread와 작업 Thread에서 처리해야 할 일들을
함수로 명확히 구분해 주기 때문에 개발자의 실수를 많이 줄어줍니다.
또한 사용도 편하죠.
AsyncTask 구조를 사용할 수 없는 경우가 아니라면
최대한 AsyncTask를 활용해 보세요.
@슈퍼성근 제가 얼핏 주워들은 얘기로는 AsyncTask 내부적으로는 Thread를 쓰는데 생성 갯수에 제한을 두고 있다고 들었습니다. 즉, 아무리 많은 AsyncTask를 생성해서 수행해도 내부적으로는 지정된 Thread개수를 초과하지 않게 수행이 되어 process overhead (소위, 죽는..)가 나는 경우를 방지한다고 들었습니다.
@juniano 좋은 정보 감사합니다. 덧붙입니다.
AsyncTask는 내부적으로 Thread pool을 가집니다.
쉽게 말하자면 동시에 스레드를 생성해서 돌리는 최대 개수가 있습니다.
공식은 아래와 같습니다.
단말기의 CPU 개수 * 2 + 1
즉 단말기에 CPU 개수가 4개라면 9개의 스레드가 동시에 돌아갑니다.
만일 초과하면 다른 스레드가 끝날때까지 대기하게 됩니다.
사실 Thread를 무작정 많이 생성하여 돌린다고 속도가 빨라지는 것이 아닙니다.
오히려 스레드 개수가 많으면 overhead가 생기죠.
따라서 스레드 개수는 적정선을 두고 돌리는 것이 이상적입니다.
그래서 위와 같은 스레드 생성 개수 제한 공식이 필요한 것입니다.
@슈퍼성근 하지만 사용자 입장에서는 내부 구조를 무시하시고 사용해도 됩니다.
내부적으로 적절히 스레드를 생성하고 동작시켜 주기 때문입니다.
여기서 오해할 수 있는 내용이 있는데요.
AsyncTask 하나는 Thread 하나입니다.
하나의 AsyncTask 객체가 여러개의 Thread를 생성하는 것이 아닙니다.
위에서 말한 것은 AsyncTask 객체를 100개 생성하고 동시에 돌릴때
각 객체만큼 Thread가 막 생기는게 아니라는 말입니다.
수고하세요.
@슈퍼성근 역시 대단하십니다. 사실 윗 답글을 적을 때 슈퍼성근님께 자세한 내용 알려주십사 부탁드리려 했는데 저도 주워들은 내용이라 제 답변 자체에 확신이 없어서 (혹시 틀린 내용일까봐서요 ㅎㅎ) 그 내용은 안 적었었습니다. 하지만 역시 기대를 저버리지 않으시는군요.. 감사합니다.