본문 바로가기
개발/안드로이드

[번역] 안드로이드 - Painless Threading

by darksilber 2012. 4. 13.
반응형

출처 - http://huewu.blog.me/110082057101

Painless Threading


안드로이드 개발자 사이트 의 Technical Articles 중에 재미있는 글들이 많이 있네요. 그 중에서 제가 개인적으로 고생했고, 또 아직도 어려운 UI 어플리케이션에서 Worker 스레드를 효율 적으로 사용하는 방법에 관해 도움이 되는 글이 있어 번역해 봅니다.

안드로이드 어플리케이션에서 오랜 시간이 걸리는 작업을 수행 할 때, 메인 스레드 대신 별개의 스레드를 사용함으로서 어떻게 하면 최적의 UI 성능을 낼 수 있는지, 또 그러한 경우 별개의 스레드가 어떤 API 를 통해 메인 스레드에서 작동 중인 안드로이드 UI 툴킷 요소들 (대표적으로 버튼등)과 상호 작용 할 수 있는지 이야기 해보려고 한다.

The UI thread


어플리케이션이 시작될 때, 시스템은 해당 어플리케이션을 위해 메인 스레드를 생성하다. 이 메인 스레드는 UI 스레드라고도 불리우며, 'Drawing' 이벤트를 포함해서, 시스템 혹은 사용자의 상호 작용으로 인해 발생 하는 모든 이벤트를 UI 툴킷 요소들에게 전달 해준다.

예를들어, 사용자가 화면상의 버튼을 터치하면, UI 스레드는 해당 이벤트를 버튼에 전달하다. 버튼은 자신의 상태를 'Normal' 에서 'Pressed' 로 변환한 후, 자신의 상태가 변화 했음을 알리는 이벤트를 발생 시킨다. 이 후에, UI 스레드는 해당 이벤트의 내용을 확인 하고, 버튼을 다시 그린다 (버튼의 onDraw 를 호출한다).

만일 개발자가 어플리케이션을 적절하게 구현 하지 않는 다면, 하나의 UI 스레드 만을 사용하는 안드로이드 어플리케이션은 굉장히 느리게 동작 할 수 있다. 예를 들어 네트워크 접근과 같은 시간이 오래걸리는 작업이나 데이타 베이스 쿼리등을 수행할 때, UI 스레드에서 해당 작업을 수행 한다면, 작업이 완료되기 전 까지, UI 스레드는 사용자 입력에 대하여 응답을 하지 못하고 멈추게 된다. 만일 작업을 수행하는데 수 초 이상의 시간이 걸릴 경우 (현재는 5초) 어플리케이션 사용자는 유명한 ANR(Application Not Response - 어플리케이션 응답 없음) 오류창을 보게 될 것이다.

만일 이러한 일들이 사용자 경험을 얼마나 헤치는지 알고 싶다면, Activity 에 버튼을 하나 추가하고, 해당 버튼의 OnClickListener 에 Thread.sleep(2000) 구문을 추가한, 간단한 어플리케이션을 작성해 보아도 좋다. 어플리케이션을 실행한 후,
버튼을 눌러 보면, 약 2초간 화면이 멈추게 된다. 어플리케이션 사용자는 당장에 어플리케이션이 굉장히 느리게 작동한다고 생각할 것이다.

즉, 어플리케이션이 사용자 입력에 빠르게 응답할 수 있기 위해서는 UI 스레드가 블락되서는 않된다. 만일 오랜 시간이 걸리는 작업을 수행해야 한다면, 해당 작업은 UI 스레드가 아닌, 별도의 스레드에서 이루어져야한다.

버튼을 누르면, 네트워크 상에서 이미지를 가져와 화면에 표시해 주는 아래의 예제를 한번 살펴 보자.

public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork();
mImageView
.setImageBitmap(b);
}
}).start();
}
위 코드를 한번 슥 보면, UI 스레드를 블락 하지 않기 때문에. 잘 작동 할 것처럼 보인다. 하지만 불행하게도 중요한 규칙을 위반하고 있다. 안드로이드 UI 툴킷 요소들은 Thread-Safe 하지 않기 때문에, 항상 UI 스레드 내에서만 변경되거나 조작 되어야 한다. 위의 코드에서는 별도의 Worker 스레드에서 ImageView 의 setImageBitmap API 를 호출 하기 때문에 여러 가지 문제를 일으킬 수 있으며, 그럴 경우 원인을 파악하기 어렵고, 고치는데 시간이 많이 걸리곤 한다.

