중요) 본 글은 원하는 사이즈를 보냈을 때 리사이즈 해주는 서버가 필요합니다.
이전에 회사에서 Glide -> Coil 로 마이그레이션 작업을 진행하면서 경험한 내용으로 이미지 사이즈에만 중점을 두고 있습니다. 또한 기승전결이 부족하고 도움이 되지 않을 수도 있습니다.
OnDemand image resizing 이란 ?
- 클라이언트의 요청에 따라 리사이징 된 이미지가 제공 되는 것을 의미합니다.
일단은 Coil 라이브러리내에서 사이즈 관련하여, 이미지를 효율적으로 로드하기 위한 방법이 존재하기 때문에 이부분을 먼저 보고 가겠습니다.
sizeResolver 부분이 이미지를 로드할 때, 사이즈를 결정하는 부분입니다. 따로 명시적으로 처리하지 않으면, resolveSizeResolver() 함수가 불리우게 됩니다.
// https://github.com/coil-kt/coil/blob/main/coil-base/src/main/java/coil/request/ImageRequest.kt#L972
private fun resolveSizeResolver(): SizeResolver {
val target = target
if (target is ViewTarget<*>) {
val view = target.view
// CENTER and MATRIX scale types should be decoded at the image's original size.
if (view !is ImageView || view.scaleType.let { it != CENTER && it != MATRIX }) {
return ViewSizeResolver(view)
}
}
return SizeResolver(OriginalSize)
}
함수의 내용을 대략적으로 훑어 보면, size 를 정할 때, 먼저 사용자가 ImageView 같은 뷰에서 로드를 하는 것인지, 혹은 BackgroundThread 같은 곳(코드에서는 Non-Target 이라 부릅니다.)에서 뷰를 로드하는 것인지 확인 합니다.
뷰 타겟이 아닌 경우, 리사이즈할 뷰의 width, height 도 없기 때문에 원본 이미지(OriginalSize) 로 이미지를 로드 합니다. 그리고 뷰 타겟이면서 뷰가 이미지 뷰가 아니거나, scaleType 이 CENTER, MATRIX 가 아니라면 View 의 사이즈를 이용하여 이미지를 로드 합니다.
위와 같은 식으로 미리 뷰의 사이즈를 계산하여, 이미지를 로드할 때 메모리를 효율적으로 이용하고 있습니다.
하지만 그전에 보여줄 이미지 URL 이 매우매우 큰 사이즈면 어떻게 될까요 ?
먼저 현재 보여지는 화면에서 해당 URL 이 사용된 적이 있는지 확인하고, 이후에는 메모리 캐시 -> 캐시 디렉토리 를 순회하면서 없다면 결과적으로 다운 받게 될 것입니다. 이때 사이즈에 비례하여 병목이 생기게 됩니다. 또한 아래 이미지를 보면 ExceptionCatchingSource 에서 이미지를 읽어와서 decode 할 때 IOException 을 던질 수도 있습니다 (inJustDecodeBounds 로 로드전 dimension 을 읽어오고 있네요)
이런 부분들을 최소화 하여, ViewTarget 인 경우 View 의 사이즈를 미리 알아내어 이미지를 리사이징 해주는 서버에 View 의 사이즈도 함께 전달하여 애초부터 뷰에 맞는 이미지를 가져오는 것이 핵심입니다.
이제 우리는 Coil 에서 이미지 로드 요청을 가로챈 뒤에 Request URL 에 뷰의 사이즈를 넣어주는 작업을 진행해야 합니다.
그리고 다행스럽게도, Coil 의 이미지 파이프 라인은, Interceptors, Mappers, Fetchers, Decoder 단계로 진행 됩니다.
간단하게만 정리하고 넘어가자면
Interceptors : 이미지 요청을 할 떄, 관찰하거나 변환, 재시도 등을 구현할 수 있습니다. Coil 예제에서는 커스텀 Cache layer 를 만들어서 예시를 들고 있습니다.
Mappers : 커스텀 데이터 타입에 대해 지원하도록 처리할 수 있습니다. 가령 썸네일과 원본 이미지를 가지고 있는 데이터 클래스에서 썸네일 이미지만 사용하도록 변경 할 수 있습니다.
Fetchers : 데이터를 처리하는 방식으로, HttpUriFetcher, HttpUrlFetcher, BitmapFetcher, DrawableFetcher, FileFetcher 등 제공하고 있습니다. 가령 File 중 영화 관련 JPG, JPEG, PNG 등만 읽어오는 MovieIconFectcher 를 만들어 볼 수 도 있습니다.
Decoders: 기본 이미지 포맷 뿐만 아니라, GIF, SVG, TIFF 등과 같은 포맷도 지원할 수 있도록 도와주는 클래스 입니다.
우리가 사용할 파이프라인은 Interceptor 부분 입니다.
먼저 앱에서 요청되는 이미지 전체에 대해 적용되어야 하기 때문에, 적당히 ImageLoader 클래스를 구현 합니다.
interface ImageLoaderModule {
fun get(): ImageLoader
}
class ImageLoaderModuleImpl @Inject constructor(@ApplicationContext val context: Context) :
ImageLoaderModule {
@OptIn(ExperimentalCoilApi::class)
fun getImageLoader(): ImageLoader = ImageLoader.Builder(context)
.precision(Precision.AUTOMATIC)
.componentRegistry {
add(OnDemandResizingInterceptor()) // 이후에 구현할 클래스
}
.okHttpClient {
OkHttpClient.Builder()
.cache(CoilUtils.createDefaultCache(context))
.build()
}
.build()
override fun get(): ImageLoader = getImageLoader()
}
componentRegistry 부분에 Interceptor 를 추가해두면 이후에 요청이 왔을 때 이를 가로채 수정을 할 수 있습니다.
참고로 기본으로 add 되어 있는 interceptor 는 아래와 같습니다 (아주 많이 제공해주네요)
그리고 OnDemandResizingInterceptor 를 구현합니다.
@ExperimentalCoilApi
class OnDemandResizingInterceptor : Interceptor {
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
var request = chain.request
when (val url = chain.request.data) {
is String -> {
if (URLUtil.isHttpUrl(url) || URLUtil.isHttpsUrl(url)) {
val size = chain.size // 사이즈를 가져옴
val imgUrl = when (size) {
is PixelSize -> "$url?width=${size.width}"
is OriginalSize -> "$url?width=Origin"
else -> url
}
request = chain.request.newBuilder().data(imgUrl).build()
}
}
}
return chain.proceed(request)
}
}
interceptor 에서 request 를 가져와 해당 요청이 String 이라면 Http 혹은 Https Url 인지 확인하여 요청 URL 에 width QueryString 을 달아주고 새로운 요청을 만들어서 리턴 시켜 줍니다. 이제는 View 의 사이즈에 맞는 Url 을 요청할 수 있게 되었습니다. 이전보다 좀더 효율적으로 메모리를 사용할 수 있게 되었습니다.
이 작업이 진행되기 위해서는 서버에서 리사이즈 기능 구현이 선행 되어야 합니다.
'Android > Today I Learned' 카테고리의 다른 글
Compose Navigation - viewmodel 사용할 때 주의할 점 (0) | 2021.12.25 |
---|---|
Android Font 고군분투기 (1) | 2021.12.06 |
Glide cache 제거 하기 (0) | 2021.11.21 |
멀티 모듈에서 Missing required view with ID 에러 해결 (1) | 2021.06.07 |
테스트 케이스로 작성해보는 RxJava 함수 (0) | 2021.02.07 |