본문 바로가기

Android

Android Network Programming 2

 

 

Android Network Programming 1

이 글 을 마지막으로 기본적인 Docker 기반의 서버 구축이 완료 되었습다. 추후 테이블도 변경하고 수정할 사항이 많겠지만 빨리 내가 직접 만든 서버와 통신을 해보고 싶어서 메다닥 달려왔습니

chanho-study.tistory.com

 

이전 글에서는 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

 

Multipart (Retrofit 2.7.1 API)

 

square.github.io

Part

Multipart 요청의 한 파트를 나타 냅니다.  @Multipart 와 같이 쓰이며 multipart 요청의 각 부분을 표현 합니다.  공식문서에 의하면 Part 어노테이션이 있는 매개변수 타입은 3가지가 있습니다.

https://square.github.io/retrofit/2.x/retrofit/index.html?retrofit2/http/Part.html

 

Retrofit 2.7.1 API

 

square.github.io

  • 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이 발생했습니다. 

storeBasicInfo.class

@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