본문 바로가기

Android(Kotlin) Study A부터 Z까지 등반

안드로이드 Coil 라이브러리란?!

Coil

Coil : Coroutine Image Loader의 약자이며 Kotlin Coroutines(코루틴) 으로 만들어진 가벼운 Android 백앤드 이미지 로딩 라이브러리이다.

요구사항

  • Android X
  • Min SDK 14+
  • Compile SDK: 29+
  • Java 8+

사용하는 방법들

  • 종속성 추가 implementation("io.coil-kt:coil:0.10.0")
//load 메소드 사용

imageView.load(R.drawable.image)
imageView.load(File("/path/to/image.jpg"))
imageView.load("content://com.android.externalstorage/image.jpg")

val imageLoader = context.imageLoader  //Coil의 주요 서비스 클래스

val request = ImageRequest.Builder(context)
    .data("<https://www.example.com/image.jpg>")
    .target { drawable ->
        // Handle the successful result.
    }
    .build()
imageLoader.enqueue(request)

// Execute an ImageRequest to suspend and return the drawable.

//preloading도 포함, target 없이
val request = ImageRequest.Builder(context)
    .data("<https://www.example.com/image.jpg>")
    .build()
val drawable = imageLoader.execute(request).drawable

//명령형(how)

💡 이거 말고도 placeholder, crossfade같은 것도 추가 된다.

 

왜 이미지 로더를 쓰는가? 그냥 load하면 안되나?

이미지 로더가 지원 가능한 Data Type

  • String (mapped to a Uri)
  • HttpUrl
  • Uri (android.resource, content, file, http, and https schemes only)
  • File
  • @DrawableRes Int
  • Drawable
  • Bitmap

💡 왜 쓰는지 알 것 같기도 하네요

 

💡 또한 싱글톤 객체로 만들어서 공용으로 쓸 수도 있고 따로 생성도 가능

 

이미지 변환(?)도 제공해준다. 4가지임

  • BlurTransformation이미지를 흐릿하게 보이기하는 가우시안 블러(Gaussian Blur)를 적용.
  • CircleCropTransformation이미지의 중심을 기준으로 원형으로 이미지를 자른다. (예. 카카톡의 원형 프로필 이미지)
  • GrayscaleTransformation그레이스케일로 음영처리를 적용.
  • RoundedCornersTransformation사이즈에 맞도록 이미지를 자르고 이미지 모서리를 둥글게 라운드를 적용.

관련 예제

// CircleCrop(이미지 원형으로 자르기) 예시
imageView.load("<https://www.example.com/image.jpg>") {
    crossfade(true)
    placeholder(R.drawable.image)
    transformations(CircleCropTransformation())
}

ImageLoader자체도 커스터마이징 가능

interface ImageLoaderModule {
    fun get(): ImageLoader
}

class ImageLoaderModuleImpl @Inject constructor(@ApplicationContext val context: Context) :
    ImageLoaderModule {

    @OptIn(ExperimentalCoilApi::class)
    fun getImageLoader(): ImageLoader = ImageLoader.Builder(context)
        .precision(Precision.AUTOMATIC)
        .componentRegistry { 
        	add(OnDemandResizingInterceptor())  // 이후에 구현할 클래스 
        }
        .okHttpClient {
            OkHttpClient.Builder()
                .cache(CoilUtils.createDefaultCache(context))
                .build()
        }
        .build()

    override fun get(): ImageLoader = getImageLoader()
}

💡 여기에 자세히 보면 componentRegistry에 Interceptor를 추가하는 부분을 자세히 보자. 어떤 인터셉터일까? 따로 구현해둔 것이다.

 

@ExperimentalCoilApi
class OnDemandResizingInterceptor : Interceptor {

    override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
        var request = chain.request
        when (val url = chain.request.data) {
            is String -> {
                if (URLUtil.isHttpUrl(url) || URLUtil.isHttpsUrl(url)) {
                    val size = chain.size // 사이즈를 가져옴
                    val imgUrl = when (size) {
                        is PixelSize -> "$url?width=${size.width}"
                        is OriginalSize -> "$url?width=Origin"
                        else -> url
                    }
                    request = chain.request.newBuilder().data(imgUrl).build()
                }
            }
        }
        return chain.proceed(request)
    }
}
  • interceptor 에서 request 를 가져와 해당 요청이 String 이라면 Http 혹은 Https Url 인지 확인하여 요청 URL 에 width QueryString 을 달아주고 새로운 요청을 만들어서 리턴 시켜준다. 이제는 View 의 사이즈에 맞는 Url 을 요청할 수 있기 때문에 이전보다 좀더 효율적으로 메모리를 사용할 수 있게 되었다.

💡 물론 서버에서 해당 리사이즈 요청에 따른 이미지를 반환해준다는 조건으로 만들어진 예제

 

