[Android] Coil은 어떻게 이미지를 로드할까? (1)

hxeyexn·2025년 8월 17일
post-thumbnail

들어가기 전

이번 시리즈는 Coil의 내부 구현을 분석합니다.

목차

  • Intro
  • Coil이란?
  • Coil이 이미지를 불러오는 과정
  • Outro


Intro

Glide나 Coil을 사용하면서, 한 번쯤은 이미지 라이브러리의 내부 동작을 깊이 분석해 보고 싶다는 생각이 있었다. 이번에 우연한 기회로 Coil을 분석할 일이 있었지만, 시간이 촉박해 빠르게 훑어보는 수준에 그쳤고 제대로 이해하지는 못했다.

Coil은 Coroutine 기반의 이미지 로더이며, 내부 구현이 Kotlin으로 작성되어 있다. 필자는 Kotlin과 Coroutine에 익숙해 Coil 분석을 선택했다.

물론 라이브러리의 내부 구현을 반드시 알아야 하는 것은 아니다. 하지만 요즘 대부분의 앱에는 이미지 관련 기능이 포함되어 있다. 이러한 라이브러리의 동작 원리를 하나라도 알고 있다면 큰 도움이 될 것으로 생각한다. 따라서 이번 글에서는 비유를 통해 Coil이 이미지를 로드하고 표시하는 과정을 간략히 살펴보려 한다.




Coil이란?

내부 구현을 살펴보기 전 Coil이 어떤 라이브러리인지 먼저 알아보자.

Coil은 Coroutine Image Loader의 약자로, 안드로이드 및 Compose 멀티플랫폼 용 이미지 로딩 라이브러리이다. 공식 문서에 따르면 빠른 속도, 가벼움, 간편한 사용성, 그리고 현대적인 설계를 주요 특징으로 내세우고 있다.

  • 빠른 속도(Fast): Coil은 메모리 및 디스크 캐싱, 이미지 다운샘플링, 요청 자동 일시 중지/취소 등 다양한 최적화를 수행
  • 가벼움(Lightweight): Coil은 Kotlin, Coroutines, Okio에만 의존하며 Google의 R8과 원활하게 호환
  • 간편한 사용성(Easy to use): API는 Kotlin의 언어 기능을 활용하여 단순성과 최소한의 보일러플레이트를 제공
  • 현대적인 설계(Modern): Kotlin을 우선으로 하며 Compose, Coroutines, Okio, OkHttp, Ktor를 포함한 최신 라이브러리와 상호 운용

Coil은 Kotlin Multiplatform으로 설계된 100% Kotlin 기반의 이미지 로딩 라이브러리이다. 특히 내부적으로 OkHttp 및 Coroutines와 같이 안드로이드 프로젝트에서 이미 널리 사용되는 라이브러리를 사용하기 때문에 기존 프로젝트에 원활하게 통합될 수 있다.




Coil이 이미지를 불러오는 과정

Coil은 이미지를 불러오는 과정을 마치 주문 제작 케이크 가게와 비슷하다.

ImageRequest -> 케이크 주문서

ImageRequest는 어떤 디자인, 어떤 사이즈, 어떤 모양인지 담긴 주문서와 비슷하다.

ImageLoader가 이미지를 로드하기 위해 필요한 모든 정보를 제공하는 값 객체이다. ImageRequest는 빌더를 사용하여 생성할 수 있다.


ImageLoader -> 케이크 공방 사장님

Coil의 이미지 로딩은 마치 케이크 공방의 사장님처럼 하나의 ImageLoader가 모든 과정을 책임진다.

ImageLoader는 ImageRequest를 실행하는 서비스 객체로서, 다음과 같은 책임을 지며 이미지 로딩 파이프라인의 핵심 역할을 담당한다.

  • 주문 관리: ImageRequest를 받아 로딩 요청을 처리
  • 캐싱 및 메모리 관리: 메모리와 디스크 캐시를 관리하며 이미지를 효율적으로 재사용
  • 재료 준비 및 가공: 이미지를 가져오고(Fetcher), 원본 데이터를 디코딩(Decoder)하여 화면에 표시할 수 있는 형태로 디코딩한다.

Coil은 단일 ImageLoader 인스턴스를 앱 전역에서 공유할 때 가장 효율적으로 작동한다. 여러 개의 ImageLoader를 생성하면 각 인스턴스가 독립적인 메모리 캐시, 디스크 캐시를 가지게 되어 리소스 낭비를 초래한다.


Interceptors -> 제빵사들

