본문 바로가기
안드로이드/Android

[Android] Image Loader Library

by jinwo_o 2024. 11. 11.

Android Memory

  • 안드로이드는 앱 내에서 사용할 수 있는 힙 메모리가 정해져 있기 때문에 이미지 로더 라이브러리를 사용하지 않은 상태에서 많은 이미지를 사용하거나 고해상도 이미지를 이미지뷰에 로드해야 하는 경우 메모리 부족으로 OOM 이 발생하게 된다.
    • 즉, 안드로이드 애플리케이션은 프로세스당 메모리 사용량에 제한이 있다.

 

Honeycomb 미만

  • Dalvik heap 영역 : Java 객체를 저장하는 메모리
  • External 영역 : Native heap 의 일종으로, 네이티브의 비트맵 객체를 저장하는 메모리
  • Dalvik heap 영역과 External 영역은 각각 프로세스당 메모리 한계까지 확장될 수 있다.
  • Dalvik heap 영역과 External 영역의 Dalvik heap footprint 와 External limit 을 합쳐 프로세스 당 메모리 한계를 초과하면 OOM 이 발생하게 된다.
  • External limit 는 External allocated 와 일정한 간격을 유지하면서 줄어들거나 늘어나지만, Dalvik heap footprint 는 한 번 증가하면 크기가 다시 감소하지 않는다.

 

Honeycomb 이상

  • External heap 을 없애고 모든 메모리를 Dalvik Heap 내에서 관리하도록 변경했다.

 

footprint

  • Dalvik VM 은 처음에 동작에 필요한 만큼만 프로세스에 heap 을 할당하게 되고, 프로세스에 할당된 메모리보다 많은 메모리를 필요하게 될 때마다 Dalvit footprint 도 증가하게 된다.
    • footprint 는 한 번 증가하면 크기가 다시 감소하지 않기 때문에 Java 객체가 사용 가능한 메모리 공간의 여유가 있어도, External heap 의 크기가 증가하면 OOM 이 발생할 수 있다.
      • 이러한 문제로 Honeycomb 이후부터는 Dalvik heap 과 External 영역이 합쳐졌기 때문에 고려할 필요가 없어졌다.
  • External 영역을 사용하는 Honeycomb 미만 버전에서는 이미지를 많이 사용하고 있는 화면에서 화면을 전환하는 행동이 발생했을 때도 OOM 이 발생하면서 앱이 중지될 것이다.
    • 왜냐하면 화면을 전환하면 이전 액티비티 인스턴스에 있던 ImageView 나 할당되었던 비트맵이 함께 소멸되어 메모리가 회수되고 새로운 액티비티 인스턴스를 생성할 텐데, 이 과정에서 이전 액티비티 인스턴스의 비트맵 객체가 회수되지 않아 메모리 누수가 발생했기 때문이다.
  • Honeycomb 미만 버전에서는 Java 비트맵 객체는 실제 비트맵 데이터를 가지고 있는 곳을 가리키는 포인터일 뿐이고 실제 데이터는 External 영역인 Native heap 영역에 저장된다.
    • Java 비트맵 객체는 참조가 없을 때 GC 에 의해 회수되지만 Native Heap 영역은 GC 수행영역 밖이기 때문에 메모리 소멸 시점이 다르다.
      • 이러한 문제로 Honeycomb 이후부터는 External 영역이 없어지면서 Dalvik heap 영역에 비트맵 메모리를 올릴 수 있게 되었고 GC 도 접근할 수 있게 되었다.

Bitmap Caching

  • 만약 고해상도 이미지를 로드할 때 OOM 이 발생하는 경우 BitmapFactory 객체를 이용해 다운샘플링, 디코딩 방식을 선택해 적절하게 뷰에 로드하면 된다.

 

  • 많은 이미지를 사용하게 되면서 OOM 이 발생하는 경우, 이미지 캐싱을 고려해 볼 수 있다.
    • 이미지가 화면에서 사라지고 다시 구성할 때 이미지를 매번 로드하는 것은 성능상으로나 사용자 경험에 좋지 않다.
  • *메모리와 *디스크 캐시를 이용하여 어디에선가 저장되어 있던 비트맵을 다시 가져온다면 다시 로드하는 시간도 단축시킬 수 있으며 성능 개선도 가능할 것이다.
 

[Android] Memory Cache와 Disk Cache

Memory vs DiskMemory 는 컴퓨터 내부에서 현재 CPU 가 처리하고 있는 내용을 저장하고 있는 휘발성 장치로, 처리 속도가 빠르다.Disk 는 Memory 보다는 느리지만, 많은 양의 데이터를 전원이 꺼져도 사

