------------------------------------------------------------------------------------------------
출처: http://huewu.blog.me/110090363656
------------------------------------------------------------------------------------------------
ListView 에 인터넷에 비동기적으로 이미지를 다운로드해서 표시하는 간단하지만 유용한 예제를
통해 안드로이드 어플리케이션에서 멀티 스레딩 작업을 수행할 때 고려해야할 여러가지 사항을
잘 정리해 주었습니다. 한번 찬찬히 살펴보시면 여러가지로 도움을 받을 수 있으실거 같네요.
(개인적으로는 말미에 SoftReference 를 이용하여 Cache 를 만드는 부분이 가장 도움 되었습니다;;;)
그는 멀티 태스킹을 사랑하는 안드로이드 엔지니어 입니다. — Tim Bray
이러한 좋은 예가 바로 네트워크 작업입니다. 네트워크 작업은 어느정도의 시간이
걸릴지 예측하기 힘들기 때문에, 조심스럽게 처리하지 않으면 사용자들은 잠깐 잠깐씩 어플리케이션이 버벅거린다고 느끼거나, 어떤 경우에는 아예 멈추어버린 것 처럼
느낄 수도 있습니다.
이미지를 다운로드 하는 간단한 코드를 작성해볼 것 입니다.
이를 바탕으로, 인터넷에서 다운로드한 이미지 썸네일을 표시해주는 ListView 를 생성한 후, 어플리케이션은 정상적으로 빠르게 동작하는 가운데, 백그라운드에서 비동기적으로 이미지 다운로드 작업을 수행하도록 구현할 것입니다.
static Bitmap downloadBitmap(String url) {
final AndroidHttpClient client = AndroidHttpClient.newInstance("Android");
final HttpGet getRequest = new HttpGet(url);
try {
HttpResponse response = client.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.w("ImageDownloader", "Error " + statusCode
+ " while retrieving bitmap from " + url);
return null;
}
final HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = null;
try {
inputStream = entity.getContent();
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
} finally {
if (inputStream != null) {
inputStream.close();
}
entity.consumeContent();
}
}
} catch (Exception e) {
// Could provide a more explicit error message for IOException
// or IllegalStateException
getRequest.abort();
Log.w("ImageDownloader", "Error while retrieving bitmap from "
+ url, e.toString());
} finally {
if (client != null) {
client.close();
}
}
return null;
}
해당 Stream 을 바로 디코딩하여 Bitmap 을 생성할 수 있습니다.
물론, 이러한 일이 가능하기 위해서는 우선적으로 어플리케이션이 INTERNET 퍼미션을 갖고 있어야 합니다.
디코딩을 수행하시는 편이 좋습니다. 이 클래스는 스트림이 끝나지 않은 한,
skip() 메서드가 실재로 전달받은 바이트 수 만큼을 건너띄도록 구현되어있습니다.
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
@Override
public long skip(long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int byte = read();
if (byte < 0) {
break; // we reached EOF
} else {
bytesSkipped = 1; // we read one byte
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}
새로운 View 를 그릴 때 마다 이미지가 다운로드 하기 위해 어플리케이션이 멈추고,
스크롤이 부드럽게 이루어지지 않습니다.
실행하면, "This thread forbids HTTP request(이 스레드는 HTTP Request 를 허용하지 않습니다.)" 라는 에러가 발생합니다. 여러분이 반드시 메인 스레드 내에서 HTTP Request 를 요청하고자 한다면, DefaultHttpClient 클래스를 사용하셔야 합니다.
public class ImageDownloader {
public void download(String url, ImageView imageView) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
task.execute(url);
}
}
/* class BitmapDownloaderTask, see below */
}
class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private final WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
// Actual download method, run in the task thread
protected Bitmap doInBackground(String... params) {
// params comes from the execute() call: params[0] is the url.
return downloadBitmap(params[0]);
}
@Override
// once the image is downloaded, associates it to the imageView
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
스레드가 맞을거 같네요)에서 수행됩니다. 위 코드에서 donInBackground 메서드는
단순히 앞서 구현한 downloadBitmap 메서드를 호출하도록 작성되었습니다.
이 메서드는 인자로 넘겨받은 Bitmap 을 미리 지정된 ImageView 에 설정합니다.
ImageView 가 WeakReference 로 저장되어 있는 것을 주의깊게 살펴보세요.
만일 BitmapDownloaderTask 가 ImageView 의 실재 참조를 갖고 있을 경우, ImageView 와 연관된 Activity 가 종료된 후에도, GC 가 해당 ImageView 를 제거할 수 없습니다.
WeakReference 를 사용했기 때문에 onPostExecute 메서드 내에서 ImageView 를
사용하기 전에 WeakReference 값이 null 인지, 그리고 실재 ImageView 가 null 인지
이중으로 확인해 보아야 합니다.
만일 여러분이 이러한 기법을 실재로 시도해본다면, 단지 몇 줄의 코드만으로 ListView 가 부드럽게 스크롤 될 만큼 성능이 향상 됨을 확인 할 수 있을 것 입니다.
AstncTask 에 관해 보다 자세히 알고 싶으시다면, Painless Threading 문서를 살펴보세요.
또, 매번 ImageView 가 화면에 그려질 때면, 해당 ImageView 에 적합한 이미지를
다운로드 하기 위한 BitmapDownloaderTask 가 정확히 실행 됩니다.
이런 경우 어떤 문제가 발생할까요? 대부분의 패러럴 어플리케이션 처럼, 가장 중요한 이슈는 바로 '순서' 에 있습니다. 우리의 경우, 이미지 다운로드 작업이 시작된 순서대로 종료된다는 보장이 없습니다.
결과적으로 특정 이미지를 다운로드 하는데 오랜 시간이 걸릴 경우, 훨씬 앞에 표시되어야할 이미지가 가장 마지막에 표시될 수도 있습니다. 만일 각각의 ImageView 에
이미지가 한 번씩만 설정된다면 별 문제 아닐 수가 있지만, 이는 일반적인 해결책이라고 할 수 없습니다. 이 문제를 한 번 해결해 봅시다.
다운로드 된 이미지만을 기억하도록 구현하면 됩니다. 다운로드가 진행 중인 동안
임시로 ImageView 에 바인드 되는 커스텀한 Drawable 클래스를 이용하여 ImageView 에 부가 정보를 추가하겠습니다. 추가된 DownloadedDrawable 클래스의 코드는 다음과
같습니다.
static class DownloadedDrawable extends ColorDrawable {
private final WeakReference<BitmapDownloaderTask>
bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
super(Color.BLACK);
bitmapDownloaderTaskReference =
new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
}
public BitmapDownloaderTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference.get();
}
}
또한, 오브젝트의 의존성을 제한하기 위하여 WeakReference 를 사용한 것을 다시 한번 유의깊게 살펴보시기 바랍니다.
public void download(String url, ImageView imageView) {
if (cancelPotentialDownload(url, imageView)) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
task.execute(url, cookie);
}
}
현재 ImageView 와 연관된 이미지 다운로드 작업을 정지 시키는 역할을 수행합니다. 하지만, 이것만으로 가장 마지막에 다운로드를 시작한 이미지가 화면에 표시된다고
보장할 수는 없습니다. 왜냐하면 이전에 진행 중이던 다운로드 작업이 이미 완료되어 onPostExecute 메서드가 실행 중인 상황인 경우, 때에 따라서 onPostExecute 메서드가 새로운 다운로드 작업이 모두 완료되고 해당 다운로드의 onPostExecute 메서드가 호출된 이 후에 실행 될 수도 있기 때문입니다.
private static boolean cancelPotentialDownload(String url, ImageView imageView) {
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(
imageView);
if (bitmapDownloaderTask != null) {
String bitmapUrl = bitmapDownloaderTask.url;
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
bitmapDownloaderTask.cancel(true);
} else {
// The same URL is already being downloaded.
return false;
}
}
return true;
}
한가지 주의할 점이 있습니다. 현재의 구현대로라면 만일 ImageView 가 가비지 콜랙팅된 경우에도 이와 연관된 다운로드 작업이 중단되지 않습니다. 이를 처리하기 위해서는 RecyclerListener 를 사용할 수 있을거 같네요.
이 함수는 다음과 같이 간단하게 구현되어있습니다.
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView)
{
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable
;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null;
}
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(
imageView);
// Change bitmap only if this process is still associated with it
if (this == bitmapDownloaderTask) {
imageView.setImageBitmap(bitmap);
}
}
수정되었습니다. 이 클래스 혹은 여기에 사용된 비동기 패턴을 여러분의 어플리케이션의 반응성을 향상시키는데 자유롭게 사용하시기 바랍니다.
여러분은 이 포스트에서 서술한 세 가지 방법 (AsyncTask 를 사용하지 않은 버전,
Bitmap 설정과 관련된 추가 작업을 수행하지 않는 버전, 최종 버전)을 선택하여
각각의 구현을 비교해 볼 수 있습니다. 관련된 문제를 보다 잘 보여주기 위해
10개의 이미지 만을 캐쉬 하도록 제한되어 있습니다.
있으며, 여러가지 유용한 기능이 제외되어 있습니다. ImageDownloader 클래스는
동일한 이미지를 여러번 다운로드 받지 않도록 캐쉬를 이용하여 성능이 크게 개선될 수 있습니다. (ListView 와 함께 사용되는 경우라면 더욱 그렇습니다.)
이미지의 URL 을 키 값으로, Bitamp 의 SoftReference 를 밸류 값을 갖는 LinkedHashMap 을 이용하여 LRU (Least Recently Used) 방식의 캐쉬를 손쉽게 구현할 수 있습니다.
물론, 로컬 저장 장치에 이미지를 저장하는 방식으로 캐쉬 메카니즘을 구현할 수도
있으며, 이미지 리사이징이나 썸네일을 만드는 기능도 필요한 경우 추가될 수 있습니다.
구현할 수도 있을 것 입니다.
수행할 수 있는 편리하고 쉬운 방법입니다. 여러분이 여러분이 수행하는 일을 보다
정교하게 제어하고자 하는 경우에는 (동시에 수행되는 다운로드 스레드의 수를
제한하는 등) Handler 클래스를 사용할 수 있습니다.
[출처] [번역] 안드로이드 멀티 스레드를 통한 성능 향상|작성자 휴우
'IT_Programming > Android_Java' 카테고리의 다른 글
Activity & Service & Fragment & View Lifecycle (0) | 2011.08.17 |
---|---|
[펌] 안드로이드 구글맵 구현 시 주의사항 (0) | 2011.08.17 |
[Ant & Android] Ant를 이용한 Android Build (0) | 2011.08.09 |
Parcelable을 사용한 오브젝트 전달 (Object serialization using Parcelable) (0) | 2011.08.02 |
[android] 애니메이션 - 비밀번호창 좌우로 7번 흔들기 (0) | 2011.08.01 |