woong's

Android Universal Image Loader 사용하기 본문

Develop/Android

Android Universal Image Loader 사용하기

dlsdnd345 2016. 2. 13. 23:19
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

안드로이드 이미지를 비동기 처리해서 다운 받아 올때 
안드로이드 google 에서 만든 ImageDownloader 를 사용했습니다 .

근데 이미지 로딩 라이브러리를 보고 ImageDownloader 가 많이 부족한 점이 있다는 것을 알았습니다 .

그래서 이미지 로딩 라이브러리 참고 및 사용 방법에 대해서 말씀 드리겠습니다.

해당 경로를 통해서 들어가면 NHN 에서 이미지 다운로드 라이브러리에 대한 비교 및 장단점이 있습니다 .

이미지 로딩 과정의 숙제들

네트워크를 통한 이미지 로딩을 구현할 때에는 여러 가지 과제를 해결해야 한다.

불안한 HTTP 클라이언트 실행 환경

원본 이미지는 대부분 HTTP 클라이언트 라이브러리를 사용해서 읽어 온다. Android의 HTTP 통신은 라이브러리나 네트워크 환경에서 불안정한 요소가 많기 때문에 이를 충분히 대비해야 한다.

무엇보다 HTTP 클라이언트 라이브러리의 버그를 버전에 따라 대처해야 안정적으로 동작한다. 이에 관해서는 "Android의 HTTP 클라이언트 라이브러리"(http://helloworld.naver.com/helloworld/377316)에서 이미 다루었다.

재시도 처리, 실패 처리도 필요하다. 서버에서 서버로 API를 호출할 때보다 불안정한 네트워크 환경을 이용할 때가 많다. 재시도 횟수와 시도 간격을 깊이 고민해야 한다.

불필요해진 호출은 빠른 시점에 취소해야 한다. 화면 회전이나 이동으로 이미 요청된 호출이 의미가 없어졌는데도 이를 끝까지 수행한다면 메모리, 성능, 배터리가 낭비된다. 특히 네트워크 상태가 좋지 않을 때 요청이 오랫동안 대기한다면 더욱 낭비가 크다. 네트워크 호출을 어떻게 취소할지는 병렬 처리 부분에서 추가로 논의하겠다.

메모리가 넘치거나 새기 쉬운 비트맵 디코딩

네트워크로 이미지를 잘 읽어왔어도 메모리를 다루면서 빠지기 쉬운 함정이 많다.

비트맵의 크기가 클 때는 Out of Memory 에러가 발생하지 않도록 유의해야 한다. 이미지 파일은 디코딩을 거쳐야 화면에 출력된다. 비트맵이 차지하는 메모리의 용량은 이미지의 크기에 비례한다. Galaxy Nexus에서 촬영한 이미지의 크기는 2592x1936픽셀인데, 이는 비트맵으로는 약 19MB(2592 x 1936 x 4바이트)이다.[1] 가용 메모리가 16MB인 기기에서 이 비트맵을 메모리에 올리면 Out of Memory 에러가 발생한다. 따라서 작은 크기로 변환하거나 품질을 낮추어서 디코딩해야 한다. Android는 BitmapFactory.Options 클래스로 그런 기능을 제공한다.

Android 버전에 따라서는 비트맵 자원을 명시적으로 해제해야 한다. Android 3.1(HONEYCOMB_MR1) 이상은 비트맵을 VM의 힙 메모리에 저장하지만, Android 2.3(Gingerbread) 이하 버전은 비트맵을 네이티브 힙에 저장한다. 예전 버전에서 비트맵은 CG의 대상이 되지 않아 Bitmap 객체의 recycle() 메서드를 호출하여 직접 메모리를 해제해야 한다. 아니면 BitmapFactory.Options의 inPurgeable 플래그를 true로 설정한다.

AsyncTask 클래스만으로는 충분하지 않은 병렬 처리

네트워크 호출과 디코딩 처리 등 대기 시간이 긴 작업은 백그라운드 스레드에서 수행되어야 한다. 그래서 이미지 로딩에서 비동기 처리, 병렬 처리는 필수이다. 이런 작업에는 AsyncTask가 많이 쓰이지만 이 클래스는 버전에 따라 다르게 동작하고 요청 취소는 작업의 특성에 맞게 구현해야 하는 등 고려할 요소가 많다.

여러 AsyncTask가 실행될 때 버전에 따라서 병렬로 실행되기도 하고 직렬로 실행되기도 한다. AsyncTask의 주석에 따르면 Android 1.5(Cupcake)에서는 직렬, Android 1.6(Donut)부터는 병렬로, Android 3.2(HONEYCOMB_MR2) 이상에서는 다시 직렬로 실행된다. AsyncTask의 소스 코드를 보면 버전별로 스레드 풀 관련 상수의 값이 다르다.

이미지 캐시와 View 재활용의 어려움

이미지가 들어간 화면을 만들 때 이미지 캐시와 View를 재활용하지 않는다면 앱은 느리게 반응하고 자원을 많이 소모한다. 따라서 이미지 캐시와 View 재활용이 필수적인데 이를 정교하게 구현하려면 많은 코드가 들어가고, 매번 구현하기도 번거롭다.

정리

요약하면, 이미지 로딩을 구현할 때는 HTTP 통신을 안정되게 구현하고, 비트맵으로 디코딩하면서 메모리가 넘치거나 새지 않도록 주의해야 한다. 네트워크 호출과 디코딩은 단순히 백그라운드 스레드에서 동작하는 것만으로는 충분하지 않고 더 적극적으로 병렬성을 활용해야 한다. 화면 회전, 전환, 스크롤 때 반복적인 요청이 가지 않도록 이미지를 캐시하고, 불필요해진 요청은 빠른 시점에 취소해서 더 나은 UI 반응을 제공하면서 자원을 절약해야 한다. 이 과제들을 모두 해결하려다 보면 처리 흐름은 복잡해지고, 비슷한 코드가 반복되기 쉽다.


이미지 워크 플로 









Android Universal Image Loader

위 라이브러리를 검토 결과 가장 기능을 잘 지원해주면서 용량이 가벼운 것이 
Android Universal Image Loader 였습니다 .

Android Universal Image Loader 는

Android Universal Image Loader(이하 AUIL)는 많은 앱에 적용되어 있고, 화면 크기를 기준으로 캐시 용량을 제한하는 등 다양한 캐시 정책을 지원한다. Executor, 스레드 풀 크기, , Bitmap Options 등 변경할 수 있는 옵션이 많습니다.


위 경로를 통해 접근하면 Android Universal Image Loader 
사용 방법 및 설명에 대해 나타나 있습니다 .

Android Universal Image Loader 특징

다중 스레드 이미지 로딩
이미지 디코드
메모리 캐시 
디스크 캐시
이미지 디스플레이 옵션 설정등
분리 된 옵션이있는 모든 디스플레이 이미지 호출을 사용자 정의 할 수있는 가능성
위젯 지원

Android Universal Image Loader 준비

https://github.com/nostra13/Android-Universal-Image-Loader

위 경로를 통해 jar 파일을 다운로드 합니다 .





다운 받은 jar 파일을 프로젝트 lib 폴더에 넣습니다.



Android Universal Image Loader 사용


1
2
3
4
5
6
7
8
9
<manifest>
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- Include next permission if you want to allow UIL to cache images on SD card -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
    <application android:name="MyApplication">
        ...
    </application>
</manifest>


위와 같이 퍼미션을 등록 합니다 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this)
                .threadPriority(Thread.NORM_PRIORITY - 2)
                .denyCacheImageMultipleSizesInMemory()
                .discCacheFileNameGenerator(new Md5FileNameGenerator())
                .tasksProcessingOrder(QueueProcessingType.LIFO)
                .writeDebugLogs() // Remove for release app
                .build();
 
        ImageLoader.getInstance().init(config);
    }

