이미지를 로드하는 외부 라이브러리로 주로 쓰이는 3가지가 Coil, Glide, 그리고 Picasso가 있다.
얘네는 무슨 차이가 있을까?
코틀린으로 만든 녀석인데, 자체적으로 코루틴을 사용하는 특징이 있다.
그렇기 때문에 다른 라이브러리에 비해 메모리 사용량이 상대적으로 매우 적은데
https://jizard.tistory.com/224 에서 비교한 자료를 볼 수 있다.
그리고 코틀린으로 이루어 졌기때문에 ImageView를 확장 함수로 지원하고 함수형 언어 덕에 코드를 간결하게 구성할 수 있다.
https://velog.io/@jshme/Android-Image-Loader-Library
에서 Coil, Glide, Picasso의 장단점을 비교한걸 볼 수 있다.
https://coil-kt.github.io/coil/changelog/ 이후에 업데이트 된건 여기서 찾아 볼 수 있다.
Compose와 OkHttp등의 라이브러리를 지원하고 있다.
코일의 장점을 알았으니 사용법을 알아보자
implementation("io.coil-kt:coil:2.5.0")
라이브러리를 추가해주고
// URL
imageView.load("https://example.com/image.jpg")
// File
imageView.load(File("/path/to/image.jpg"))
URL이든 File이든 load로 이미지를 로드할 수 있다.
imageView.load("https://example.com/image.jpg") {
crossfade(true)
placeholder(R.drawable.image)
transformations(CircleCropTransformation())
}
후행 람다를 사용해서 추가적으로 요청을 할 수 있는데
crossfade 는 크로스 페이드 효과, 이전 이미지에서 새 이미지로 부드럽게 전환되는 효과를 준다.
placeholder(R.drawable.image)는 이미지가 로드 되는 동안 표시될 임시 이미지를 설정하는데 스켈레톤 UI등을 표시 할 수 있다.
CircleCropTransformation()은 이미지를 원형으로 잘라낸다.
추가적인 기능들이 더 있는데
코드를 까봤다.
보기만 해도 어지러운 코드가 날 반겨준다
각 필드랑 메소드가 세세하게 적혀있어 알아두면 좀더 세밀한 제어가 가능할 것이다.
defaults: 이미지 로딩 요청에 적용될 기본 옵션
data: 로드할 이미지의 소스. URL, URI, 파일, 리소스 ID 등 다양한 형태를 지원
target: 이미지가 로드되면 결과를 받을 대상
listener: 이미지 로딩 과정에서 발생하는 이벤트를 수신하는 리스너. onStart, onCancel, onError, onSuccess 등의 콜백 메소드를 포함
memoryCacheKey: 메모리 캐시에서 사용될 키
bitmapConfig: 이미지를 디코딩할 때 사용할 Bitmap.Config 설정
colorSpace: 이미지의 색상 공간을 정의
transformations: 이미지에 적용할 변환(예: CircleCropTransformation) 목록
transitionFactory: 이미지가 로드될 때 적용할 전환 애니메이션을 생성하는 팩토리
headers: HTTP 요청 헤더
tags: 요청과 관련된 추가 메타데이터를 저장하는 태그
memoryCachePolicy, diskCachePolicy, networkCachePolicy: 메모리, 디스크, 네트워크 캐시 정책
interceptorDispatcher, fetcherDispatcher, decoderDispatcher, transformationDispatcher: 각 처리 단계에 사용될 코루틴 디스패처
parameters: 이미지 요청에 전달될 추가 파라미터
placeholderMemoryCacheKey, placeholderResId, placeholderDrawable, errorResId, errorDrawable, fallbackResId, fallbackDrawable: 이미지 로딩 중, 오류 발생 시, 또는 데이터가 없을 때 표시될 대체 이미지 리소스나 드로어블
lifecycle, sizeResolver, scale: 이미지 요청의 생명주기, 크기 해결 방법, 스케일링 모드를 설정
어떤 것들로 구성 되어 있는지 알았다.
이제 함수를 알아보자
data(data: Any?): 로드할 이미지의 데이터 소스를 설정
memoryCacheKey(key: String?): 메모리 캐시에 사용할 키를 설정, 설정하지 않으면, 코일이 자동으로 계산
memoryCacheKey(key: MemoryCache.Key?): 메모리 캐시에 접근할 때 사용할 키를 설정
diskCacheKey(key: String?): 이미지를 디스크에 캐싱
listener(onStart, onCancel, onError, onSuccess): 콜백 설정
dispatcher(dispatcher: CoroutineDispatcher): 이미지 로딩에 사용할 코루틴 디스패처를 설정
interceptorDispatcher(dispatcher: CoroutineDispatcher): 요청의 가로채기(interception)에 사용할 디스패처를 설정, 성능 최적화하려고 사용한다고 한다.
transformations(transformations: List): 이미지에 적용할 변환(예: 둥근 모서리, 원형 크롭 등) 목록을 설정
bitmapConfig(config: Bitmap.Config): 이미지를 로드할 때 사용할 Bitmap.Config를 지정하는 데 사용
Bitmap.Config란?
Android에서 비트맵 이미지의 픽셀 형식을 정의, 이미지의 표현 방식 및 메모리 사용량에 영향을 미친다.예시)
ARGB_8888: 기본값이며, 가장 높은 품질의 컬러와 알파 채널을 제공
RGB_565: 알파 채널을 지원하지 않으며, 각 픽셀이 2바이트를 사용
ALPHA_8: 오직 알파 채널만을 저장하며, 각 픽셀 당 1바이트를 사용. 주로 투명도 맵에 사용함
colorSpace(colorSpace: ColorSpace): 이미지를 로드할 때 사용할 색공간을 지정
ColorSpace란?
색공간(ColorSpace)은 색상을 표현하고 재현하는 방식에 대한 특정 기준을 제공하는건데디지털 이미지 처리에서 색공간은 이미지의 색상을 어떻게 인코딩하고 디스플레이할지 결정한다
그래서 왜 씀?
- 색상 정확도 향상: 특정 색공간을 지정함으로써 이미지의 색상을 더 정확하게 재현할 수 있다.
- 특정 디스플레이 기술과의 호환성: 사용자의 디스플레이 기술(예: sRGB, Display P3 등)에 맞춰 색공간을 지정하면, 이미지가 해당 디스플레이에서 의도한 대로 정확히 보이도록 할 수 있다.
size(size: Size): 요청된 이미지의 너비와 높이를 설정
scale(scale: Scale): 이미지의 스케일링 방식
precision(precision: Precision): 이미지 로딩의 정밀도를 설정. 정확한 크기로 이미지를 로드할지, 가까운 크기로 충분할지를 결정
fetcherFactory(factory: Fetcher.Factory, type: Class): 이미지를 가져오는 데 사용할 커스텀 fetcher 팩토리를 설정
커스텀 fetcher가 왜 필요함?
Fetcher는 특정 유형의 데이터를 로드하는 방법을 정의하는 컴포넌트
- 새로운 데이터 소스 지원: Coil은 기본적으로 네트워크, 리소스, 파일 등의 표준 Android 데이터 소스에서 이미지를 로드하는데, 상황에 따라 표준 소스 외에 다른 곳에서 로드해야 할 수가 있다.
암호화된 파일, 데이터베이스, 커스텀 네트워크 프로토콜 등등에서 이미지를 로드할때 커스텀 페처가 필요,
- 특정 데이터 유형 처리: 특정 유형의 데이터를 이미지로 변환하는 고유한 로직이 필요한 경우(예: JSON에서 이미지 URL 추출, 커스텀 이미지 포맷 처리 등).
- 로딩 동작 커스터마이징: 이미지 로딩 과정에서 추가 로직을 실행하고 싶을 때(예: 로딩 전/후에 로깅, 이미지 처리, 캐싱 전략 변경 등).
allowConversionToBitmap(enable: Boolean): 결과 이미지를 Bitmap으로 변환할지 여부를 설정
이미지가 비트맵 형태로 필요하지 않은 경우, 벡터 등 이미지 뷰에서 표현 가능한 데이터를 받을때 설정해줄수 있다
allowHardware(enable: Boolean): 이미지를 로드할때 하드웨어 가속된 비트맵을 사용할지 여부를 결정
하드웨어 가속 비트맵
GPU에서 직접 렌더링할 수 있어 더 효율적인 렌더링과 성능을 제공한다장점
- 성능 향상: 하드웨어 가속 비트맵은 GPU에서 직접 렌더링되므로, 더 빠른 이미지 렌더링과 스크롤 성능 향상을 기대할 수 있다.
- 메모리 사용 최적화: CPU 메모리 대신 GPU 메모리를 사용하기 때문에, 애플리케이션의 전반적인 메모리 사용량을 최적화할 수 있다.
고려사항
- 하드웨어 가속 비트맵 제한사항: 하드웨어 가속 비트맵은 수정할 수 없으며, 캔버스(Canvas) 작업에 사용할 수 없고, 오래된 기기나 특정 상황에서 호환성 문제가 생길 수 있다.
- 메모리 사용: GPU 메모리 사용은 애플리케이션의 CPU 메모리 사용과 별개로 관리된다. 일부 기기에서는 GPU 메모리가 제한적일 수 있으므로, 대량의 이미지를 처리할 때는 주의가 필요함
이미지 로딩의 성능과 호환성을 조절할 수 있는 유용한 옵션
allowRgb565(enable: Boolean): 비트맵 포맷을 RGB_565로 허용할지 결정
RGB_565는 ARGB_8888에 비해 절반 가량의 메모리를 사용한다. 이는 이미지가 많거나, 메모리 사용량에 민감한 애플리케이션에서 유리할 수 있다.
premultipliedAlpha(enable: Boolean): 이미지를 로드할 때 프리멀티플라이드 알파(Premultiplied Alpha)를 사용할지 여부를 결정
프리멀티플라이드 알파란?
이미지의 각 픽셀 색상 값이 알파 값(투명도)으로 미리 곱해진 상태
이미지 렌더링 시 효율적인 알파 블렌딩을 가능하게 하여, 퍼포먼스를 향상시킬 수 있다장점: 렌더링 시간 단축, GPU에서 보다 효율적인 이미지 합성 가능.
단점: 원본 색상 정보의 일부 손실 가능성, 이미지 처리 시 주의 필요(예: 색상 값 조정).프리멀티플라이드 알파를 사용할지 여부는 성능 향상과 색상 정보의 정확성 사이의 균형을 고려하여 결정해야 한다.
이미지를 다양한 투명도와 함께 복잡한 배경 위에 렌더링해야 하는 경우, 프리멀티플라이드 알파를 활성화하여 렌더링 성능을 최적화할 수 있다.
memoryCachePolicy(policy: CachePolicy): 메모리 캐싱 정책을 설정
CachePolicy 옵션
ENABLED: 기본값, 이미지를 메모리 캐시에 저장하고, 요청 시 캐시된 이미지를 재사용
DISABLED: 메모리 캐싱을 비활성화
READ_ONLY: 이미지를 메모리 캐시에서만 읽을 수 있다. 새로운 이미지는 캐시에 저장되지 않지만, 이전에 캐시된 이미지는 재사용될 수 있다
WRITE_ONLY: 새로 로드된 이미지를 메모리 캐시에 저장할 수 있지만, 캐시에서 이미지를 읽을 수는 없다
diskCachePolicy(policy: CachePolicy): 이미지를 로드할 때 디스크 캐시 정책을 지정
메모리 캐싱과 차이점
메모리 캐싱
저장 위치: 메모리 캐싱은 RAM(랜덤 액세스 메모리)에 데이터를 저장
속도: RAM에 데이터를 저장하기 때문에 매우 빠르게 데이터에 접근할 수 있다
수명주기: 애플리케이션이 실행되는 동안에만 데이터가 유지
용도: 자주 사용되는 데이터나 임시 데이터를 빠르게 접근하기 위해 사용디스크 캐싱
저장 위치: 디스크 캐싱은 하드 드라이브나 SSD와 같은 영구 저장소에 데이터를 저장
속도: 영구 저장소에 데이터를 쓰고 읽는 속도는 RAM보다 느리다. 그러나 메모리에 비해 훨씬 더 많은 양의 데이터를 저장할 수 있다.
수명주기: 애플리케이션이 종료되어도 저장된 데이터는 유지
용도: 대용량 데이터나 오프라인에서도 접근해야 하는 데이터를 저장하기 위해 사용
networkCachePolicy(policy: CachePolicy): 네트워크 캐시 정책을 제어
headers(headers: Headers): 네트워크 요청 시 사용할 HTTP 헤더를 지정
커스텀 HTTP 헤더를 추가하여 이미지를 로드할 때 필요한 인증 정보, 캐시 제어 옵션, 사용자 에이전트 문자열 등을 서버에 전송할 수 있다.
API 키가 필요한 외부 이미지 서비스를 사용하거나, 특정 HTTP 헤더를 요구하는 웹 서비스와의 상호작용에서 특히 유용하다.// 헤더 객체 생성 및 커스텀 헤더 추가 val customHeaders = Headers.Builder() .add("Authorization", "Bearer your_api_token") .add("User-Agent", "your_custom_user_agent") .build() // Coil 이미지 요청 생성 val imageRequest = ImageRequest.Builder(context) .data("https://your.image.source/image.jpg") .headers(customHeaders) // 커스텀 헤더 설정 적용 .build
tag(type: Class, tag: T?): 요청에 태그를 첨부, 태그는 요청과 관련된 메타데이터를 저장하는 데 사용
특정 이미지 로드 요청에 대한 추가적인 정보를 저장하거나, 나중에 이 정보를 사용하여 특정 요청을 쉽게 식별하고 관리할 수 있다.
placeholder(drawableResId: Int),
error(drawableResId: Int),
fallback(drawableResId: Int): 로딩 중, 오류 발생 시, 또는 데이터가 없을 때 사용할 대체 이미지를 설정
target(target: Target?): 이미지 로딩 결과를 받을 타겟을 설정.
crossfade(enable: Boolean), crossfade(durationMillis: Int)
이미지 로드 시 크로스페이드 전환 애니메이션을 사용할지와 그 지속 시간을 설정
lifecycle(lifecycle: Lifecycle?)
이미지 요청의 생명 주기를 관리하는 Lifecycle 객체를 설정