Glide, Fresco 놔두고 왜? 장점들

  • Glide, Fresco보다 상대적으로 가볍다.
  • 코루틴이 기본이지만 메인까지는 아니며 심플함과 최소한의 보일러 플레이트(boilerplate)를 위하여 Kotlin의 기능을 활용하여 Kotlin을 잘 다룬다면 사용하기가 매우 쉽다. extension functions, inlining, lambda params, and sealed classes 로 사용가능
  • 메모리와 디스크의 캐싱, 메모리의 이미지 다운 샘플링, Bitmap 재사용, 일시정지/취소의 자동화 등등 수 많은 최적화 작업을 수행하므로 처리 속도가 굉장히 빠르다.
  • 다이나믹 이미지 샘플링을 지원하며 이는 Coil에서 자동으로 알아서 처리된다.
    • 100x100 이미지 4개에서 갑자기 큰 이미지를 불러와야할 때, CrossFade 애니메이션과 함께 보여야 할 이미지를 100x100으로 불러와질때까지 잠깐 보여줬다가 전환(자동)

  • Coil의 이미지 파이프라인(PipeLine)은 크게 세 가지로 Mappers, Fetchers, Decoders로 구성되는데(interceptors도 있음) 이러한 인터페이스를 사용하여 기본 이미지 로딩 기능을 강화하거나 재정의하고 Coil에 새로운 파일 형식에 대한 지원을 추가할 수 있음.
    • 이미지 파이프 라인 총 4가지Interceptors : 이미지 요청을 할 떄, 관찰하거나 변환, 재시도 등을 구현할 수 있습니다. Coil 예제에서는 커스텀 Cache layer 를 만들어서 예시를 들고 있음.Fetchers : 데이터를 처리하는 방식으로, HttpUriFetcher, HttpUrlFetcher, BitmapFetcher, DrawableFetcher, FileFetcher 등 제공하고 있습니다. 가령 File 중 영화 관련 JPG, JPEG, PNG 등만 읽어오는 MovieIconFectcher 를 만들어 볼 수도 있음.
    • Decoders: 기본 이미지 포맷 뿐만 아니라, GIF, SVG, TIFF 등과 같은 포맷도 지원할 수 있도록 도와주는 클래스.
    • Mappers : 커스텀 데이터 타입에 대해 지원하도록 처리할 수 있습니다. 가령 썸네일과 원본 이미지를 가지고 있는 데이터 클래스에서 썸네일 이미지만 사용하도록 변경 할 수 있음.
    • 간단하게만 정리하고 넘어가자
  • Bitmap Pooling : Glide나 Fresco처럼 Coil도 비트맵 풀링을 지원한다. 비트맵 풀링이란 사용되지않는 비트맵 오브젝트를 재활용하는 테크닉을 말한다. 이를 이용하면 메모리 성능을 상당히 개선할 수 있다.
  • Kotlin으로 개발 되었으며 Coroutines, OkHttp, Okio, AndroidX Lifecycles등의 최신 라이브러리를 사용한다.

그래도 단점은 있다.

  • 버전이 아직 정식버전이라고는 할 수 없어서 신뢰성이 낮아 실무에서 사용하는 기업이 많이 없다. // 이건 아마 이제 아닐듯
  • 최소 Android SDK 버전 14부터 지원을 하나 최신버전에서 사용을 하는 것을 권장함.(AndroidX)
  • Kotlin을 모른다면 사용하기 어렵다.
  • 일부 기능 사용을 위해 Coroutines이 무엇인지 개념적으로 어느정도 알아야 한다.

Glide, Picasso, Coil 비교

https://jizard.tistory.com/224

결론

고화질 사진 이미지뷰에 로드 : Coil <<<< Glide << Picasso (Coil이 엄청 많이 차이남)

이미지 리사이징 : Picasso < Coil < Glide (이건 솔직히 다 똑같음)

💡 상당히 안드로이드를 위한 라이브러리라는 것이 느껴졌음, 성능면에서도 많이 부족한 부분이 지금은 없는 것 같다. 요즘은 Glide ⇒ Coil로 마이그레이션 하는 회사들도 좀 있다고함

 

https://developer.android.com/studio/profile/memory-profiler.html#how-counted

  • Java: 자바 또는 Kotlin 코드에서 할당된 객체의 메모리.
  • Native: C 또는 C++ 코드에서 할당된 객체의 메모리.
  • 앱에서 C++를 사용하지 않더라도, Android 프레임워크에서 이미지 애셋 및 기타 그래픽을 처리하는 경우와 같이 개발자 대신 다양한 작업을 처리하기 위해 네이티브 메모리를 사용하므로 코드가 자바 또는 Kotlin으로 작성된 경우에도 일부 사용된 네이티브 메모리가 여기에 표시될 수 있습니다.
  • Graphics: GL 표면, GL 텍스처 등 픽셀을 화면에 표시하기 위해 그래픽 버퍼 큐에 사용되는 메모리. 참고: 이 메모리는 전용 GPU 메모리가 아니라 CPU와 공유됩니다.
  • Stack: 앱의 네이티브 및 자바 스택에서 사용하는 메모리. 일반적으로 앱에서 실행 중인 스레드 수와 관련이 있습니다.
  • Code: 앱에서 dex 바이트 코드, 최적화되거나 컴파일된 dex 코드, .so 라이브러리, 글꼴 등 코드 및 리소스에 사용하는 메모리.
  • Others: 앱에서 사용하는 메모리 중 시스템에서 분류하는 방법을 알지 못하는 메모리.
  • Allocated: 앱에서 할당한 자바/Kotlin 객체 수. C 또는 C++에서 할당된 객체는 계산되지 않습니다.
  • Android 7.1 이하를 실행하는 기기에 연결된 경우 메모리 프로파일러가 실행 중인 앱에 연결된 때만 이 할당 카운트가 시작됩니다. 따라서 프로파일링을 시작하기 전에 할당된 객체는 모두 고려되지 않습니다. 하지만 Android 8.0 이상의 경우 모든 할당을 추적하는 기기 내 프로파일링 도구가 포함되므로, 이 숫자는 항상 Android 8.0 이상의 앱에서 실행 중인 총 자바 객체 수를 나타냅니다.

참고

https://jaeyeong951.medium.com/android-coil-안드로이드-image-loader-e989aaa95ce5

https://coil-kt.github.io/coil/

https://velog.io/@jojo_devstory/Android-이미지-로딩-라이브러리-Coil-을-알아보자

https://nanamare.tistory.com/188