AsyncTask
AsyncTask는 백그라운드에서 실행할 작업과 완료 후 UI 변경 작업이 필요할 때, 메서드 구현만으로 각각의 작업 스레드를 신경쓰지 않고 구현할 수 있도록 하기 위해 만들어진 클래스이다. 내부적으로 Handler의 sendMessage() 메서드를 통해 Message를 전달하여 handleMessage() 메서드에서 UI를 갱신하는 방식으로 구현되어있다.
1. 백그라운드 스레드와 UI 스레드를 구분해서 사용하는 방법
Handler의 sendMessage() 메서드를 통해 Message를 전달하고, handleMessage()에서 UI 작업
private final static int BITMAP_MSG = 1;
// 메인 스레드에서 생성한 Handler는 메인 Looper를 사용한다.
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if(msg.what == BITMAP_MSG) {
imageView.setImageBitmap((Bitmap) msg.obj);
}
}
}
public void onClick(View v) {
// 백그라운드 스레드 생성
new Thread(new Runnable() {
@Override
public void run() {
final Bitmap bitmap = loadImageFromNetwork("http://yeseul94.tistory.com/image.png");
// Message를 오브젝트 풀에서 가져오는 법: Message.obtain() 또는 handler.obtainMessage()
Message message = Message.obtain(handler, BITMAP_MSG, bitmap);
handler.sendMessage(message);
}
}).start();
}
Handler의 post() 메서드를 통해 전달하는 Runnable에서 UI 작업
private Handler handler = new Handler(); // 메인 Looper 사용하는 Handler
public void onClick(View v) {
// 백그라운드 스레드 생성
new Thread(new Runnable() {
@Override
public void run() {
final Bitmap bitmap = loadImageFromNetwork("http://yeseul94.tistory.com/image.png");
handler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
}).start();
}
View의 post() 메서드에 Runnable 전달
public void onClick(View v) {
// 백그라운드 스레드 생성
new Thread(new Runnable() {
@Override
public void run() {
final Bitmap bitmap = loadImageFromNetwork("http://yeseul94.tistory.com/image.png");
// 내부적으로 메인 Looper에서 돌아가는 Handler를 사용한다.
imageView.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
}).start();
}
Activity의 runOnUiThread() 메서드도 동일하게 사용하면 된다.
2. AsyncTask 예제
public void onClick(View v) {
new DownloadImageTask().execute("http://yeseul94.tistory.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
@Override
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
@Override
protected void onPostExecute(Bitmap result) {
imageView.setImageBitmap(result);
}
}
종종 스레드를 다루는데 익숙하지 않은 개발자들이 API에 너무 의존해서 단순히 백그라운드 스레드를 실행하면 되는 작업에도 AsyncTask를 사용하는 경우가 있다. onPostExecute() 메서드를 구현할 필요가 없는 경우에는 AsyncTask를 쓰지 않고 백그라운드를 생성해서 실행하는 것이 낫다. 아래와 같이 백그라운드 스레드와 UI 스레드를 구분하기 위한 용도로만 쓰이는 경우에도 Handler를 사용하는 것이 더 단순할 수 있다.
private class MyTask extends AsyncTask<Void, Void, Void> { ... }
3. 메모리 이슈
액티비티에서 AsyncTask를 실행한 후 작업중에 액티비티를 종료할 경우 메모리에는 액티비티가 남아서 메모리 누수 문제가 생길 수 있다. 오래걸리는 작업이 아니라면 큰 문제는 아니다. 하지만 화면 회전으로 인해 계속해서 새로 생성되는 액티비티마다 AsyncTask가 쌓인다면 OOM의 원인이 될 수 있다. 메모리 누수 문제를 해결할 수 있는 방법은 두 가지가 있다.
AsyncTask를 상속한 정적 내부 클래스(Static Inner Class)의 생성자에 Activitiy를 전달
Activity가 종료될 때 AsyncTask를 함께 종료시켜서 메모리 해제
Activity에서 AsyncTask의 인스턴스를 가지고 onDestroy() 메서드에서 AsyncTask의 cancel() 메서드를 호출한다.
4. Fragment에서 AsyncTask 실행 이슈
Fragment에서 AsyncTask를 실행한 후 작업중에 Fragment가 붙어있는 Activity가 종료되는 경우 onPostExecute()에서 getContext() 혹은 getActivity()를 통해 Context를 사용할 때 NPE가 발생한다. 해결방법은 onPostExecute() 메서드 첫 시작부분에 getContext() 혹은 getActivity()가 null을 반환한다면 바로 리턴하는 것이다.
5. AsyncTask의 예외처리
AsyncTask는 기본적으로 예외를 처리하기 위한 메서드를 따로 지원하지 않기 때문에 직접 구현해야한다. 예외 발생시 토스트 메시지 등으로 사용자에게 알려야하는 경우가 많으므로 UI 작업이 필요하다. 일반적으로 많이 사용되는 방법은 doInBackground()에서 null을 반환하고 onPostExecute() 처음에 Result가 null이면 에러 메시지를 띄우는 방법이다. doInBackground()에서 결과는 멤버 변수에 따로 저장하고, Boolean.TRUE, Boolean.FALSE를 리턴하여 정상인 경우와 예외가 발생한 경우를 구분하는 것이다.
6. AsyncTask 병렬 실행
AsyncTask를 병렬로 실행하려면 executeParallel() 메서드를 통해 실행하면된다. 병렬로 실행하면서도 순서가 중요한 경우에는 일반적으로 CountDownLatch를 사용한다. CountDownLatch를 사용하면 먼저 시작한 AsyncTask의 작업이 끝날 때까지 대기했다가 그 이후 결과를 보여줄 수 있다.
'Android > 안드로이드 프로그래밍 Next Step' 카테고리의 다른 글
안드로이드 프로그래밍 Next Step - 5.1 액티비티 생명주기 (0) | 2019.03.29 |
---|---|
안드로이드 프로그래밍 Next Step - 3.1 HandlerThread (0) | 2019.03.28 |
안드로이드 프로그래밍 Next Step - 2.2 ANR(Application Not Responding) (0) | 2019.03.25 |
안드로이드 프로그래밍 Next Step - 2.1 메인 스레드와 Handler (0) | 2019.03.24 |
댓글