본문 바로가기

Android/Tech

[Jetpack Compose] Glide 와 Coil, 무엇을 사용하면 좋을까.

Unsplash, Omid Armin.

이미지 처리 라이브러리

개발시, 저는 UI에 관련된 라이브러리는 최대한 사용하지 않으려고 하는 편입니다. 어쨌든 라이브러리도 사람이 만드는 것이고, 자세히 파고들면 그다지 어려운 개념이 들어가는 것도 아니니까, 직접 구현하겠다는 생각으로 말이죠. 그런 저도 꼭 사용하는 UI 라이브러리는 이미지 처리 라이브러리입니다. 애초에 이미지 자체가 워낙 심화된 영역이기도 하고, 이미지 처리 라이브러리는 그 역사가 깊어 사용하기도 수월하고 성능도 좋기 때문이죠.

 

이미지 처리 라이브러리의 역사는 안드로이드의 역사와 궤를 같이 합니다. Glide, Fresco, Picasso, 그리고 모던한 Coil 까지, 수많은 라이브러리가 존재하죠. 우리는 수많은 선택지들 중 하나를 골라야 합니다. 물론 여러 라이브러리를 동시에 사용할 수는 있겠지만, 구태여 여러 라이브러리를 함께 사용해 패키지 용량을 늘리는 일은 하지 않는 것이 낫습니다.

 


그렇다면, 무엇을 사용할까?

안드로이드 UI 생태계가 XML 에서 Jetpack Compose 로 변모하였으니, 시대의 변화에 발 맞춰 Jetpack Compose 를 기반으로 포스트를 풀어 나가겠습니다. Picasso 는 조금 올드한 느낌이 있고, Fresco 는 인지도가 떨어지는 경향이 있으니, Glide 와 Coil 만을 두고 비교해봅니다.

 

Glide

안드로이드 개발자들이 가장 사랑하는 이미지 처리 라이브러리입니다. 역사도 오래 되었고, 충분히 안정적입니다. 동종 라이브러리들에 비해 가장 인지도가 높고, 현업에서도 굉장히 많이 사용합니다. 아마 8~9할은 Glide 를 쓰지 않을까 싶습니다. 특징으로는 XML 환경에서 가독성이 굉장히 높은 코드를 작성할 수 있으며, GIF 를 완벽하게 지원합니다.(Coil 역시 GIF 를 지원하긴 하나, 추가적으로 확장 라이브러리 의존성이 필요하며, 그마저도 그다지 안정적이지 않다는 의견이 있습니다.) 어떤 이미지 처리 라이브러리를 사용해야할지 모르겠다면, 그냥 Glide 를 사용하면 됩니다. 사용하는 유저가 많음에 따라 얻을 수 있는 장점들이 아주 많기 때문입니다. (라이브러리 자체 이슈 파악, 이슈 발생시 트러블 슈팅 방법, 사용자 수에 따른 범용성 증가, 등) 

 

Coil

Glide 의 떠오르는 대항마, Modern Image Library, Coil 입니다. 그리 오래 되지 않았음에도 불구하고 인지도가 상승 곡선을 달리고 있습니다. Glide 와는 다르게 Kotlin 으로 구현되어 있으며, 내부적으로 Coroutine 을 사용하여 이미지를 로드합니다.(확인해보시면 아시겠지만, Glide 의 경우 추상화 단계를 조금만 뚫고 들어가면 바로 Java 코드가 등장합니다.) Glide 에 비해 다소 경량이며, Kotlin 언어의 장점을 통해 Jetpack Compose 환경에서 깔끔한 코드를 작성할 수 있습니다.

 

성능 

성능에 대해선 이미 많은 포스팅이 존재하는데요. 전체적인 의견을 취합해보면 그래픽 처리에 사용되는 메모리는 비슷하나, Coil 의 경우 Coroutine 을 사용하기 때문에 Native Code 호출 메모리가 적습니다. Glide 의 경우 Background Thread 에서 데이터를 불러오는 반면, Coil 은 경량 스레드인 Coroutine 에서 데이터를 불러오기 때문이지요.

 

성능상으로만 본다면 Coil 이 무조건 유리하기 때문에 Coil 을 사용하는 것이 맞겠지만, 테스트 결과는 조건이나 상황에 따라 얼마든지 달라질 수 있으며, 선택시에는 메모리 소비량만을 따질 것이 아니라 전체적인 성능과 안정성 등, 다양한 지표를 판단의 근거로 삼아야 합니다. 개인적으로 느끼기엔, 안정적이고 메이저한 서비스라면 굳이 Glide 에서 Coil 로 마이그레이션할 필요는 없을 것 같고, MVP 수준이나 이제 막 시작하는 신생 서비스의 경우에는 Coil 을 도입해볼만 하다고 여겨집니다.

 

코드

성능도 당연히 중요하지만, 원활한 유지보수를 위해 코드 작성 시 가독성이 높은 쪽을 선택하는 것도 중요합니다. 이하는 Glide 와 Coil 의 코드 비교입니다. 상위 기술했듯, Glide 보다는 Coil 이 Jetpack Compose 에서 더 유려하게 코드를 작성할 수 있도록 구현돼 있습니다.

 

 

GlideImage(
    modifier = Modifier
        .weight(1f)
        .height(60.dp),
    model = image, // Image url
    contentDescription = "reviewImage",
    contentScale = ContentScale.Crop
)

 

