티스토리 뷰

학습 목표

android에서의 스레드 사용의 주요 목적은 오래 걸리는 작업을 UI스레드에서 제거하는 것이다. 그 테스크를 백그라운드 테스크를 만들고 UI스레드와 통신하는 방법이 필요한데, 이런 작업들을 쉽게 하는것이 android AsyncTask를 이용하면 좋다.

오늘은 AsyncTask 클래스에 대해 상세히 알아보고, AsyncTask가 백그라운드 테스크 실행을 부드럽게 처리하는 방법과 조심해야 할 위험성에 대해 살펴본다.


10.1 기본 사항

AsyncTask : 백그라운드 스레드에서 실행되는 비동기 태스크다. doInbackground()를 통해 task가 실행된다.

public class MinimalTask extends AsyncTask {
    @Override
    protected Object doInBackground(Object... objects) {
        // 백그라운드 스레드를 실행하는 테스크 구현
    }
}

실행은 execute 메서드를 호출함으로써 실행된다.

new MinimalTask().execute(Object... objects);


AsyncTask 실행이 완료되면 다시 실행할 수 없다. --> 일회성 동작!


스레드 결과값을 UI 스레드로 보낼 때 AsyncTask가 유용하다. 즉, UI 스레드 준비, 테스크 실행, 태스크 실행 상황 보고, 결과 반환 까지 모든 흐름을 처리할 수 있다.

AsyncTask의 서브클래스에 대한 추가적인 콜백 메서드를 이용해 사용될 수 있다.

public class FullTask extends AsyncTask<Params, Progress, Result>

{ @Override protected void onPreExecute() { ... } @Override protected Result doInBackground(Params ... params) { ... } @Override protected void onProgressUpdate(Progress ... progress) { ... } @Override protected void onPostExectue(Result result) { ... } @Override protected void onCancelled(Result result) { ... } }


Params : 백그라운드에서 실행되는 테스크로 입력되는 데이터


Progress : 백그라운드 스레드의 doInBackground에서 UI 스레드의 onProgressUpdate 로 보고되는 진행 데이터


Result : 백그라운드 스레드에서 만들어 UI 스레드로 보내진 결과


모든 콜백 메서드는 순차적으로 실행한다.

onPreExecute() -> doInBackground() -> onProgressUpdate(Progress) -> onPostExecute(Result) -> onCancelled(Result)


10.1.1 생성과 시작

AsyncTask 구현 시 기본 생성자는 UI 스레드에서 호출되어야 함.

execute 시 시작이 되는데, UI스레드에서 호출해야한다. 그리고 두 번 이상 호출되면 IllegalStateException이 발생한다.


10.1.2 취소

사용자가 마음이 바뀌거나 화면에서 실행하던 응용프로그램을 백그라운드로 놓는 경우 UI스레드가 AsyncTask의 결과를 사용하지 않기로 하면 UI스레드는 종료 요청을 cancel(boolean) 호출을 통해 보낼 수 있다


// 태스크 시작

AsyncTask task = new MyAsyncTask().execute(/* 생략 */);

// 태스크 취소

task.cancel(true);


cancel 이 불리면 취소 정책을 doInBackground에 작성하여 작업을 바로 취소시킬 수 있다. (isCancelled() 로 체크)

public class InttruptionTask extends AsyncTask {
    @Override
    protected Void doInBackground(String ...s) {
        try {
            while(!isCancelled()) {
                doLongInterruptibleOperation(s[0]);
            }
        } catch (InterruptedException iex) {
            // 아무것도 않하고 종료
       }
       return null;
     }
}

10.1.3 상태

AsyncTask는 순서대로 다음과 같은 상태를 갖는다.


Pending : AsyncTask가 생성되었지만, execute가 호출되지 않음

Running : execute가 호출 됨. onPostExecute가 실행되기 전까지 실행

Finished : onPostExecute 또는 onCancelled 를 통해 최종 작업이 실행 됨.


Running상태가 되면 새로운 실행을 시작하는 것은 불가능 하고, 매 실행마다 새로운 AsyncTask 인스턴스가 생성되어야 한다.


10.2 AsyncTask 구현

구현 시 고려할 사항은 다음과 같다.


메모리 누수 피하기

작업자 스레드가 살아 있는 한 작업자 스레드의 모든 참조된 객체는 메모리에 유지되므로 AsyncTask를 독립형이나 정적 내부 클래스로 선언해야 한다.


Context와 생명주기 연결

AsyncTask는 종종 Context를 참조하는 UI 스레드로 업데이트를 발행한다. 정적 내부 클래스로 선언하면 Context 참조가 쉽다. 또한 참조가 더 이상 필요하지 않을 땐, null로 설정하여 참조를 제거한다.


취소 정책

취소가 필요 시 태스크를 중단하거나 취소시킬 수 있다.


10.2.1 이미지 다운로드

public static class DownloadImageTask extends AsyncTask {
        ImageView bmImage;
        ProgressDialog progressDialog;
        Context context;

        public DownloadImageTask(ImageView bmImage, Context context) {
            this.bmImage = bmImage;
            this.context = context;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            progressDialog = new ProgressDialog(context);
            progressDialog.setMessage("Loading image...");
            progressDialog.show();
        }

