리뷰 작성 화면에서 이미지 업로드는 흔한 기능이다.
하지만 “이미지를 어떻게 업로드하느냐”에 따라 앱의 안정성과 성능은 크게 달라진다.
이 글에서는
Compressor)를 도입하게 된 배경suspend 함수여야 했는지를 실제 문제 해결 경험을 바탕으로 정리한다.
리뷰 작성 기능에서 다음과 같은 문제가 반복적으로 발생했다.
사용자가 선택한 이미지를 압축 없이 그대로 업로드
이미지 크기: 약 5~6MB
결과:
사진을 선택하는 순간 바로 업로드 실행
사용자가 리뷰 작성을 취소해도
즉, 리뷰 작성과 무관하게 불필요한 업로드가 발생하는 구조였다.
문제를 다음 두 단계로 나누어 해결했다.
이를 위해 다음 선택을 했다.
suspend 함수로 업로드 구조 재설계이미지 압축을 위해 사용한 라이브러리는 zetbaitsu의 Compressor이다.
quality) 조절 가능val compressedFile =
Compressor.compress(context, file) {
quality(80)
}
이미지 품질 저하가 체감되지 않는 수준에서 전송 크기를 대폭 줄일 수 있었다.
이미지 압축 코드를 보면 겉으로는 단순해 보인다.
Compressor.compress(context, file)
하지만 내부적으로는 전혀 단순하지 않다.
이미지 압축 과정에는 다음 작업들이 포함된다.
이는 모두 CPU + IO를 동시에 사용하는 무거운 작업이다.
만약 이 로직을 UI 스레드에서 실행한다면:
즉, 이미지 압축은 반드시 메인 스레드 밖에서 실행되어야 한다.
그래서 이미지 압축 함수는 다음과 같이 정의했다.
private suspend fun compressImage(
imageString: String?
): MultipartBody.Part? {
if (imageString != null) {
val file = File(imageString)
val compressedFile =
Compressor.compress(this@ReviewWriteRateActivity, file) {
quality(80)
}
val requestFile =
compressedFile.asRequestBody("image/*".toMediaTypeOrNull())
return MultipartBody.Part.createFormData(
"multipartFileList",
compressedFile.name,
requestFile
)
}
return null
}
여기서 핵심은 suspend 키워드다.
suspend 함수는
Coroutine Dispatcher 위에서 실행되며,
작업 중 스레드를 점유하지 않는다.
withContext(Dispatchers.IO) {
compressImage(path)
}
이미지 압축 → 업로드는
순서가 중요한 작업이다.
Coroutine을 사용하면 다음처럼 자연스럽게 표현할 수 있다.
viewModelScope.launch {
val imagePart = compressImage(path)
uploadReview(imagePart)
}
콜백이나 Handler 없이 “위에서 아래로 읽히는 코드”가 된다.
suspend 함수는 Coroutine Scope에 묶인다.
즉,
기존 구조는 다음과 같았다.
이를 다음처럼 변경했다.
suspend 기반 이미지 압축이로 인해
라는 효과를 얻었다.