먼저, Glide 의 코드입니다. 이렇게 보면 굉장히 깔끔합니다. 

 

AsyncImage(
    modifier = Modifier
        .clip(shape = CircleShape)
        .size(24.dp),
    model = reviewComment.userImage[0], // Image url
    contentDescription = "commentAuthorImage",
    contentScale = ContentScale.FillBounds
)

 

Coil 코드입니다. Glide 와 별반 차이는 없습니다.

 

GlideImage(
    modifier = Modifier.size(24.dp),
    model = author?.profilePath ?: "",
    contentDescription = "profileImage",
    requestBuilderTransform = object : RequestBuilderTransform<Drawable> {
        override fun invoke(p1: RequestBuilder<Drawable>): RequestBuilder<Drawable> {
            return p1.thumbnail()
        }
    }
)

 

다만, 단순히 이미지를 보여주는 기능을 떠나 다른 속성을 적용하려면 조금 지저분해집니다. 물론, requestBuilderTransform Attribute 는 Lambda 로 대체가 가능합니다. 다만, 그래도 지저분한 모습은 여전합니다. 이하는 이미지 로드 실패로 인해 대체 이미지를 보여주는 코드입니다.

 

GlideImage(
    modifier = Modifier.size(24.dp),
    model = author?.profilePath ?: "",
    contentDescription = "profileImage",
    requestBuilderTransform = {
        it.error(ContextCompat.getDrawable(context, R.drawable.ic_image_error))
    }
)

 

Glide 의 코드입니다. 기존 Glide 를 사용하던 방식 그대로 error() 메서드를 사용하며, 이미지를 불러올 땐 ContextCompat 을 이용해주어야 합니다.

 

AsyncImage(
    modifier = Modifier
        .fillMaxWidth()
        .height(120.dp),
    model = brand.imagePath?.get(0) ?: "",
    contentDescription = "brandLogoImage",
    contentScale = ContentScale.Inside,
    error = painterResource(id = R.drawable.ic_image_error)
)

 

Coil 의 코드입니다. 'error' Attribute 는 인자로 Painter 를 받기 때문에, 평소 Image Composable 을 사용하던 것 처럼 painterResource() 메서드를 통해 이미지를 불러오면 됩니다. 

 

Coil 의 경우 Glide 와 다르게 Kotlin 으로 구성되어 있고, 애초부터 Jetpack Compose 를 제대로 지원하고자 만들어진 이미지 처리 라이브러리이기 때문에, Glide 와 비교했을 때 비교적 깔끔하게 코드를 작성할 수 있습니다.

 


결론

대부분의 IT 기업들이 안드로이드 포지션의 개발자를 채용할 때 Jetpack Compose 를 다룰 줄 아는 인재에 집중하고 있음이 확인되는 요즘입니다. 그만큼 Jetpack Compose 는 메이저 기술로 도약했고, 꼭 익혀야만 하는 기술이 된 것 같습니다. 그리고 그에 따라 Coil 에 대한 니즈도 자연스레 조금씩 상승하고 있는 것으로 보입니다.

 

만약 Jetpack Compose 를 기존 서비스에 도입하려는 경우, GIF 에 대한 확실한 요구가 없는 경우에는 Coil 을 적용해보는 것도 매우 좋아 보입니다. 아주 깔끔하게 Compose 코드와의 결합이 가능하니까요.

 

다만, 이미 충분한 안정성을 자랑하는 Glide 에서 Jetpack Compose 와 통합하기 쉬운 Compose 관련 코드가 추가되어 배포된다면, 그 때는 또 다시 비교를 거쳐야만 할 것입니다. 

 

Coil 의 Jetpack Compose Image Loading Method 인 AsyncImage 의 사양을 마지막으로 포스트를 마칩니다.

 

@Composable
fun AsyncImage(
    model: Any?,
    contentDescription: String?,
    modifier: Modifier = Modifier,
    placeholder: Painter? = null,
    error: Painter? = null,
    fallback: Painter? = error,
    onLoading: ((State.Loading) -> Unit)? = null,
    onSuccess: ((State.Success) -> Unit)? = null,
    onError: ((State.Error) -> Unit)? = null,
    alignment: Alignment = Alignment.Center,
    contentScale: ContentScale = ContentScale.Fit,
    alpha: Float = DefaultAlpha,
    colorFilter: ColorFilter? = null,
    filterQuality: FilterQuality = DefaultFilterQuality,
) = AsyncImage(
    model = model,
    contentDescription = contentDescription,
    imageLoader = LocalImageLoader.current,
    modifier = modifier,
    placeholder = placeholder,
    error = error,
    fallback = fallback,
    onLoading = onLoading,
    onSuccess = onSuccess,
    onError = onError,
    alignment = alignment,
    contentScale = contentScale,
    alpha = alpha,
    colorFilter = colorFilter,
    filterQuality = filterQuality
)

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

Bitmap, Drawable, BitmapDrawable, ImageBitmap, AndroidBitmap  (0) 2023.03.13
코루틴과 그린 스레드  (0) 2023.03.09
안드로이드와 Clean Architecture  (0) 2023.02.11
JVM 의 Garbage Collector  (0) 2023.02.01
Jetpack Compose 의 remember  (0) 2023.01.19