
얼마 전에 코일(Coil) 마이그레이션 첫 화면을 배포했는데요. 에러 로그 하나 없이 이미지가 전부 안 뜨더라고요. 의존성 딱 한 줄을 빠뜨렸을 뿐이었습니다.
원인을 찾는 데 두어 시간을 날렸어요. 스택트레이스가 없으니 어디부터 봐야 할지 감이 안 잡히더라고요. 제가 겪은 순서대로 하나씩 풀어볼게요.
Glide가 멀쩡한데 Coil 마이그레이션을 결심한 진짜 이유
솔직히 말하면 글라이드(Glide) 는 5년 동안 별 탈 없이 잘 돌아갔습니다. "빠르니까 갈아탄다"는 얘기가 아니었어요.
진짜 이유는 코드베이스가 가는 방향이었습니다. 화면을 하나씩 젯팩 컴포즈(Jetpack Compose) 로 옮기면서 코틀린 코루틴 중심으로 정리하고 있었거든요. 글라이드는 어노테이션 프로세서(kapt) 무게가 있어서, KSP·경량화로 가는 흐름과 살짝 어긋났어요.
크기 얘기도 자주 나오는데요. 코일이 대략 90KB대, 글라이드가 220KB대 수준으로 알려져 있어요. 절반 이하이긴 한데 버전에 따라 달라서 결정타는 아니었습니다.
그러니까 "동작하면 건드리지 마라"는 원칙과 정면으로 부딪히는 결정이었어요. 안정적인 뷰 기반 레거시라면 전환 비용이 이득보다 클 수 있습니다. 마이그레이션은 필수가 아니라 선택이라고 봅니다.

io.coil-kt.coil3 패키지 변경과 점진적 마이그레이션
코일 3부터 좌표랑 패키지가 통째로 바뀝니다. io.coil-kt → io.coil-kt.coil3, 패키지도 coil → coil3 로요.
처음엔 이게 귀찮았는데, 파고들어 보니까 오히려 다행이더라고요. 코일 2와 코일 3가 한 프로젝트에 병존할 수 있거든요. 덕분에 화면 단위로 하나씩 옮기는 점진적 마이그레이션이 가능합니다.
문제는 그 다음이었어요. 글라이드 코드를 코일로 1:1 치환하려다 막혔습니다.
글라이드의 Glide.with().load().into() 흐름을 코일에서 그대로 찾으려 하면 어색해져요. 코일은 ImageRequest 를 만들고 ImageLoader.execute() 로 실행하는 구조입니다. 동기식 submit().get() 은 suspend 함수 execute() 로, RequestListener 는 코일 쪽 리스너로 넘어가는데요. 이게 문법 교체가 아니라 사고방식 교체라는 걸 뒤늦게 깨달았습니다. 한 줄씩 바꾸려 들면 계속 걸리곤 했어요.

