안드로이드 프레임워크·시스템 학습 노트 목차

이미지·미디어 — Coil·Media3·CameraX

왜 이미지·미디어가 메모리의 지뢰밭인가

이미지·영상·카메라는 메모리를 가장 많이 먹고 성능에 가장 민감한 영역이다. 숫자로 보면 분명하다 — 4000×3000 사진 한 장을 화면에 띄우려고 디코딩하면, 압축이 풀린 Bitmap은 픽셀당 4바이트4000 × 3000 × 4 ≈ 48MB다. 목록에 이런 이미지 열 장만 원본 크기로 들고 있어도 480MB — 앱이 OutOfMemoryError로 즉사한다. 그런데 화면엔 64dp(≈수백 픽셀)로 작게 보여줄 뿐이다. 그래서 원본을 표시 크기에 맞춰 줄여(다운샘플) 디코딩하는 게 필수인데, 이걸 직접 하면 실수가 잦다.

그래서 안드로이드는 검증된 라이브러리에 맡긴다 — 이미지는 Coil, 미디어 재생은 Media3(ExoPlayer), 카메라는 CameraX. 공통점은 — 모두 생명주기·코루틴·Compose에 맞물려, 무거운 자원을 알아서 줄이고·캐시하고·해제한다는 점이다([andsys 23 권장 라이브러리는 2026 현재 공식 권장과 일치]). (출처: Coil / Android — Media3 / CameraX.)

이미지 로딩 — Coil이 대신 해주는 것

Coil(Coroutine Image Loader)은 코틀린·Compose 우선의 이미지 로더다. AsyncImage 한 줄이 — 비동기 다운로드 → 뷰 크기에 맞춘 다운샘플 디코딩 → 메모리/디스크 캐시 → 스크롤로 벗어나면 로드 취소까지 다 처리한다.

AsyncImage(
    model = "https://.../photo.jpg",
    contentDescription = "프로필",                 // 접근성([18]) — 낭독기가 읽음
    contentScale = ContentScale.Crop,
    modifier = Modifier.size(64.dp).clip(CircleShape)
)
// 자동: 64dp에 맞춰 다운샘플(48MB 원본을 안 들고 있음)·캐시·스크롤 이탈 시 취소·placeholder/error

여기서 결정적 이점자동 다운샘플링이다 — Coil은 표시될 크기(64dp)를 보고 그에 맞게 줄여서 디코딩하므로, 4000px 원본을 메모리에 통째로 안 들고 있는다. 거기에 메모리·디스크 캐시(다시 스크롤해 돌아와도 재다운로드 안 함), placeholder/error 이미지, 원형·블러 같은 변환까지 공짜다. (Glide·Picasso도 같은 역할의 View 시대 라이브러리지만, Compose/Kotlin 프로젝트엔 Coil이 1순위다.)

미디어 재생 — Media3 / ExoPlayer

영상·오디오 재생은 Media3(과거 ExoPlayer가 androidx.media3로 이전)를 쓴다 — 기본 MediaPlayer보다 강력해 스트리밍(HLS/DASH)·적응 비트레이트(네트워크에 따라 화질 자동 조절)·자막·백그라운드 재생을 처리한다.

val player = ExoPlayer.Builder(context).build().apply {
    setMediaItem(MediaItem.fromUri(videoUrl)); prepare()
}
// Compose에선 AndroidView로 PlayerView를 호스팅([18] interop)

여기서 절대 잊지 말 것플레이어는 무거운 네이티브 자원(코덱·버퍼·소켓)을 쥔다. 화면이 사라질 때 release()를 안 하면 그 자원이 통째로 누수되고 다른 영상이 안 열리거나 메모리가 샌다([01 생명주기]에 맞춰 해제). 백그라운드 재생(음악)은 MediaSessionService알림과 함께 한다.

카메라 — CameraX

원시 Camera2 API상태 머신이 복잡해 직접 다루기 어렵다. CameraX는 이를 단순화한 생명주기 인지 라이브러리다 — 프리뷰·촬영·이미지 분석(ML) 세 유스케이스를 생명주기에 바인딩한다.

다이어그램 로딩 중…

bindToLifecycle로 묶었기에 — 화면이 보이면 카메라가 켜지고, 사라지면 자동으로 꺼져 해제된다(생명주기 인지의 산물, [01]). 카메라를 직접 열고 닫는 실수가 없어진다. 물론 카메라 권한([10])이 선행돼야 한다.

메모리 관점 — 무거운 자원은 줄이고 제때 놓는다

이미지·미디어 사고는 거의 두 원인으로 모인다.

  • 이미지를 원본 크기로 들고 있기 — 위 48MB 계산처럼 OOM의 단골 원인이다. 표시 크기에 맞춰 다운샘플(Coil이 자동)하고 캐시 크기를 제한한다.
  • 네이티브 자원을 안 놓기 — 미디어 플레이어·카메라는 생명주기에 맞춰 반드시 release/unbind([01·12]). Bitmap 누수와 함께 OOM·자원 고갈의 흔한 원인이다.

결론은 — 직접 디코딩·관리하지 말고 검증된 라이브러리에 맡기고, 그 라이브러리가 못 놓는 무거운 자원(플레이어·카메라)만 생명주기에 묶어 해제하는 것이다.

정리 — 라이브러리에 맡기고 생명주기에 묶는다

이미지·미디어의 핵심 — 이미지는 Coil(AsyncImage)로 자동 다운샘플·캐시·취소(원본을 안 들고 있어 OOM 방지), 영상·오디오는 Media3/ExoPlayer(스트리밍·적응 비트레이트, 생명주기 release 필수), 카메라는 CameraX(bindToLifecycle로 자동 해제·권한 선행), 모두 무거운 자원이라 표시 크기로 줄이고 제때 해제. 직접 다루지 말고 라이브러리 + 생명주기가 메모리 안전의 지름길이다.

한 줄 요약 — 이미지/미디어는 메모리 지뢰밭(4000×3000 디코딩≈48MB→OOM). Coil AsyncImage(표시 크기로 자동 다운샘플·메모리/디스크 캐시·스크롤 취소·placeholder·contentDescription). Media3/ExoPlayer(스트리밍·적응 비트레이트·자막·생명주기 release 필수·백그라운드는 MediaSessionService). CameraX(bindToLifecycle·Preview/Capture/Analysis·자동 해제·권한 선행). 무거운 자원은 줄이고 제때 놓는다.

(출처: Coil docs / Android — Media3 ExoPlayer / CameraX architecture / CameraX / Load bitmaps efficiently.)

알림·푸시 — Notification·채널·FCM위치·센서 — FusedLocation·SensorManager·권한