        protected Void doInBackground(String... urls) {
            String urldisplay = urls[0];
            Bitmap mIcon11 = null;
            Bitmap result = null;
            try {
                InputStream in = new java.net.URL(urldisplay).openStream();
                mIcon11 = BitmapFactory.decodeStream(in);
                result = Utils.getclip(mIcon11);
                publishProgress(result);
            } catch (Exception e) {
                Log.e("Error", e.getMessage());
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Bitmap... values) {
            super.onProgressUpdate(values);
            if(context != null) {
                bmImage.setImageBitmap(values[0]);
            }
        }

        protected void onPostExecute(Void avoid) {
            super.onPostExecute(avoid);
            progressDialog.dismiss();
        }
    }

10.3 백그라운드 태스크 실행

AsyncTask는 태스크를 비동기적으로 실행하므로, 여러개의 태스크가 순차적 또는 동시에 실행 될 수 있다. 응용프로그램에서 명시적으로 정의하지 않으면 플랫폼에서 암시적으로 설정된다.


execute(Params..) : 모든 플랫폼 버전에 사용될 수 있는 메서드, 순차 또는 동시 실행


execute(Runnable) : doInBackground 실행 대신 Runnable 태스크 처리, onPreExecute, onPostExecute, onCancelled는 호출되지 않는다.


executeOnExecutor(Executor, Params...) : 커스텀 Executor를 사용

Executor 인수는 다음 중 하나이다.

AsyncTask.THREAD_POOL_EXECUTOR : 태스크들은 스레드 풀에서 동시 처리

AsyncTask.SERIAL_EXECUTOR : 안전한 순차적 태스크 스케줄러


10.3.1 응용 프로그램의 전역 실행

AsyncTask 인스턴스는 프로그램 전체의 전역 실행 속성을 공유한다.

따라서 많은 AsyncTask를 생성해서 동시에 실행해도 태스크는 순차적으로 실행한다.

AsyncTask 인스턴스는 전역 실행을 공유하기 때문에 실행 환경에 따라 인스턴스 끼리 서로 영향을 줄 수 있다.


순차 실행(SERIAL_EXECUTOR) : 응용 프로그램에서 모든 선행 태스크가 처리될 때까지 작업자 스레드에서 처리되지 않는다. execute() 혹은 executeOnExecutor(AsyncTask.SERIAL_EXECUTOR) 에서 적용된다.


동시 실행(THREAD_POOL_EXECUTOR) : 쿼드코어 기준으로 5개의 AsyncTask를 동시에 처리할 수 있다.


10.3.2 다양한 플랫폼 버전에서 실행

플랫폼 버전에 따라 동시 실행이 가능하거나, 순차 실행만 하는지 결정된다.


API 13 이후부터는 execute()는 순차실행, executeOnExecutor() 는 순차/동시 커스터마이징 가능하다.


10.3.3 커스텀 실행

AsyncTask에 미리 정의 된 실행자 유형(SERIAL_EXECUTOR, THREAD_POOL_EXECUTOR)은 응용 프로그램 전역에 걸처 적용되므로, 많은 태스크를 실행할 때는 성능 저하의 위험이 있으므로, 전역 실행을 피하기 위해서는 커스텀 Executor로 처리해야 한다.


new AsyncTask().executeOnExecutor(Params, MyCustomExecutor);


10.4 AsyncTask의 대안

AsyncTask는 간단하기 때문에 많이 쓰이는 비동기 기술이다. 그러나 몇가지 고려할 사항이 필요하다.

  • AsyncTask는 전역 실행 환경을 가지므로, 더 많은 태스크를 실행할수록 태스크가 예상대로 처리되지 못할 위험이 있다.
  • 플랫폼 버전마다 실행 환경이 다르기 때문에, 성능을 위한 실행(동시 실행)이나 스레드 안전(순차 실행)을 최적화하기가 어렵다.
10.4.1 AsyncTask가 너무 평범하게 구현된 경우
매개변수 없이 태스크를 실행(AsyncTask<Void, Void, Void>) 
UI 스레드와 백그라운드 스레드 간에 데이터를 전달할 수 없다.

오직 doInBackground 메서드만 구현
단순한 백르아운드 태스크다.

이런 경우는 Thread 또는 HandlerThread를 대신 사용하자.

10.4.2 루퍼가 필요한 백그라운드 스레드
AsyncTask에서 백그라운드 태스크를 실행하는 작업자 스레드는 관련된 루퍼 또는 메시지 큐를 가지고 있지 않다. 따라서 메시지 전달이 불가능 하다.

루퍼를 원하면 AsyncTask 대신 HandlerThread를 사용하라.

10.4.3 지역 서비스
지역서비스는 오래걸리는 작업의 실행을 처리하기 위해 응용프로그램에서 다른 구성 요소와 병렬로 실행된다. 서비스는 UI스레드 안에서 실행되고, 오래걸리는 작업은 백그라운드 스레드로 작업해야 하는데, 테스크 전역 실행 프로그램은 다른 구성요소가 동시에 실행 환경을 활용하고 서로 간섭할 수 있어야 하므로, Thread, Executor, HandlerThread, 커스텀 실행자를 가진 AsyncTask를 사용하자.

10.4.4 execute(Runnable)  사용
태스크를 Runnable 인스턴스로 실행하면 AsyncTask의 주요 자점이 제거된다. 작업중인 큐에 Runnable을 집어 넣은 뒤 스레드 풀에서 유휴 스레드가 사용 가능할 때, Runnable을 실행한다.
UI 스레드는 콜백을 받을 수 없으니 Thread와 Executor 프레임워크를 사용하자.

10.5 마치며
AsyncTask를 사용해야하는 필요성을 반드시 이해하고, 무분별한 사용은 피하는 것이 좋다.
즉, 루퍼를 사용하지 않고, UI가 실행 전, 실행 중, 실행 후의 업데이트 되어야 하는 액티비티 안의 백그라운드 태스크 실행이 필요할 경우 안성맞춤이다.


댓글
댓글쓰기 폼
공지사항
최근에 달린 댓글
Total
49,188
Today
6
Yesterday
129
TAG
more
«   2020/06   »
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30        
글 보관함