안드로이드는 별개의 스레드에서 UI 스레드에 접근 할 수 있도록, 몇 가지 방법을 제공한다.

어플리케이션 개발자는 앞서 예로 든 코드의 문제점을 해결하기 위해, 제시된 방법 중 무엇이든 사용할 수 있다.

public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap b = loadImageFromNetwork();
mImageView
.post(new Runnable() {
public void run() {
mImageView
.setImageBitmap(b);
}
});
}
}).start();
}

하지만, 이러한 클래스 혹은 메서드를 사용하는 건, 코드를 복잡하고 이해하기 어렵게 만드는 경향이 있다. 더군다나 어플리케이션 개발자가 오랜 시간이 걸리고, 빈번한 UI 업데이트를 요구하는 복잡한 작업을 수행하고자 한다면 문제는 더 심각해 질 수 있다.

이러한 문제를 해결하기 위해, 안드로이드 1.5 이후의 안드로이드 플랫폼에서는 AsyncTask 라는 유틸리티 클래스를 제공한다. AsyncTask는 오랫동안 유지 되며, 사용자 인터페이스와 상호작용 할 수 있는 테스크를 손 쉽게 만들 수 있도록 해준다.

AsyncTask 은 어플리케이션 개발자를 대신에 Worker 스레드를 관리 해준다. 이전 예제 코드는 AsyncTask 를 이용해서 아래와 같이 쉽게 수정 될 수 있다.

public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<string, void,="" bitmap=""> {
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}

protected void onPostExecute(Bitmap result) {
mImageView
.setImageBitmap(result);
}
}

위의 코드를 살펴보면 알 수 있듯이, AsyncTask 를 상속하여 원하는 작업을 수행 하는 Subclass 를 생성하여 사용하면 된다. AsyncTask 를 사용할 때, 추가로 몇 가지 알고 넘어가야 할 점이 있다. 첫째, AsyncTask 는 반드시 UI 스레드에서 생성 되어야 하며, 둘째, 딱 한 번만 실행 될 수 있다. (두번째 excute API 호출은 Exception 을 발생 합니다.) AsyncTask 에 대하여 보다 상세한 내용은 AsyncTask documentation 을 살펴 보면 좋지만, 간단하게 AsyncTask 가 어떤식으로 작동하는지 정리해 본다.

  • 개발자는 Generics 을 사용해, Parameter, Progress 정보, 결과 값의 자료 형을 임의로 정해서 사용할 수 있다.
  • 자동적으로, doInBackground() 는 UI 스레드가 아닌 별도의 스레드에서 실행 된다.
  • 반면에, onPreExcute() / onPostExcute() 그리고 onProgressUpdate() 는 모두 UI 스레드에서 호출 된다. (즉 마음대로 UI 툴킷들을 건들 수 있겠네요...)
  • doInBackground() 의 결과 값은 onPostExcute() 로 전달 된다.
  • 개발자는 doInBackgroud() 함수 내에서 언제든지 publishProgress() 를 호출 할 수 있다.
  • 개발자는 언제 어떤 스레드에서도 AsyncTask 작업을 취소 할 수 있다.

공식 문서들외에 추가로 참고할 만한 몇 가지 예제들이 있다. 그 중에서도 Sheleves 코드를 볼 것을 강력히 추천한다. 해당 코드를 잘 살펴 보면, Activity 의 상태가 변화해서 다시 시작하거나 하는 경우에도, 어떻게 지속적으로 AsyncTask 를 유지 하는지, 또 Activity 가 종료될 때에는 어떻게 모든 AsyncTask 를 적합하게 취소 하는지 알 수 있을 것이다.

어플리케이션 개발자는, AsyncTask 를 사용하던, 그렇지 않던 단일 스레드 모델에 관해, 다음의 두가지 규칙을 꼭 명심해야 한다.

1. 절대로 UI 스레드가 블락 되게 하지 말아라,
2. 오직 UI 스레드 에서만 UI 툴킷으 접근해라.

AsyncTask 는 단지 이 두 가지 일을 쉽게 해주기 위한 것이다.
반응형

댓글