백그라운드 스레드에서 작업하는 진행 상태나 결과 데이터를 UI스레드에 전달하고, 백그라운드 스레드와 UI스레드를 고민하지 않고 구분해서 쓸 수 있도록 만들어진 것 (UI작업이 필요하지 않으면 쓰지 않는게 낫다)
백그라운드 스레드에서 UI를 변경하는 방식 3
1) Handler이용 (2가지 방식)
private final static int BITMAP_MSG = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == BITMAP_MSG) {
// 3. message받으면 UI 작업 실행
mImageView.setImageBitmap((Bitmap) msg.obj);
}
}
};
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
// 1. 백그라운드에서 이미지 가져오고
final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
Message message = Message.obtain(mHanlder, BITMAP_MSG, bitmap);
mHandler.sendMessage(message); // 2. sendMessage()로 Message 보내고
}
}).start();
}
private Handler mHandler = new Handler();
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
// 1. 백그라운드에서 이미지 가져오고
final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
Message message = Message.obtain(mHanlder, BITMAP_MSG, bitmap);
// 2. post()를 사용하여 Runnable에서 UI작업 실행
mHandler.post(new Runnable() {
@Override
public void run() {
mImageView.setImageBitmap(bitmap)
}
});
}
}).start();
}
2) View의 post() 메서드에 Runnable 전달
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
// 1. 백그라운드에서 이미지 가져오고
final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
Message message = Message.obtain(mHanlder, BITMAP_MSG, bitmap);
// 2. view의 post()를 사용하여 Runnable에서 UI작업 실행
mImageView.post(new Runnable() {
@Override
public void run() {
mImageView.setImageBitmap(bitmap)
}
});
}
}).start();
}
3) AsyncTask 이용
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
@Override
protected Bitmap doInBackground(String... urls) { // 데이터 가져오고
return loadImageFromNetwork(urls[0]);
}
@Override
protected void onPostExecute(Bitmap result) { // 뷰 변경
mImageView.setImageBitmap(result);
}
}
내부적으로 handler 이용한 첫 번째 방식으로 되어 있다.
액티비티 종료 시점과 불일치
- 메모리 문제 발생 가능 : 화면 회전하면 액티비티는 종료되고 새로 시작하는데, 이때 이전에 생성됐던 AsyncTask가 실행중인경우 기존 액티비티에서도 메모리에서 제거되지 않고 쌓인다. -> OOM 원인
- 순차 실행으로 인한 속도 저하 : 화면 회전 마다 작업이 쌓이므로 실행이 느려질 수 있음
- Fragment에서 AsyncTask 실행 문제 : Fragment에서 AsyncTask 실행 도중에 백키로 액티비티 종료시 context null 체크를 반드시 해야함
AsyncTask 취소
AsyncTask 를 액티비티 종료 시점에 근접하게 종료하는 방법은 isCancelled() 리턴값을 doInBackground() 곳곳에 체크하고, AsyncTask 를 멤버 변수로 유지하고 Activity의 onDestroy()에서 AsyncTask의 cancel 메서드를 호출
* mayInterruptIfRunning 파라미터
cancel(boolean mayInterruptIfRunning)의 파라미터는 doInBackground() 를 실행하는 스레드에 interrupt()를 실행할지 여부 체크
예외 처리 메서드 없음
백그라운드 스레드에서 하는 작업은 네트워크 문제와 같은 다양한 예외 케이스가 있는데, 이때 처리하기 위한 메소드가 없음
대책방안
1. AsyncTask의 기본 패턴 변형 : doInBackground에서 예외가 발생하면 Boolean.FALSE 리턴
2. 대안으로 RxJava 사용
public void onClick(View v) {
// 1. 다운로드 결과를 Observable 형태로 리턴
Observable<Bitmap> observable = loadImageFromNetwork("http://example.com/image.png");
observable.subscribeOn(Schedulers.io()) // 2. loadImageFromNetwork()가 실행되는 스레드를 정한다
.observeOn(AndroidSchedulers.mainThread()) // 3. Observable 메서드가 실행되는 스레드 정한다
.subscribe(new Observer<Bitmap>() {
// 4. AsyncTask의 onPostExecute() 메서드와 역할 비슷
@Override
public void onNext(Bitmap bitmap) {
mImageView.setImageBitmap(bitmap);
}
// 5. 예외 처리
@Override
public void onError(Throwable e) {
// 화면에 에러 메시지를 보여준다.
}
@Override
public void onCompleted() {
Log.d(TAG, "onCompleted");
}
});
}
병렬 실행시 doInBackground() 실행 순서가 보장되지 않음
안드로이드 버전이 올라가면서 AsyncTask를 실행할 때 기본 동작이 '병렬 실행'에서 '순차 실행' 으로 바뀌었다. 제어 가능하다고 자신하는 경우에만 '병렬 실행'을 쓰라고 하는데 이때 순서가 보장되지 않음.
=> CountDownLatch로 실행 순서 조정
private ArrayList<String> composedList = new ArrayList<>();
// 2. CountDownLatch 생성. 파라미터 숫자는 횟수.
private CountDownLatch latch = new CountDownLatch(1);
private class AsyncTaskA extends AsyncTask<Void, Void, List<String> {
@Override
protected List<String> doInBackground(Void... params) {
...
return Arrays.asList("spring", "summer", "fall", "winter");
}
@Override
protected void onPostExecute(List<String> result) {
try {
composedList.addAll(result);
title.setText(TextUtils.join(", ", composedList);
} catch (Exception e) {
Toast.makeText(SomeActivity.this, "Error=" +e.getMessage(), Toast.LENGTH_LONG).show()
} finally {
// countDown 실행하면 파라미터의 값이 1씩 줄어드는데 0되면 대기 상태가 풀림
latch.countDown();
}
}
}
private class AsyncTaskB extends AsyncTask<Void, Void, List<String> {
@Override
protected List<String> doInBackground(Void... params) {
try {
...
return Arrays.asList("east", "south", "west", "north");
} catch (Exception e) {
Log.e(TAG, "exception = "+e.getMessage());
} finally {
try {
// 대기하고 있다가 conutDown되면 대기 상태를 풀고 다음 작업을 진행
latch.await();
} catch (InterruptedException e) {}
}
}
@Override
protected void onPostExecute(List<String> result) {
if(result != null) {
composedList.addAll(result);
title.setText(TeztUtils.join(", ", composedList));
}
}
public void onClick(View view) {
// 1. AsyncTaskA, AsyncTaskB 병렬로 시작
AsyncTaskCompat.executeParallel(new AsyncTaskA());
AsyncTaskCompat.executeParallel(new AsyncTaskB());
}
}
CASE 1 : AsyncTaskA가 먼저 실행될 경우
- spring, summer, fall, winter 표시되고 countDown호출되므로 대기 해제하고, AsyncTaskB에서는 대기없이 east, south, west, north 표시
CASE 2 : AsyncTaskB가 먼저 실행될 경우
- AsyncTaskB에서 await()호출로 대기하고, AsyncTaskA에서 spring, summer, fall, winter 표시후 대기해제됨으로써 AsyncTaskB에 east, south, west, north도 표시
'Android > 안드로이드 프로그래밍 Next Step' 카테고리의 다른 글
[Next Step] 5. 액티비티 생명주기 (0) | 2023.08.27 |
---|---|
[Next Step] 4. Context 클래스 (0) | 2023.08.25 |
[Next Step] 3. 스레드 풀 사용 (0) | 2023.08.12 |
[Next Step] 3. HandlerThread 클래스 (0) | 2023.08.09 |
[Next Step] 1장 안드로이드 프래임워크 - 안드로이드 버전 (0) | 2023.06.17 |