개요
Android에서 Jetpack Compose 기반 UI를 통해 서버에서 이미지를 업로드하고자 합니다. 이때 Android 10(버전 코드 Q) 이상에서 저장소 접근 정책이 달라짐에 따라 이미지를 불러오고 접근하는 과정에서 이슈를 겪었습니다. 그리고 파일을 직접 업로드 하기 위해서는 multipart/form-data 형식의 통신을 사용하여야 합니다. 이에 대한 전반적인 내용을 다뤄보고 서버에 이미지를 업로드를 하는 과정을 코드를 통해 살펴보겠습니다.
Compose에서 이미지 선택
1 |
|
이미지를 선택하는 액티비티 런처를 통해 컴포저블의 onUpload
를 통해 imageUri
를 업데이트하고, 이를 Image
컴포넌트에 그리는 방식입니다.
코드 주석에서 확인할 수 있듯이 Android 10 미만의 버전에서는 deprecated
된 API인 getBitmap
을 사용합니다. 그 이상부터는 ImageDecoder
클래스를 사용합니다. 다만 여러 버전의 기기 호환성을 고려해서, 위 코드와 같이 API level 28 이하 버전에서의 동작 코드 또한 작성해주셔야 합니다.
이미지를 선택하는 액티비티 런처에서는 결괏값으로 URI를 반환합니다. 따라서 onResult
프로퍼티에는 (Uri?) -> Unit
타입의 고차 함수를 넣어주어야 합니다. 위 코드의 UploadImage
컴포저블의 매개변수인 imageUri
와 onUpload
는 각각 실시간으로 선택한 이미지를 표시하고 저장하기 위해 ViewModel
에 선언된 state와 메서드임을 가정합니다
URI to File
이미지를 서버에 업로드하기 위해서는 URI가 아닌 File 형태가 필요합니다. 기존에는 URI를 통해 File의 실제 경로를 가져오는 방식으로 서버에 올릴 이미지를 특정했습니다. 그런데 Android 10 이상에서는 저장소 접근 정책이 변경됨에 따라, 사용자가 선택한 파일의 URI에서 파일의 절대 경로를 얻어내는 것이 불가능해졌습니다. 따라서 불러온 이미지의 URI로부터 임시 파일로 복사본을 생성한 뒤, 해당 복사본을 서버에 업로드하는 방식으로 구현해야 합니다.
1 |
|
File 처리를 맡는 FileUtil
과 Uri를 담당하는 UriUtil
2가지 파일로 구분하여 작성했습니다. 코드를 보시다시피, 경로를 정확히 알고있는 임시 파일을 생성한 후 해당 파일에 바이트 스트림을 열어 불러온 이미지의 URI를 통해 내용을 복사하는 방식입니다. 이미지를 실제로 업로드할 때에는 이 임시 파일을 올리게 됩니다.
Retrofit에서 Multipart 전송하기
HTTP 통신으로 파일을 직접 전송하기 위해서는 application/json
이 아닌 multipart/form-data
방식을 사용합니다. 이때 안드로이드에서 가장 흔하게 사용하는 Retrofit
에서 이를 지원합니다. 기존의 방식에서 데이터 소스에 직접 접근하면 API 인터페이스만 약간 다를 뿐 전체적은 틀은 동일합니다.
1 |
|
우선 인터페이스 메서드에 @Multipart
어노테이션을 추가합니다. 이후 multipart 타입의 이미지 파일은 @Part
어노테이션을, key-value
형식의 부가 정보 텍스트는 @PartMap
어노테이션을 사용합니다. 이때 유의할 점은 imageInfo
의 타입으로 immutable한 Map
을 사용할 시 작동하지 않으며, HashMap
또는 MutableMap
을 사용해야 정상 작동합니다.
마치며
Compose를 통해 Android 10 버전 이상에서 프로젝트를 진행하며 겪었던 이미지 업로드 이슈 해결 과정에 대해 종합적으로 정리해보았습니다. 많은 분들께 도움이 되었기를 바랍니다.