dev-baik.tistory.com

  • 그럼에도 불구하고 ImageView 에 이미지를 바로 로드하려고 한다면, Out of Memory, Slow Loading, Bitmap Caching, Memory Cache, Gabarge collector 이슈 등 개발자가 고려해야 할 요소가 많아진다. 이러한 복잡성을 해결하기 위해 개발자들은 종종 third party library 에 의존하게 된다.
 

[Android] 이미지 최적화(로드 개선)

해상도화면 또는 인쇄 등에서 이미지의 정밀도를 나타내는 지표이미지를 표현하는 데 몇 개의 픽셀 또는 도트로 나타냈는지 그 정도를 나타내는 말이다.즉, 해상도가 높다는 말은 1인치 당 찍게

dev-baik.tistory.com

 

[CS] 가비지 컬렉션 (GC: Garbase Collection)

가비지 컬렉션 (GC: Garbase Collection)메모리 관리 방법JVM(자바 가상 머신)의 Heap 영역에서 동적으로 할당됐던 메모리 중 필요 없게 된 메모리 객체(garbage)를 모아 주기적으로 제거하는 프로세스프로

dev-baik.tistory.com


Third Party Library

Picasso

  • MinSdkVersion = 14 / CompileSdkVersion = 29 / Library Size = 121 Kb / GIF 지원 X
  • Square에서 만든 이미지 로더 라이브러리
  • 최소한의 메모리로 이미지의 다양한 Transformation 을 지원하며, 자동으로 메모리 & 디스크 캐싱, 어댑터에서 ImageView 를 재활용 및 다운로드 취소가 가능하다는 점을 강조하고 있다.
  • 원본 이미지를 메모리로 가져온 후 GPU 에서 실시간으로 리사이징 하여 ImageView 에 할당하기 때문에 메모리 사용량이 많다.
  • 원본 이미지 크기를 그대로 비트맵에 그린 후에 ImageView 에 적용하기 때문에, 고화질의 이미지를 로드하면 OOM 을 발생시킬 수 있다.
    • 예시) 1000x800 픽셀의 이미지가 있을 때, Bitmap 에 1000 * 800 * 4btytes = 3MB 가 ImageView 위에 올라갈 것이다.
  • 위 문제를 방지하기 위해 fit() 함수를 이용한다면 고화질 이미지를 로드하기 전 ImageView 의 크기를 먼저 측정하기 때문에 메모리 사용량을 최소화할 수 있다.
  • *ARGB 8888 비트맵 포맷을 사용한다.
    • ARGB 8888 : 4 btye(32 bit)로 1 px 를 표현하는 방식이다. 32 bit 를 각 8 bit 로 동등하게 표현이 가능하며, 투명도를 나타내는 알파 값이 추가되어 보다 다채로운 색상의 표현이 가능하다.
  • 이미지 원본 그대로를 캐싱한다.
Picasso.get().load(url)
    .fit()
    .transform(RoundedCornersTransformation(128, 3))
    .into(imageView5, object : Callback {
        override fun onSuccess() {
            toast("Complete")
        }

        override fun onError(e: Exception?) {
            TODO("Not yet implemented")
        }
    })

 

Glide

  • MinSdkVersion = 14 / CompileSdkVersion = 26 / Library Size = 440 Kb / GIF 지원 O
  • Google 에서 만든 이미지 로더 라이브러리
  • 빠른 이미지 로딩, 버벅 거림과 끊김 현상이 발생하지 않는다는 점을 강조하고 있다.
  • 커스텀 된 HttpUrlConnection 을 사용하지만 Volley 또는 OkHttp 라이브러리에 연결할 수 있는 유틸리티 라이브러리도 포함되어 있어, 우리가 자주 사용하고 있는 라이브러리와 호환성이 좋다.
  • *RGB 565 비트맵 포멧을 사용한다.
    • RGB 565 : 2 byte(16 bit)로 1 px 를 표현하는 방식이다. 16 bit 를 R(5 bit) G(6 BIt) B (5 bit)로 표현하게 된다. R 과 B 의 경우 2^5인 32가지의 색상을 나타낼 수 있어 색상 표현 수준은 낮으나, 그만큼 이미지의 용량을 아낄 수 있다.
  • ImageView 의 크기에 맞는 이미지를 캐싱하기 때문에, 여러 크기의 ImageView 에서 동일한 사진을 보여주고자 할 때, 중복 캐싱으로 인해 메모리 사용 효율이 떨어지게 된다.
    • 이미지를 최대한 빨리 로드해주는데에 최적화 되어있기 때문에 이러한 정책을 기본적으로 적용하고 있다.
  • 아래처럼 싱글톤으로 만들어 간단하게 사용할 수 있다.
