2010. 7. 22. 21:47

[번역] Painless threading



Painless threading

 

2009 6 6일 오전 9:30Romain Guy 에 의해 포스팅 됨

 

원문: http://android-developers.blogspot.com/2009/05/painless-threading.html

 

번역: SSKK (http://codemuri.tistory.com/)

 

처음 안드로이드 애플리케이션을 시작할 때마다, “main” 이라고 불리는 하나의 쓰레드가 자동으로 생성된다. UI 쓰레드라고도 불리는 Main 쓰레드는 그리는 이벤트를 포함하는 여러 이벤트들을 적절한 위젯으로 전송하는 역할을 담당하기 때문에 매우 중요하다. 이 쓰레드는 또한 안드로이드 위젯들과도 상호작용한다. 예를 들어, 만약 화면 위의 버튼을 터치한다면, UI 쓰레드는 터치 이벤트를 위젯으로 전달하고 이 위젯은 눌려진 상태(pressed state)로 설정되고 이벤트 큐에 무효화 요청(invalidate request)을 포스팅한다. UI 쓰레드는 그 요청을 큐에서 빼내어 자신을 다시 그려야 하는 위젯에 알린다.

 

단일 쓰레드 모델은 퍼포먼스가 빈약할 수 있다. 모든 것이 단일 쓰레드에서 일어나기 때문에, 네트워크 접근 또는 데이터베이스 질의 와 같은 오래 걸리는 연산을 그 쓰레드에서 수행하는 것은 모든 사용자 인터페이스를 멈추게 할 것이다. 오래 걸리는 작업이 처리되는 동안에는 그리기 이벤트를 포함한 어떤 이벤트도 처리될 수 없다. 사용자 측면에서, 애플리케이션이 정지된 것처럼 보일 것이다. 더욱 나쁜 것은, UI 쓰레드가 몇 초 이상 (현재는 약 5) 블러킹된다면, 사용자는 악명높은 애플리케이션 응답 없음” (ANR) 대화상자를 보게 될 것이다.

 

이것이 얼마나 나쁘게 보일 수 있는지를 직접 보길 원한다면, OnClickListener내에Thread.Seep(2000) 을 호출하는 버튼을 가지는 간단한 애플리케이션을 작성하라. 그 버튼은 일반 상태로 돌아가기 전에 약 2초 정도 눌려진 상태로 남게 될 것이다. 이러한 현상이 일어날 때, 사용자는 그 애플리케이션이 매우 느린 것처럼 여길 것이다.

 

자 이제 UI 쓰레드에서 오래 걸리는 작업을 피해야 한다는 것을 알았으므로, 당신은 아마 그러한 작업을 수행하기 위한 다른 쓰레드 (백그라운드 또는 작업 쓰레드)를 사용하려고 할 것이다. 네트워크크를 통해서 이미지를 다운 받고 그 이미지를 ImageView 에 보이도록 하는 클릭 리스너의 예를 보자.

 

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

 

먼저, 이 코드는 UI 쓰레드를 블러킹하지 않기 때문에 그 문제에 대한 좋은 해결책인 것처럼 보인다. 불행히도, 이것은 단일 쓰레드 모델을 위반한다: 안드로이드 UI 툴킷은 쓰레드에 안전하지 않고 항상 UI 쓰레드에서 조작되어야 한다. 이 코드에서, ImageView 는 작업 쓰레드에서 조작되고, 이것은 정말 기이한(weird) 문제를 야기할 수 있다. 이러한 버그를 찾고 수정하는 것은 어렵고 시간을 많이 낭비할 수 있다.

 

안드로이드는 다른 쓰레드에서 UI 쓰레드에 접근하기 위한 몇가지 방법을 제공한다. 당신은 이미 그 방법중의 일부를 알고 있을 수 있지만 여기 전체 리스트가 있다.

 

l  Activity.runOnUiThread(Runnable)

l  View.post(Runnable)

l  View.postDelayed(Runnable, long)

l  Handler

 

이 클래스와 메소드 모두 앞에서 소개된 코드 예제를 바로잡기 위해 사용될 수 있다.

 

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 업데이트를 필요로 하는 복잡한 연산을 구현할 때는 더욱 나빠진다. 이러한 문제를 해결하기 위해, Android 1.5 AsyncTask 라 불리는 새로운 유틸리티 클래스를 제공한다. 이 클래스를 이용하여 사용자 인터페이스와 상호작용할 필요가 있는 오랫동안 수행되는 태스크를 간단하게 생성할 수 있다.

 

AsyncTask 는 안드로이드 1.0 1.1 에서 UserTask 라는 이름으로 역시 사용가능하다. 이것은 완전히 동일한 API 를 제공하고 당신이 해야하는 모든 것은 위의 소스코드를 당신의 애플리케이션 내에 복사하는 것이다.

 

AsyncTask 의 목적은 당신을 위해 쓰레드 관리를 해주는 것이다. 앞서 소개된 예제는 AsyncTask 를 이용하여 쉽게 재작성될 수 있다.

 

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

private class DownloadImageTask extends AsyncTask {
     
protected Bitmap doInBackground(String... urls) {
         
return loadImageFromNetwork(urls[0]);
     
}

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

 

보다시피, AsyncTask 는 상속을 이용해야 한다. AsyncTask 인스턴스는 UI 쓰레드에서 생성되어야 하고 오직 한번만 실행될 수 있다는 것을 기억하는 것 역시 매우 중요하다. 이 클래스를 사용하는 방법에 대한 완전한 이해를 위해 AsyncTask 문서를 읽을 수 있지만, 아래에 AsyncTask 가 어떻게 동작 하는지에 대한 빠른 개요를 제공한다.

 

l  Generic 을 이용하여 파라미터, 프로그레스 값, 그리고 태스크의 최종 값의 타입을 지정할 수 있다.

l  doInBackground() 메소드는 작업 쓰레드에서 자동적으로 실행된다.

l  onPreExecute(), onPostExecute() 그리고 onProgressUpdate() 는 모두 UI 쓰레드에서 호출된다.

l  doInBackground() 에 의해 반환되는 값은 onPostExecute() 에 전달된다.

l  UI 쓰레드의 onProgressUpdate() 를 실행하도록 하기 위해 doInBackground() 에서 언제든지 publishProgress() 를 호출할 수 있다.

l  어떤 쓰레드에서든, 항상 그 태스크를 취소할 수 있다.

 

공식 문서에 덧붙여서, Shelves (Shelves.java AddBookActivity.java) 그리고 Photostream(LoginActivity.java, PhotostreamActivity.java 그리고 ViewPhotoActivity.java) 의 소소 코드에 있는 몇가지 복잡한 예를 참고할 수 있다. 형상 변경(configuration changes)시에도 태스크를 지속하는 방법과 액티비티가 소멸될 때 태스크를 올바르게 최소하는 방법을 알기 위해서는 Shelves 소스 코드를 참고할 것을 강력히 추천한다.

 

AsyncTask 를 사용하든 그렇지 않든간에, 단일 쓰레드 모델에 대한 두 가지 규칙만은 반드시 기억해야 한다: UI 쓰레드를 블러킹하지 말 것과 안드로이드 UI 툴킷은 오로지 UI 쓰레드에서만 접근되어야 한다. AsyncTask 는 단지 이러한 두 규칙을 보다 쉽게 지킬수 있게 한다.

 

보다 멋진 테크닉을 배우길 원한다면, Google I/O에 참석하라. 안드로이드 팀의 멤버가 깊이 있는 기술적인 세션의 시리즈를 제공하기 위해 그리고 당신의 모든 질문에 답변을 주기 위해 거기서 기다리고 있을 것이다.

 

.