안드로이드 개발할 떄 서버와 통신은 필수적인 요소 중 하나입니다. 이때 이미지, 비디오, 오디오와 같은 바이너리 데이터를 서버에 업로드하거나 다운로드할 경우 Multipart 요청이 자주 사용됩니다. 그렇기에 이번 글에서 Multipart 요청을 사용하는 방법과 관련된 정보를 제공하고자 합니다.
Multipart 요청은 일반적으로 파일 업로드와 같은 바이너리 데이터를 전송할 때 사용되는 HTTP 요청입니다. 이 요청은 여러 파트로 구성되어 있으며, 각 파트는 다른 데이터 유형을 가질 수 있습니다. 예를 들어, Text 데이터와 이미지 데이터를 동시에 보내고 받을 수 있습니다.
안드로이드에서 Multipart 요청을 사용하려면, 주로 OkHttp 라이브러리를 사용합니다. OkHttp는 네트워킹 작업을 쉽게 처리할 수 있는 라이브러리입니다. 먼저 OkHttp와 OkHttp의 Multipart 라이브러리를 추가하려면 build.gradle 파일에 다음 종속성을 추가해야합니다.
1. 종속성 추가
dependencies {
implementation 'com.squareup.okhttp3:okhttp:$version_okhttp'
implementation 'com.squareup.okhttp3:logging-interceptor:$version_okhttp'
}
2. Multipart 요청 생성
val file = File("imageData.jpg")
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("text", "HHT")
.addFormDataPart(
"image",
file.name,
file.asRequestBody("image/jpeg".toMediaTypeOrNull())
)
.build()
val request = Request.Builder()
.url("요청할 URL")
.post(requestBody)
.build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
// Handle fail
}
override fun onResponse(call: Call, response:Response) {
if (response.isSuccessful) {
val responseBody = response.body?.string()
} else {
// Handle error
}
}
이미지 파일을 생성한 뒤 MultipartBody.Builder를 사용하여 Multipart 요청 본문을 생성합니다. 이때 setType(MultipartBody.FORM)을 호출하여 요청 유형을 설정합니다. 그런 다음 텍스트 데이터와 이미지 데이터를 각각의 파트로 추가합니다. 이미지 파일은 RequestBody로 변환되어 요청에 포함됩니다.
그 후 Request.Builder를 사용하여 HTTP 요청을 생성합니다. url() 메서드로 요청할 서버의 URL을 설정하고, post() 메서드에 앞서 생성한 Multipart 요청 본문을 전달하여 POST 요청을 생성합니다.
OkHttpClient 객체를 생성하고 newCall(request).enqueue() 메서드를 사용하여 요청을 비동기적으로 전송합니다. 요청이 완료되면 onFilure 또는 onResponse 콜백 함수가 호출되어 결과를 처리할 수 있습니다. 요청이 성공적으로 수행되면 응답 본문을 문자열로 변환하여 사용할 수 있습니다. 그렇지 않으면 오류 처리를 수행합니다.
위의 구조를 조금 실제 프로젝트에서 적용하여 예시를 보여드리겠습니다.
Data 레이어
1. 데이터 클래스 정의
data class ApiResponse(val message: String)
interface ApiService {
@Multipart
@POST("endPoint")
suspend fun uploadImage(
@Part("text") text: RequestBody,
@Part image: MultipartBody.Part
): ApiResponse
}
Domain 레이어
class Repository(private val apiService: ApiService) {
suspend fun uploadImage(text: String, file: File): ApiResponse {
val textPart = RequestBody.create("text/plain".toMediaTypeOrNull(), text)
val imagePart = MultipartBody.Part.createFormData(
"image",
file.name,
file.asRequestBody("image/jpeg".toMediaTypeOrNull())
)
return apiService.uploadImage(textPart, imagePart)
}
}
class MyViewModel(private val repository: Repository) : ViewModel() {
private val _uploadResult = MutableLiveData<ApiResponse>()
val uploadResult: LiveData<ApiResponse>
get() = _uploadResult
fun uploadImage(text: String, file: File) {
viewModelScope.launch {
runcatching {
repository.uploadImage(text, file)
}.onSuccess{ res ->
_uploadResult.value = res
}.onFailure{ e ->
e.printStackTrace()
}
}
}
}
presentation 레이어
val viewModel: MyViewModel by viewModels()
viewModel.uploadResult.observe(this, Observer { result ->
// Handle success
})
val file = File("imageData.jpg")
viewModel.uploadImage("HHT", file)
// 예시 코드
- 30초로 설정
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
fun uploadImage() {
// Coroutine을 사용하여 백그라운드에서 네트워크 작업 수행
CoroutineScope(Dispatchers.IO).launch {
val response = uploadImageToServer(file)
}
}
이외에도 퍼미션과 캐싱 및 재시도 정책, 보안 등 주의해야하는 작업들이 있습니다.
이러한 부분들을 감안하고 코드를 작성한다면 파일 업로드 및 다양한 데이터 유형을 동시에 전송해야하는 경우 매우 유용하게 사용할 수 있습니다.