이미지 로딩을 사용할 액티비티 onCreate ImageLoaderConfiguration 설정합니다 .


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
        .memoryCacheExtraOptions(480, 800) // default = device screen dimensions
        .discCacheExtraOptions(480, 800, CompressFormat.JPEG, 75, null)
        .taskExecutor(...)
        .taskExecutorForCachedImages(...)
        .threadPoolSize(3) // default
        .threadPriority(Thread.NORM_PRIORITY - 1) // default
        .tasksProcessingOrder(QueueProcessingType.FIFO) // default
        .denyCacheImageMultipleSizesInMemory()
        .memoryCache(new LruMemoryCache(2 * 1024 * 1024))
        .memoryCacheSize(2 * 1024 * 1024)
        .memoryCacheSizePercentage(13) // default
        .discCache(new UnlimitedDiscCache(cacheDir)) // default
        .discCacheSize(50 * 1024 * 1024)
        .discCacheFileCount(100)
        .discCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
        .imageDownloader(new BaseImageDownloader(context)) // default
        .imageDecoder(new BaseImageDecoder()) // default
        .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
        .writeDebugLogs()
        .build();
 

ImageLoaderConfiguration 모든 설정입니다 . 기호에 따라서 필요한 부분을 추가하여 작성하면 되겠습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
 
