이전 글에서는 Retrofit Annotation을 알아보며 마무리 했었습니다. 이번엔 Retrofit으로 이미지를 전송하기 위한 Annotation에 대해 알아볼게요. 이 함수는 이미지 전송을 위한 예시 함수입니다. 하나씩 뜯어보죠
@Multipart
@POST(URL_TO_UPLOAD_IMAGE)
suspend fun uploadImage(
@Part image: MultipartBody.Part
): Response<ImageDataClass>
Multipart
HTTP 클라이언트가 파일, 데이터등 여러 종류의 데이터를 동시에 HTTP 서버로 보내기 위한 방식으로 이름에서 유추할 수 있듯이 웹 클라이언트에서 요청을 보낼 때, HTTP Protocol Body에 데이터를 여러 부분으로 나눠 보내는 것 입니다.
안드로이드에서 이미지를 전송하기 위해선 @Multipart 어노테이션을 명시해 줍니다.
https://square.github.io/retrofit/2.x/retrofit/retrofit2/http/Multipart.html
Part
Multipart 요청의 한 파트를 나타 냅니다. @Multipart 와 같이 쓰이며 multipart 요청의 각 부분을 표현 합니다. 공식문서에 의하면 Part 어노테이션이 있는 매개변수 타입은 3가지가 있습니다.
https://square.github.io/retrofit/2.x/retrofit/index.html?retrofit2/http/Part.html
- Multipart.Part Type : @Part MultipartBody.Part part와 같이 어노테이션을 사용하면 해당 파트의 내용이 직접 사용됩니다.파트의 이름을 어노테이션에서 생략할 수 있습니다.
- RequestBody Type : @Part("foo") RequestBody foo와 같이 어노테이션을 사용하면 RequestBody의 값이 직접 사용되며, 해당 값의 컨텐츠 타입이 함께 사용됩니다. 파트의 이름을 어노테이션에서 지정해주어야 합니다.
- 다른 객체 타입인 경우: @Part("foo") Image photo와 같이 어노테이션을 사용하면, 해당 객체는 Retrofit의 컨버터를 통해 적절한 형태로 변환됩니다. 파트의 이름을 어노테이션에서 지정해주어야 합니다.
@Multipart
@POST("/db/upload") // 서버 엔드포인트 URL, HTTP POST 요청
suspend fun registStore(
@Part storeimage: MultipartBody.Part, // 이미지 데이터를 나타내는 파라미터
@Part("storename") storename: RequestBody,
@Part("ceoName") ceoName: RequestBody,
@Part("CRN") CRN: RequestBody,
@Part("contact") contact: RequestBody,
@Part("address") address: RequestBody,
@Part("latitude") latitude: RequestBody,
@Part("longitude") longitude: RequestBody,
@Part("kind") kind: RequestBody
): ResponseBody
- 위 예시를 REST 서버로 이미지와 텍스트 데이터를 전송합니다. 이미지를 전송하기 위해 @Multipart 어노테이션을 사용하고 REST API의 POST를 사용해 데이터를 전송했습니다.
- 이미지는 Multypartbody.part 타입으로 보내고 그 외 String Type 데이터는 RequestBody 타입으로 전송했습니다.
val file = File(absolutelyPath(imgUri, context))
val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull())
val body = MultipartBody.Part.createFormData("storeimage", file.name, requestFile)
// 절대경로 변환
fun absolutelyPath(path: Uri, context : Context): String {
val proj: Array<String> = arrayOf(MediaStore.Images.Media.DATA)
val c: Cursor? = context.contentResolver.query(path!!, proj, null, null, null)
val index = c?.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
c?.moveToFirst()
val result = c?.getString(index!!)
return result!!
}
- file : 갤러리에서 가저온 이미지 Uri와 context를 전달해 파일의 절대 경로를 저장합니다.
- requestFile : RequestBody 파일 객체를 생성하고 MIME Type은 "image/*" 로 지정합니다.
- body : Multipary.part 객체를 생성합니다.
📌 Error Cause !
개요
이미지와 함께 매장 정보를 서버에 등록하는 과정에서 매장 정보를 객체로 보내면 코드가 더 줄어들것이라는 생각에 @Body 어노테이션을 사용해 객체를 전달하도록 수정했지만 Exception이 발생했습니다.
@Multipart
@POST("db/upload")
fun registStore(
@Part storeimage: MultipartBody.Part,
@Body requestBody: StoreBasicInfo // Error
) : Call<String>
@Body를 사용하면 HTTP 요청의 body에 객체를 직렬화해서 넣어줍니다.
하지만 @Multipart나 @FormUrlEncoded와 함께 사용할 수 없습니다.
일반적으로 HTTP 요청은 하나의 본문만을 가지며, 이 본문은 단일 데이터 형식이여야 합니다.
@Body 어노테이션을 사용하면 해당 어노테이션이 붙은 매개변수를 HTTP 요청의 본문으로 사용해 이후에 오는 @Multipart나 @FormUrlEncoded 어노테이션과 충돌이 발생합니다 !!
이를 해결하기 위해 객체로 만들어 주었던 파라미터를 모두 @Part로 나눠 전송해주었습니다.
interface StoreInfoApi {
/**
* Multipart : 이미지, 오디오, 비디오 등과 같은 여러 종류의 데이터를 서버에 업로드하거나 전송할 때 사용
* 업로드할 데이터를 @Part 어노테이션을 사용해 파라미터로 지정
* */
@Multipart
@POST("/db/upload") // 서버 엔드포인트 URL, HTTP POST 요청
fun registStore(
@Part storeimage: MultipartBody.Part, // 이미지 데이터를 나타내는 파라미터
@Part("storename") storename: RequestBody,
@Part("ceoName") ceoName: RequestBody,
@Part("CRN") CRN: RequestBody,
@Part("contact") contact: RequestBody,
@Part("address") address: RequestBody,
@Part("latitude") latitude: RequestBody,
@Part("longitude") longitude: RequestBody,
@Part("kind") kind: RequestBody
): Call<String>
// 서버로부터 받은 응답을 처리하기 위한 Retrofit Call 객체를 반환
// @Part로 나눠진 파라미터를 하나의 객체로 만들어 전송
}
'Android' 카테고리의 다른 글
Retrofit Network Time out (0) | 2023.12.15 |
---|---|
안드로이드 HTTP 보안 정책 (0) | 2023.12.14 |
Android Network Programming 1 (0) | 2023.12.13 |
Android Studio MySQL 연동 6 (0) | 2023.12.08 |
Android Studio MySQL 연동 5 (0) | 2023.12.08 |