주요글: 도커 시작하기
반응형

특정 URL 이미지를 가져와 ImageView에 보여주는 기능을 구현해보자. 먼저 이 기능을 구현하는데에는 아래 글에서 구현했던 기능들이 재사용된다. 그러니, 코드 중간에 설명되지 않는 내용들은 아래 글들을 읽어보면 이해가 될 것이다.

클래스 구성


URL로부터 이미지를 다운받아 ImaveView에 출력하는 기능을 구현하기 위한 클래스는 다음과 같다.

  • ImageDownloadAsyncCallback
  • ImageDownloader
    • 기본 이미지를 ImageView에 반영 후, AsyncExecutor를 이용해서 이미지 다운로드를 실행

ImageDownloadAsyncCallback 클래스


ImageDownloadAsyncCallback 클래스는 AsyncExecutor에 전달되는 콜백 객체를 구현하고 있다.


public class ImageDownloadAsyncCallback implements AsyncCallback<File>,

AsyncExecutorAware<File> {


private String url;

private WeakReference<ImageView> imageViewReference;

private AsyncExecutor<File> asyncExecutor;

private ImageCache imageCache;


public ImageDownloadAsyncCallback(String url, ImageView imageView,

ImageCache imageCache) {

this.url = url;

this.imageViewReference = new WeakReference<ImageView>(imageView);

this.imageCache = imageCache;

}


@Override

public void setAsyncExecutor(AsyncExecutor<File> asyncExecutor) {

this.asyncExecutor = asyncExecutor;

}


public boolean isSameUrl(String url2) {

return url.equals(url2);

}


@Override

public void onResult(File bitmapFile) {

Bitmap bitmap = addBitmapToCache(bitmapFile);

applyBitmapToImageView(bitmap);

}


private Bitmap addBitmapToCache(File bitmap) {

imageCache.addBitmap(url, bitmap);

return imageCache.getBitmap(url);

}


private void applyBitmapToImageView(Bitmap bitmap) {

ImageView imageView = imageViewReference.get();

if (imageView != null) {

if (isSameCallback(imageView)) {

imageView.setImageBitmap(bitmap);

imageView.setTag(null);

}

}

}


private boolean isSameCallback(ImageView imageView) {

return this == imageView.getTag();

}


public void cancel(boolean b) {

asyncExecutor.cancel(true);

}


@Override

public void exceptionOccured(Exception e) {

}


@Override

public void cancelled() {

}


}


AsyncExecutor가 파일 다운로드를 완료하면 onResult() 메서드가 호출되는데, onResult() 메서드는 전달받은 이미지 파일을 캐시에 추가하고 ImageView에 반영한다.


applyBitmapToImageView() 메서드는

  • imageViewReference로부터 ImageView를 구한다.
  • imageView가 존재한다면,
    • imageView의 콜백 객체가 this와 동일한지 확인한다.
      • 참고로, 뒤에서 살펴볼 ImageDownloader는 이미지 다운로드가 시작되면, ImageView의 tag에 관련된 콜백 객체를 설정한다.
    • 동일하다면, imageView에 이미지를 반영한다.
    • 동일하지 않다면, 이 콜백이 생성된 뒤에 동일한 ImageView에 대해 다른 URL 요청이 있었다는 것이므로 이미지를 imageView에 반영하지 않는다.

ImageDownloader 클래스


ImageDownloader 클래스의 코드 중 앞 부분은 다음과 같다.


// ImageDownloader의 앞 부분


public class ImageDownloader {

static final String TAG = "ImageDownloader";


private Context context;

private ImageCache imageCache;


public ImageDownloader(Context context, String imageCacheName) {

this.context = context;

imageCache = ImageCacheFactory.getInstance().get(imageCacheName);

}


public void download(String url, ImageView imageView, Drawable noImageDrawable) {

Bitmap bitmap = imageCache.getBitmap(url);

if (bitmap == null) {

forceDownload(url, imageView, noImageDrawable);

} else {

cancelPotentialDownload(url, imageView);

imageView.setImageBitmap(bitmap);

}

}


private void forceDownload(String url, ImageView imageView, Drawable noImageDrawable) {

if (!cancelPotentialDownload(url, imageView))

return;


imageView.setImageDrawable(noImageDrawable);

runAsyncImageDownloading(url, imageView);

}


private boolean cancelPotentialDownload(String url, ImageView imageView) {

ImageDownloadAsyncCallback asyncCallback =

(ImageDownloadAsyncCallback) imageView.getTag();

if (asyncCallback == null)

return true;

if (asyncCallback.isSameUrl(url))

return false;

asyncCallback.cancel(true);

return true;

}


ImageDownloader는 '안드로이드 이미지 캐시 구현' 글에서 만들었던 ImageCache를 이용해서 다운로드 받은 이미지 파일을 캐시하는데, 생성자를 통해서 사용할 캐시 이름을 전달받는다.


download() 메서드는 다음과 같이 세 개의 파라미터를 받는다.

  • download(String url, ImageView imageView, Drawable noImageDrawable)

첫 번째 파라미터는 이미지 URL이고, 두 번째 파라미터는 이미지를 보여줄 ImageView이다. 그리고 세 번째 noImageDrawable은 이미지를 다운로드 받는 동안에 imageView에 보여줄 이미지이다.


download 메서드는

