출처 : https://androidyongyong.tistory.com/15?category=672654
학습 목표
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는 간단하기 때문에 많이 쓰이는 비동기 기술이다. 그러나 몇가지 고려할 사항이 필요하다.