Coil의 Interceptors는 마치 케이크 공방에서 사장님에게 전달받은 주문서를 먼저 확인하는 막내 제빵사처럼 동작한다. 주문서(ImageRequest)를 검토한 뒤, 필요하다면 내용을 수정하거나, 이미 완성된 케이크(캐시)가 있다면 주방(남은 파이프라인)을 거치지 않고 바로 고객에게 전달하기도 한다.

여러 제빵사(Interceptor)가 차례대로 주문서를 확인했음에도 주문이 처리되지 않았다면, 마지막으로 EngineInterceptor라는 메인 제빵사가 등장한다. 그는 직접 재료를 가져오고(fetching), 케이크를 굽는(decoding) 핵심 작업을 수행한다.


Mapper -> 매니저

제빵사들의 검토가 끝난 뒤, 케이크를 본격적으로 제작하기 전에 해야 할 일은 손님의 요청을 제빵사가 이해할 수 있는 정확한 제품 코드나 형식으로 변환하는 것이다.

즉, 손님이 “딸기 케이크”라고만 주문했더라도, 매니저가 이를 정확히 어떤 제품인지 식별할 수 있는 형태로 정리해 주방에 넘기는 상황과 비슷하다.(e. g. /images/strawberrycake.jpgUri)

Coil에서 이 과정을 담당하는 것이 바로 Mapper이며, 이는 입력 데이터(T)를 Fetcher가 처리할 수 있는 형태(V)로 변환하는 인터페이스이다.


Keyer -> 케이크 고유 번호 생성기

공방에서는 케이크를 제작하기 전에 주문마다 고유한 번호를 붙인다.
손님의 주문서에 적힌 케이크 종류, 크기, 모양, 데코레이션 옵션 등 모든 조건을 종합해 유일한 주문 번호(캐시 키)를 만들어낸다.

이렇게 하면 같은 조건으로 주문이 다시 들어왔을 때,
이미 만들어둔 케이크를 곧바로 꺼내 고객에게 전달할 수 있다.

Coil에서 Keyer는 이러한 역할을 한다. 이는 입력 데이터(T)를 문자열 키(String)로 변환하며, 생성된 키는 메모리 캐시에서 이미지를 식별하고 재사용하는 데 활용된다.


MemoryCache -> 쇼케이스

쇼케이스는 손님이 자주 찾는 케이크를 바로 꺼내 줄 수 있도록 눈앞에 진열해 두는 공간이다. 주문한 케이크가 이미 이곳에 있다면, 별다른 과정 없이 곧바로 제공한다. 즉, 메모리 캐시는 빠르고 효율적인 이미지 로딩 방법이다.


DiskCache -> 냉동 창고

쇼케이스에 케이크가 없다면, 공방은 냉동 창고를 확인해 예전에 만든 케이크를 꺼낸다. 냉동된 케이크를 꺼내 살짝 해동하면 새로 만들지 않고도 손님에게 전달할 수 있다. 이처럼 디스크 캐시는 메모리 캐시에 없을 때 활용되는 두 번째 저장소이다.


Fetcher -> 재료 준비/반죽 담당 제빵사

쇼케이스나 냉동 창고에 케이크가 없을 경우, 재료 준비/반죽 담당 제빵사는 재료를 직접 꺼내 반죽을 준비한다. 즉, Fetcher는 데이터(URI, 파일 등)를 키로 사용하여 원격 소스(네트워크·디스크 등)에서 바이트를 가져오거나, 데이터를 직접 읽어 이미지를 만드는 단계이다.


Decoder -> 오븐 담당 제빵사

준비한 반죽을 오븐에 넣어 케이크를 굽는 과정이다.
Decoder는 SourceFetchResult를 받아 Bitmap/Drawable 같은 최종 이미지로 변환한다. 또한 GIF, SVG, TIFF 같은 특수 포맷 지원을 위해 커스텀 디코더를 추가할 수도 있다.


이미지 로드 -> 케이크 전달

모든 과정이 끝난 뒤 케이크는 손님에게 전달된다.
Coil에서는 이 순간이 곧 ImageView나 UI 컴포넌트에 이미지가 표시되는 시점이다. 즉, 로딩된 결과가 실제 화면에 렌더링되는 최종 단계이다.




Outro

이번 1편에서는 Coil이 이미지를 로드하는 전체 흐름을 간략히 살펴보았다. 다음 편에서는 이어서 Coil의 캐싱 전략에 좀 더 깊이 들여다보겠다.




참고자료

Coil 공식 문서
Pluu Dev: Coil 요청 가로채기
Manifest Android Interview

profile
Android Developer

0개의 댓글