Glide.with(this)
    .load(url)
    .apply(RequestOptions.bitmapTransform(RoundedCornersTransformation(128, 3)))
    .listener(object : RequestListener<Drawable> {
        override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
            TODO("Not yet implemented")
        }

        override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
            toast("Complete")
            return false
        }
    })
    .into(imageView)
GlideImage(
    model = url,
    contentDescription = null
) { requestBuilder ->
    requestBuilder
    	.placeholder(R.drawable.img_dummy) // 이미지 로드 전 더미 이미지를 보여줌
        .override(viewSize) // 이미지 사이즈를 뷰사이즈로 설정
        .encodeQuality(80) // 이미지 품질을 80%로 설정
        .format(DecodeFormat.PREFER_RGB_565) // 이미지 포맷을 RGB565로 설정
        .diskCacheStrategy(DiskCacheStrategy.ALL) // 디스크 캐시 전략 설정
        .error(R.drawable.img_error) // 이미지 로드 실패시 에러 이미지를 보여줌
        .listener(object: RequestListener<Drawable> {
            override fun onLoadFailed(
                e: GlideException?,
                model: Any?,
                target: Target<Drawable>,
                isFirstResource: Boolean
            ): Boolean {
                TODO("Not yet implemented")
            }

            override fun onResourceReady(
                resource: Drawable,
                model: Any,
                target: Target<Drawable>?,
                dataSource: DataSource,
                isFirstResource: Boolean
            ): Boolean {
                toast("Complete")
                return false
            }
        })
}
  • 할당하려는 ImageView 의 크기를 측정한 다음 원본 이미지를 ImageView 크기에 맞게 리사이징 한 후, 비트맵에 그려주는 것을 기본 옵션으로 하기 때문에 메모리 효율성이 Picasso 보다 좋다.

https://medium.com/android-news/best-strategy-to-load-images-using-glide-image-loading-library-for-android-e2b6ba9f75b2

  • flow : Memory 확인 → Disk 확인 → 서버 요청 → *CDN 캐시 → 이미지 저장 스토리
    • CDN(Content Delivery Network) 캐시 : 전 세계에 분산된 서버 네트워크를 통해 웹 콘텐츠를 사용자에게 빠르게 제공하는 시스템
    • 각 단계에서 원하는 이미지를 찾으면 다음 단계를 진행하지 않는다. 따라서 클라이언트는 메모리와 디스크에서 한 번 다운로드한 이미지를 자주 참조할 수 있도록 잘 관리해야 한다.
  • LRU 캐시 알고리즘을 사용한다.
 

[Algorithm][Kotlin] LRU 알고리즘

캐시자주 사용하는 데이터나 값을 미리 복사해 놓는 임시 저장소로, 리소스에 대한 중복 요청을 막거나 바뀌지 않는 데이터를 새로 불러오는 비용을 줄이기 위해 사용된다. [CS] 캐시 (Cache)캐시

dev-baik.tistory.com

 

Coil (Coroutine Image Loading)

  • MinSdkVersion = 14 / CompileSdkVersion = 30 / GIF 지원 O
  • Instacart 에서 만든 이미지 로더 라이브러리
  • 거의 100% 코틀린으로 이루어져있고, AndroidX, OkHttp 등 현업에서 많이 쓰이고 있는 라이브러리들을 지언하고 있다.
  • ImageView 의 확장함수로 지원하고, 코틀린의 매력인 함수형 언어 덕으로 다른 라이브러리보다 더욱 간결한 코드를 구성할 수 있다.
imageView.load(url) {
    listener(
        onError = { _,_ -> /** Show toast. */ },
        onSuccess = { _,_ -> toast("Complete") }
    )
    transformations(RoundedCornersTransformation(128f))
}

 

[Android] Hello👋, Out Of Memory

많은 이미지를 사용하거나 고해상도 이미지를 이미지뷰에 로드해야하는 경우 발생하게 되는데, 이는 안드로이드 앱에서 사용할 수 있는 힙 메모리는 정해져있는데 반해 그 크기를 넘겨버렸기

velog.io

 

[Android] Image Loader Library 톺아보기 - Picasso, Glide, Coil

대표적인 라이브러리의 종류로는 예전에 많이 쓰이던 AUIL부터 현재에도 쓰이고 있는 Piccaso, Glide, Coil 등이 존재하는데 각각의 라이브러리들은 어떻게 사용하는지 간단하게 적어볼 예정이다.

velog.io

 

[Android] Image Optimization

안드로이드에서 효율적인 이미지 로드를 하기 위한 고민

velog.io