  • 캐시에 이미지가 존재하는 지 확인한다.
  • 캐시에 존재하지 않으면 이미지 다운로드를 실행한다. (forceDownload)
  • 캐시에 존재하면 
    • cancelPotentialDownload()를 호출해서 ImageView에 대해 다른 URL을 다운로드 중이라면 작업을 취소하고,
    • 캐시에서 읽어온 이미지를 imageView에 반영한다.

forceDownload() 메서드는

  • cancelPotentialDownload() 메서드가 false를 리턴한 경우 바로 리턴한다.
    • 이 메서드가 false를 리턴하면, 같은 URL에 대해 이미 다운로드가 진행중이라는 것이다.
  • cancelPotentialDownload()가 true를 리턴한 경우
    • imageView의 이미지로 noImageDrawble를 설정한 뒤에
    • runAsyncImageDownloading()을 실행해서 이미지 다운로드를 시작한다.

cancelPotentialDownload() 메서드는 한 개의 ImageView에 대해 서로 다른 이미지를 로딩하는 것을 방지하기 위한 코드이다. 예를 들어, ListView에서 사용되는 ImageView는 재사용될 가능성이 높은데 이 경우 시간 간격을 두고 ImageView에 서로 다른 URL 이미지 다운로드가 실행될 수 있다. 이 경우 앞서 요청한 이미지는 다운로드 할 필요가 없기 때문에 다운로드를 취소함으로써 불필요한 네트워크 사용을 방지할 수 있을 것이다. cancelPotentialDownload() 메서드가 바로 이 취소작업을 처리한다.


뒤에서 살펴볼 runAsyncImageDownloading() 메서드는 이미지 다운로드를 시작하기 전에 ImageView의 tag 값으로  ImageDownloadAsyncCallback 객체를 보관하는데, cancelPottentialDownlad() 메서드는 이 tag 객체를 이용해서 ImageView와 관련된 이미지 다운로드가 진행중인 것이 있는지 확인한다. 


cancelPotentialDownload() 메서드는

  • imageView.getTag()를 이용해서 ImageDownloadAsyncCallback 객체를 구한다.
  • imageView의 tag가 null이면 ImageView와 관련된 다운로드가 진행중이지 않은 것이므로 true를 리턴한다.
  • tag에 보관된 콜백 객체의 url이 새롭게 요청한 url과 같으면 이미 동일 URL에 대한 다운로드가 진행중인 것이므로 false를 리턴한다.
  • 콜백 객체의 url이 요청 url과 다르면, 다른 이미지를 다운로드하고 있는 것이므로 작업 취소를 요청하고 false를 리턴한다.

실제로 이미지 다운로드를 시작하는 runAsyncImageDownloading() 메서드 코드 및 관련 메서드를 아래 코드에 표시하였다.


// ImageDownloader의 뒷 부분


private void runAsyncImageDownloading(String url, ImageView imageView) {

File tempFile = createTemporaryFile();

if (tempFile == null)

return;


Callable<File> callable = new FileDownloadCallable(url, tempFile);

ImageDownloadAsyncCallback callback = new ImageDownloadAsyncCallback(

url, imageView, imageCache);

imageView.setTag(callback);


new AsyncExecutor<File>().setCallable(callable).setCallback(callback).execute();

}


private File createTemporaryFile() {

try {

return File.createTempFile("image", ".tmp", context.getCacheDir());

} catch (IOException e) {

Log.e(TAG, "fail to create temp file", e);

return null;

}

}


}


runAsyncImageDownloading() 메서드는

  • 다운로드 받은 내용을 저장할 임시 파일을 생성한다. (createTemporaryFile())
  • 파일 다운로드 작업을 실행하는 FileDownloadCallable 객체를 생성한다. (이 클래스는 '안드로이드에서 URL로 파일 다운 받아 로컬에 저장하기' 글에서 작성한 것이다.)
  • 비동기 파일 다운로드가 완료될 때 사용될 ImageDownloadAsyncCallback 객체를 생성하고, ImageView의 tag 값으로 콜백 객체를 설정한다.
  • AsyncExecutor를 이용해서 비동기 파일 다운로드를 시작한다.


ImageDownloader 클래스 사용


ImageDownloader 클래스의 사용 방법은 다음과 같이 간단하다.


-- 이미지 캐시를 사용하기 때문에, onCreate() 같은 곳에서 파일캐시/이미지캐시 초기화 필요

// 2레벨 캐시(이미지 파일 캐시)를 사용하려면 동일 이름의 파일 캐시를 생성해 주어야 한다.

FileCacheFactory.getInstance().create("imagecache", cacheSize);

// 이미지 캐시 초기화

ImageCacheFactory.getInstance().createTwoLevelCache("imagecache", 40);



-- ImageDownloader가 필요한 코드에서 사용, 예를 들면 LiveView의 Adapter


public class FeaturedToonAdapter extends BaseAdapter {


private ImageDownloader imageDownloader;


public FeaturedToonAdapter(Context context) {

...

this.imageDownloader = new ImageDownloader(context, "imagecache");

}


@Override

public View getView(int position, View convertView, ViewGroup parent) {

View row = convertView;

...

BitmapDrawable noImage = new BitmapDrawable(context.getResources(), 

BitmapFactory.decodeResource(context.getResources(), R.drawable.noimage))

imageDownloader.download(imageUrl, imageView, noImage);

return row;

}



관련자료




+ Recent posts