DisplayImageOptions options = new DisplayImageOptions.Builder()
                .showImageOnLoading(R.drawable.img_kakao) // 로딩중에 나타나는 이미지
                .showImageForEmptyUri(R.drawable.img_ing) // 값이 없을때 
                .showImageOnFail(R.drawable.img_end) // 에러 났을때 나타나는 이미지
                .cacheInMemory(true)
                .cacheOnDisc(true)
                .considerExifParams(true)
                .build();
 
        ImageLoadingListener animateFirstListener = new AnimateFirstDisplayListener();
 
        ImageLoader.getInstance().displayImage(arSrc.get(position).getImg(), addBookHolder.bookImage, options, animateFirstListener);

DisplayImageOptions 설정과 이미지 로딩 사용 방법 입니다 . 보여질 이미지에 대한 설정을 하고 이미지를 받아오도록 사용하는 코득가  되겠습니다 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DisplayImageOptions options = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.ic_stub) // resource or drawable
        .showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable
        .showImageOnFail(R.drawable.ic_error) // resource or drawable
        .resetViewBeforeLoading(false)  // default
        .delayBeforeLoading(1000)
        .cacheInMemory(false// default
        .cacheOnDisc(false// default
        .preProcessor(...)
        .postProcessor(...)
        .extraForDownloader(...)
        .considerExifParams(false// default
        .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default
        .bitmapConfig(Bitmap.Config.ARGB_8888) // default
        .decodingOptions(...)
        .displayer(new SimpleBitmapDisplayer()) // default
        .handler(new Handler()) // default
        .build();
 

DisplayImageOptions 의 모든 기능 입니다 기호에 따라서 골라 사용 하면 되겠습니다 .
이미지 다운로딩 라이브러리를 통해서 사용가능한 url 입니다 .
 
1
2
3
4
5
String imageUri = "http://site.com/image.png"// from Web
String imageUri = "file:///mnt/sdcard/image.png"// from SD card
String imageUri = "content://media/external/audio/albumart/13"// from content provider
String imageUri = "assets://image.png"// from assets
String imageUri = "drawable://" + R.drawable.image; // from drawables (only images, non-9patch)


sdcard 에 있는 주소만 넣어주면 로컬에 있는 이미지도 불러올수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Load image, decode it to Bitmap and display Bitmap in ImageView (or any other view 
//  which implements ImageAware interface)
imageLoader.displayImage(imageUri, imageView, displayOptions, new ImageLoadingListener() {
    @Override
    public void onLoadingStarted(String imageUri, View view) {
        ...
    }
    @Override
    public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
        ...
    }
    @Override
    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
        ...
    }
    @Override
    public void onLoadingCancelled(String imageUri, View view) {
        ...
    }
});


 
마지막으로 이미지를 받는 과정에서 생기는 이벤트를 캐치 할수 있는 메서드를 제공해 줍니다.

'Develop > Android' 카테고리의 다른 글

Google Drive 준비 하기  (0) 2016.02.13
Android ORM 소개 & ORMLite 사용하기  (1) 2016.02.13
Jsoup 사용하기  (0) 2016.02.13
Android Gson 사용하기  (0) 2016.02.13
Kakao App 연동 & 링크 하기  (0) 2016.02.13
Comments