retrofit multipart로 이미지 전송하기

나고수·2022년 8월 28일
2

1일1공부

목록 보기
60/68

공식문서
참고블로그
참고블로그2

service

interface FileUploadService {

    @Multipart 
    @POST(".") 
    //원래는 baseURL 뒤에 이 api를 위한 동적인 주소를 적어줘야 하지만, 
    //baseURL 뒤에 붙어 있는 다른 주소가 없다면 . 을 찍어주면 된다.
    suspend fun apiPostFile(
        @PartMap partMap: Map<String,@JvmSuppressWildcards RequestBody>,
        @Part files: List<MultipartBody.Part>
    ): retrofit2.Response<ResponseWrapper<FileUploadResponse>>

}

JvmSuppressWildcards란?

multiPart란?

  • 웹 클라이언트가 요청을 보낼 때, http 프로토콜의 바디 부분에 데이터를 여러 부분으로 나눠서 보내는 것.
    파일을 한방에 바디에 넣어 보내기엔 너무 무리 이기 때문에, 파일을 잘게 나눠서 보낸다고 이해하면 된다.

Requestbody란?

  • multipart 통신을 위해서는 requestBody 라는 okhttp에서 제공하는 타입(아닏듯? http통신 규칙인듯하다.현재 이부분 다시 공부중인데 정확히 공부하고 다시 적어야징)으로 모든 데이터를 보내야합니다.
    파일 뿐만 아니라 String, Int 등 primitive 타입 역시 모두 requestBody 타입으로 보내야 합니다.
    MultipartBody.Part 역시 body 부분은 requestBody 타입입니다.
 class Part private constructor(
    @get:JvmName("headers") val headers: Headers?,
    @get:JvmName("body") val body: RequestBody
  ) 

primitive type -> requestBody

//string이 아닌 데이터 -> requestBody
//toString()으로 String 타입으로 바꾼 후 toRequestBody()
val num = 3
var requestNum: RequestBody =
  num.toString().toRequestBody("text/plain".toMediaTypeOrNull())

//string -> requestBody
val name = "gosoo"
var requestName: RequestBody =
  num.toRequestBody("text/plain".toMediaTypeOrNull())

//공식문서에는 .toRequestBody(MultipartBody.FORM) 이라고 되어 있어서 이렇게했는데
//    val FORM = "multipart/form-data".toMediaType()
//블로그에는  .toRequestBody("text/plain".toMediaTypeOrNull()) 라고 되어 있었습니다.
//찾아보니, 저 부분은 인코딩을 어떻게 할지 결정하는 부분인데
//아래 사진과 같다고 합니다.
//지금은 파일/이미지를 데이터로 넣는 부분이 아니니 text/plain 을 써도 될 것 같습니다.
//특히나 데이터에 공백이 있다면 무조건 text/plain 타입을 써야 하지 않을까 싶습니다.

위에 방식대로 requestBody로 변환된 primitive 타입 데이터는 두가지 방법으로 데이터를 담아 보낼 수 있습니다.

1. 보내야 할 primitive 타입의 파라미터가 많을 때 -
@PartMap partMap: Map<String, RequestBody>
requestBody를 map 으로 묶어 한번에 보내기

// create a map of data to pass along
//createPartFromString는 위의 primitive type -> requestBody 부분임.
val description = createPartFromString("hello, this is description speaking")
val place = createPartFromString("Magdeburg")
val time = createPartFromString("2016")val partMap : HashMap<String, RequestBody> = hashMapOf() 
partMap.put("description", description)
partMap.put("place", place)
partMap.put("time", time)


2. 보내야 할 primitive 타입의 파라미터가 하나 일 때 -
@Part ("description") description : RequestBody
requestBody 하나만 보내기
//description에는 api 에서 요구하는 파라미터 이름을 넣어주면 된다.

val description = createPartFromString("hello, this is description speaking")

file -> requestBody

보통 이미지 하나만 보내는 경우보다 , 여러개의 이미지를 한번에 보내는 경우가 많기 때문에 여러개의 이미지를 리스트로 묶어 보내는 방법을 설명하겠음.

  fun getPartList(
        uriArray: List<Uri>,
        context: Context
    ): List<MultipartBody.Part> {
        return uriArray.mapNotNull {
        it.asMultipart("files", context.contentResolver)
//        return uriArray.mapNotNull {
//            val file = File(it.path)
//            MultipartBody.Part
//                .createFormData(
//                    "files",
//                    file.name,
//                    file.asRequestBody(context. contentResolver.getType(it)?.toMediaType())
//                )
//        }
    }}

//선택된 파일의 uri -> multipart
//api 29 이전에는 위의 주석된 코드로 작동 가능했으나
//안드로이드 보안 정책 변경으로 인해 아래의 코드로 작동 가능함
// 출처 : https://ohdbjj.tistory.com/49
 @SuppressLint("Range")
    fun Uri.asMultipart(name: String, contentResolver: ContentResolver): MultipartBody.Part? {
        return contentResolver.query(this, null, null, null, null)?.let {
            if (it.moveToNext()) {
                val displayName = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME));
                val requestBody = object : RequestBody() {
                    override fun contentType(): MediaType? {
                        return contentResolver.getType(this@asMultipart)?.toMediaType()
                    }

                    override fun writeTo(sink: BufferedSink) {
                        sink.writeAll(contentResolver.openInputStream(this@asMultipart)?.source()!!)
                    }
                }
                it.close()
                MultipartBody.Part.createFormData(name, displayName, requestBody)
            } else {
                it.close()
                null
            }
        }
    }

궁금한 점

  • 회사에서는 s3를 이용하기 때문에 파일 용량에 제한이 없었다.
    하지만 만약 서버통신에 파일 용량 제한이 있다면,
    파일을 선 압축 후 멀티파트로 변환해서 서버통신을 해야 하는지?
  • Uri.asMultipart 이 방법은 이미지/pdf 등 파일 종류 상관없이 작동하는지?
profile
되고싶다

0개의 댓글