Compose 이미지 로딩, AsyncImage와 placeholder 제대로 쓰기
컴포즈 화면에서는 오히려 코드가 단순해집니다. 세 가지를 상황 따라 골라 쓰면 돼요.
- 단순 표시는
AsyncImage하나로 끝납니다. 대부분 여기서 해결돼요. - 페인터를 직접 다뤄야 하면
rememberAsyncImagePainter를 씁니다. - 로딩·에러 상태를 컴포저블로 세밀하게 커스텀하려면
SubcomposeAsyncImage로 갑니다.
여기서 한 가지 걸리는 점이 있는데요. AsyncImage 의 placeholder / error / fallback 인자는 컴포저블이 아니라 Painter 를 받습니다. 페인터 방식이 성능은 더 낫지만 유연성은 떨어지는 맞바꿈이 있어요. 로딩 화면에 복잡한 UI를 넣고 싶으면 SubcomposeAsyncImage 로 가야 합니다.
글라이드의 RequestListener 에서 하던 성공·실패 처리는 AsyncImage 의 onSuccess / onError 콜백으로 옮기면 자연스럽습니다. 크로스페이드 같은 옵션도 ImageRequest.Builder 에서 crossfade(true) 로 켜주면 되고요.
coil-core만 넣어 이미지가 에러 없이 사라진 함정
자, 제가 두 시간을 날린 그 함정입니다. 코일 3의 최대 함정은 성능이 아니라 네트워크 로딩 아티팩트 누락이었어요.
coil-core 는 네트워크 이미지 로딩을 기본으로 지원하지 않습니다. coil-network-okhttp 나 coil-network-ktor 를 명시적으로 추가해야 하는데요. 이걸 빠뜨리면 예외도, 에러 로그도 없이 네트워크 이미지가 전부 조용히 안 뜹니다.
정상적으로는 이렇게 두 줄이 들어가야 해요.
implementation("io.coil-kt.coil3:coil-compose:3.5.0")
// 이 한 줄이 없으면 네트워크 이미지가 조용히 실패합니다
implementation("io.coil-kt.coil3:coil-network-okhttp:3.5.0")
글라이드는 의존성 하나 넣으면 다 되던 구조였잖아요. 근데 코일 3는 core / network / gif / svg 로 잘게 쪼개져 있어서, 이 파편화가 처음 넘어오는 사람한테는 진입 장벽이 됩니다. iOS나 Ktor 엔진을 쓰는 경우엔 증상과 해법이 또 달라져요. 이슈 트래커에도 같은 함정에 빠진 사례가 여럿 올라와 있더라고요.

Glide Coil 비교, 아직 Glide가 유리한 경우
솔직하게 말하면 "코일이 무조건 빠르다"는 건 오해입니다.
무거운 커스텀 트랜스포메이션이나 복잡한 모델 로더, GIF 성숙도는 아직 글라이드가 유리한 것으로 보입니다. 콜드 스타트·메모리·생명주기 인지 같은 실사용 지표에서는 코일이 동등하거나 그 이상이지만요. 특정 무거운 작업에서는 얘기가 달라지는 경우가 많습니다.
안드로이드만 다루는 팀이라면 KMP(코틀린 멀티플랫폼) 매력도 크게 와닿지 않습니다. 그런 경우엔 코일 2로도 충분할 때가 많고요.
생태계 성숙도랑 참고 자료는 아직 글라이드가 우세합니다. 그리고 제가 겪은 이 경험도 뷰+컴포즈+OkHttp 라는 특정 스택에 기댄 얘기예요. 스택이 다르면 증상도 다를 수 있다는 점은 감안하셔야 됩니다.
결국 갈아탈지 말지는 성능표가 정하는 게 아니라, 우리 코드베이스가 어디로 가고 있는지가 정합니다. 병존이 되니까 전면 교체로 덤빌 이유는 없어요. 딱 한 화면부터 옮겨보고, 그 화면에서 의존성 두 줄만 제대로 챙겼다면 저처럼 로그 없는 두 시간을 날릴 일은 없습니다.

'Android 개발 > Jetpack Compose' 카테고리의 다른 글
| 로직은 KMP인데 아이콘은 세 벌이던 프로젝트, Compose Resources로 끝낸 기록 (0) | 2026.10.14 |
|---|---|
| 썸네일이 상세에서 흰색으로 번쩍이던 걸 Compose 공유 요소 전환으로 잡은 기록 (0) | 2026.10.08 |
| 1.9.24 vs 1.9.25, 숫자 하나로 빌드 터지던 Compose 컴파일러 버전 청소법 (0) | 2026.10.06 |
| 4px 패딩 회귀를 눈으로 못 잡던 나, Compose 스크린샷 테스트로 넘긴 이야기 (0) | 2026.09.26 |
| Compose에서만 죽던 로그인 자동완성, credentialRequest로 살린 배선 일지 (0) | 2026.09.20 |