전 포스트에서 설명했던 여러 스레드 구현방법들은 비록 아무 문제가 없지만 구현방법이 복잡해서 코드를 읽기 힘들게 만드는 경향이 있었다. Background작업에 관한 모든 사항(스레드 객체 생성, 사용, UI스레드와 통신 등)이 Activity 코드에 포함 되고 특히 background 스레드가 UI위젯과 빈번한 통신을 할수록 Activity 코드의 복잡함은 점점 배가 된다.
안드로이드에서는 이런 문제를 해결하기 위해 API level 3 (1.5 version) 부터 AsyncTask라는 클래스를 제공하고 있다.
AsyncTask클래스는 background작업을 위한 모든 일(스레드생성, 작업실행, UI와 통신 등)을 추상화 함으로 각각의 background작업을 객체 단위로 구현/관리 할 수 있게 하는것이 목적이다. 그림으로 표현하면 다음과 같다.
참고로 1.0과 1.1 version의 API를 사용하는 디바이스에서는 구글 code에 공개되어 있는 UserTask 라는 클래스를 어플리케이션 프로젝트에 복사해 넣어 사용할 수 있다. 기능과 사용법은 AsyncTask와 완전히 동일하다.
그럼 AsyncTask에 관해 자세히 살펴보자.
1. AsyncTask 클래스 소개
AsyncTask라는 클래스 이름은 Asynchronous Task의 줄임이며, UI스레드의 입장에서 볼 때 비동기적으로 작업이 수행되기 때문에 붙여진 이름이다. (Ajax: Asynchronous javascript and XML 에서 사용된 의미와 같다)
AsyncTask의 상속관계는 다음과 같다.
Object로부터 상속하는 AsyncTask는 Generic Class이기 때문에 사용하고자 하는 type을 지정해야 한다.
AsyncTask클래스의 사용시 지정해야 하는 generic type은 각각 다음의 용도로 사용된다.
- Params: background작업 시 필요한 data의 type 지정
- Progress: background 작업 중 진행상황을 표현하는데 사용되는 data를 위한 type 지정
- Result: background 작업 완료 후 리턴 할 data 의 type 지정
그림으로 각 generic type이 결정하는 것들을 표현하면 다음과 같다.
(각 메소드의 자세한 정보는 다음 단락의 예제 코드와 설명 참조)
만약 type을 정할 필요가 없는 generic이 있다면 void를 전달하면 된다.
예. …AsyncTask<void, void, void> {…}
2. AsyncTask의 사용
우선 AsyncTask가 어떻게 사용되는지 예제 소스를 보자.
AsyncTask 클래스의 사용 예
접기
001 | package com.holim.test; |
003 | import android.app.Activity; |
004 | import android.os.AsyncTask; |
005 | import android.os.Bundle; |
006 | import android.os.SystemClock; |
007 | import android.view.View; |
008 | import android.widget.Button; |
009 | import android.widget.ProgressBar; |
010 | import android.widget.TextView; |
012 | public class AsyncTaskDemo extends Activity |
013 | implements View.OnClickListener { |
015 | ProgressBar progressBar; |
017 | Button btnExecuteTask; |
019 | /** Called when the activity is first created. */ |
021 | public void onCreate(Bundle savedInstanceState) { |
022 | super .onCreate(savedInstanceState); |
023 | setContentView(R.layout.main); |
025 | progressBar = (ProgressBar)findViewById(R.id.progressBar); |
026 | textResult = (TextView)findViewById(R.id.textResult); |
027 | btnExecuteTask = (Button)findViewById(R.id.btnExecuteTask); |
029 | btnExecuteTask.setOnClickListener( this ); |
032 | public void onClick(View v) { |
037 | new DoComplecatedJob().execute( "987" , |
051 | private class DoComplecatedJob extends AsyncTask<String, Integer, Long> { |
058 | protected void onPreExecute() { |
059 | textResult.setText( "Background 작업 시작 " ); |
060 | super .onPreExecute(); |
065 | protected Long doInBackground(String... strData) { |
066 | long totalTimeSpent = 0 ; |
069 | int numberOfParams = strData.length; |
072 | for ( int i= 0 ; i<numberOfParams; i++) { |
076 | SystemClock.sleep( new Integer(strData[i])); |
079 | totalTimeSpent += new Long(strData[i]); |
083 | publishProgress(( int )(((i+ 1 )/( float )numberOfParams)* 100 )); |
085 | return totalTimeSpent; |
094 | protected void onProgressUpdate(Integer... progress) { |
095 | progressBar.setProgress(progress[ 0 ]); |
102 | protected void onPostExecute(Long result) { |
103 | textResult.setText( "Background 작업에 걸린 총 시간: " |
104 | + new Long(result).toString() |
112 | protected void onCancelled() { |
접기
AsyncTask 클래스는 다음과 같이 중요한 callback들을 제공 함으로 상황에 맞게 오버라이딩 해야 한다.
- protected void onPreExecute(): Background 작업이 시작되자마자 UI스레드에서 실행될 코드를 구현해야 함. (예. background 작업의 시작을 알리는 text표현, background 작업을 위한 ProgressBar popup등)
- protected abstract Result doInBackground(Params… params): Background에서 수행할 작업을 구현해야 함. execute(…) 메소드에 입력된 인자들을 전달 받음.
- void onProgressUpdate(Progress... values): publishProgress(…) 메소드 호출의 callback으로 UI스레드에서 보여지는 background 작업 진행 상황을 update하도록 구현함. (예. ProgressBar 증가 등)
- void onPostExecute(Result result): doInBackground(…)가 리턴하는 값을 바탕으로 UI스레드에 background 작업 결과를 표현하도록 구현 함. (예. background작업을 계산한 복잡한 산술식에 대한 답을 UI 위젯에 표현함 등)
- void onCancelled(): AsyncTask:cancel(Boolean) 메소드를 사용해 AsyncTask인스턴스의 background작업을 정지 또는 실행금지 시켰을 때 실행되는 callback. background작업의 실행정지에 따른 리소스복구/정리 등이 구현될 수 있다.
또, AsyncTask 클래스는 background 작업의 시작과 background 작업 중 진행정보의 UI스레드 표현을 위해 다음과 같은 메소드를 제공한다.
- final AsyncTask<…> execute(Params… params): Background 작업을 시작한다. 꼭 UI스레드에서 호출하여야 함. 가변인자를 받아들임으로 임의의 개수의 인자를 전달할 수 있으며, 인자들은 doInBackground(…) 메소드로 전달된다.
- final void publishProgress(Progress... values): Background 작업 수행 중 작업의 진행도를 UI 스레드에 전달 함. doInBackground(…)메소드 내부에서만 호출.
위의 메소드들은 AsyncTask 클래스를 이용해 구현된 background 작업 시 다음과 같은 형태로 사용된다.
위 의 그림에서 처럼AsyncTask인스턴스는 자기 자신을 pending, running, finished 이렇게 세 가지 상태(status)로 구분하는데 각각 AsyncTask:Status 클래스에 상수 PENDING, RUNNING, FINISHED로 표현 될 수 있다.
현재 AsyncTask인스턴스의 상태는 다음 메소드를 호출해서 얻을 수 있다.
public final AsyncTask.Status getStatus ()
return
AsyncTask인스턴스의 상태정보를 AsyncTask.Status 객체의 상수 값 PENDING, RUNNING, FINISHED 중에서 리턴.
또, AsyncTask클래스는 background 작업을 정지, 또는 시작금지 시키기 위해 다음 메소드를 제공한다. 이 메소드가 성공적으로 호출되면 onCacelled() callback이 호출되니 onCacelled()에 적절한 뒤처리를 해주어야 한다.
final boolean cancel (boolean mayInterruptIfRunning)
parameter
mayInterruptIfRunning: true값을 제공했을 때 background작업이 실행 중일 경우(running 상태) 작업을 중단 시키고, 준비 중(pending 상태) 일 경우 작업을 실행 금지 시킴. (execute() 명령 사용 불가. 사용하면 exception 발생)
return
true: background작업을 성공적으로 중지하거나 실행 금지 시킴
false: 벌써 작업이 완료된 상태(finished 상태) 일 경우 리턴
마지막으로 AsyncTask 사용해 background작업을 구현 시 꼭 지켜야 하는 사항들이다.
- AsyncTask클래스는 항상 subclassing 하여 사용하여야 한다.
- AsyncTask 인스턴스는 항상 UI 스레드에서 생성한다.
- AsyncTask:execute(…) 메소드는 항상 UI 스레드에서 호출한다.
- AsyncTask:execute(…) 메소드는 생성된 AsyncTask 인스턴스 별로 꼭 한번만 사용 가능하다. 같은 인스턴스가 또 execute(…)를 실행하면 exception이 발생하며, 이는 AsyncTask:cancel(…) 메소드에 의해 작업완료 되기 전 취소된 AsyncTask 인스턴스라도 마찬가지이다. 그럼으로 background 작업이 필요할 때마다 new 연산자를 이용해 해당 작업에 대한 AsyncTask 인스턴스를 새로 생성해야 한다.
- AsyncTask의 callback 함수 onPreExecute(), doInBackground(…), onProgressUpdate(…), onPostExecute(…)는 직접 호출 하면 안 된다. (꼭 callback으로만 사용)