티스토리 뷰
매번 서버로 요청해서 데이타를 받거나 파일을 받아 저장만 했었는데,
이번에는 로컬의 파일을 서버로 보내는 일을 하게 되었네요.
기존 volley를 이용해 보냈는데 파일이 없다고 해서 왜 없나 한참을 고민하다,
iOS에서 AlamoFire 라이브러리로 통신을 하는걸 보니 param 구조가 다르다는걸 알게 되었어요.
기존 VolleyFileUploadRequest class에 몇가지 추가를 해주면 된다.
getBody()에 getByteData에 파일 정보를 가져오는 코드를 추가해준다.
override fun getBody(): ByteArray {
val byteArrayOutputStream = ByteArrayOutputStream()
val dataOutputStream = DataOutputStream(byteArrayOutputStream)
try {
if (params != null && params.isNotEmpty()) {
processParams(dataOutputStream, params, paramsEncoding)
}
// 파일 내용을 가져오는 코드 추가 [[
val data = getByteData() as? Map<String, FileDataPart>?
if (data != null && data.isNotEmpty()) {
processData(dataOutputStream, data)
}
// ]]
dataOutputStream.writeBytes(divider + boundary + divider + ending)
return byteArrayOutputStream.toByteArray()
} catch (e: IOException) {
e.printStackTrace()
}
return super.getBody()
}
그리고 추가한 코드에서 필요한 함수도 추가
@Throws(AuthFailureError::class)
open fun getByteData(): Map<String, Any>? {
return null
}
@Throws(IOException::class)
private fun processData(dataOutputStream: DataOutputStream, data: Map<String, FileDataPart>) {
data.forEach {
val dataFile = it.value
dataOutputStream.writeBytes("$divider$boundary$ending")
dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"${it.key}\"; filename=\"${dataFile.fileName}\"$ending")
if (dataFile.type.trim().isNotEmpty()) {
dataOutputStream.writeBytes("Content-Type: ${dataFile.type}$ending")
}
dataOutputStream.writeBytes(ending)
val fileInputStream = ByteArrayInputStream(dataFile.data)
var bytesAvailable = fileInputStream.available()
val maxBufferSize = 1024 * 1024
var bufferSize = min(bytesAvailable, maxBufferSize)
val buffer = ByteArray(bufferSize)
var bytesRead = fileInputStream.read(buffer, 0, bufferSize)
while (bytesRead > 0) {
dataOutputStream.write(buffer, 0, bufferSize)
bytesAvailable = fileInputStream.available()
bufferSize = min(bytesAvailable, maxBufferSize)
bytesRead = fileInputStream.read(buffer, 0, bufferSize)
}
dataOutputStream.writeBytes(ending)
}
}
}
class FileDataPart(var fileName: String?, var data: ByteArray, var type: String)
파일 데이터는 multipart/form-data 형식으로 보낼때 일반 parameter와 다른걸 알 수 있었다.
일반 parameter는 key(name), value 구조로 전송(실제 전송 데이타는 이것저것 구분되는것이 추가 됨) 하지만,
파일 데이터는 key(name), filename, Content-Type(옵션) 그리고 파일 데이터는 "Content-Type"에 추가되어 전송이 이뤄지는걸 알 수 있다.
이 부분이 없어 파일이 전송이 안되는 것이였다.
그리고 여기서 중요한 것은 파일을 받아 저장하는 서버에서 지정된 paramter 이름으로 해야 파일 데이터를 전달받아 저장할 수 있다.
예를들어 parameter 이름이 fileCtnt인데 이 부분에 다른 이름인 파일 이름을 넣어줬더니 서버에서 수신을 못해 문제가 발생하였다.
그리고 VolleyFileUploadRequest를 호출하는 곳에서 getByteData()를 override 해줘야한다.
override fun getByteData(): Map<String, Any> {
val params = HashMap<String, FileDataPart>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val file = File(경로, 파일명) // 업로드할 파일 정보
params[fileUpload.fileKeyName] = FileDataPart("fileCtnt", Files.readAllBytes(file.toPath()), "jpg")
} else {
// TODO android version O 미만에 대한 처리 필요
}
return params
}
파일의 byteArray를 가져오기 위해 Files.readAllBytes()를 사용하다보니 android O 이상에서만 동작이 되어, 하위 버전에 대한 처리는 추가로 작업이 필요하네요. ㅠㅠ
이렇게 해서 파일이 서버에 올라는것은 확인 했습니다.
하지만 파일 사이즈가 큰 것은 분할해서 보내야한다고 하네요.
이 부분에 대한 숙제가 남아있어 다시 찾아보러 갑니다.
아래는 VolleyFileUploadRequest class 전체 내용입니다.
import com.android.volley.*
import com.android.volley.toolbox.HttpHeaderParser
import java.io.*
import java.lang.Integer.min
open class VolleyFileUploadRequest(
method: Int,
url: String,
listener: Response.Listener<NetworkResponse>,
errorListener: Response.ErrorListener) : Request<NetworkResponse>(method, url, errorListener) {
private var responseListener: Response.Listener<NetworkResponse>? = null
init {
this.responseListener = listener
}
private var headers: Map<String, String>? = null
private val divider: String = "--"
private val ending = "\r\n"
private val boundary = "imageRequest${System.currentTimeMillis()}"
override fun getHeaders(): MutableMap<String, String> =
when(headers) {
null -> super.getHeaders()
else -> headers!!.toMutableMap()
}
override fun getBodyContentType() = "multipart/form-data;boundary=$boundary"
@Throws(AuthFailureError::class)
override fun getBody(): ByteArray {
val byteArrayOutputStream = ByteArrayOutputStream()
val dataOutputStream = DataOutputStream(byteArrayOutputStream)
try {
if (params != null && params.isNotEmpty()) {
processParams(dataOutputStream, params, paramsEncoding)
}
val data = getByteData() as? Map<String, FileDataPart>?
if (data != null && data.isNotEmpty()) {
processData(dataOutputStream, data)
}
dataOutputStream.writeBytes(divider + boundary + divider + ending)
return byteArrayOutputStream.toByteArray()
} catch (e: IOException) {
e.printStackTrace()
}
return super.getBody()
}
@Throws(AuthFailureError::class)
open fun getByteData(): Map<String, Any>? {
return null
}
override fun parseNetworkResponse(response: NetworkResponse): Response<NetworkResponse> {
return try {
Response.success(response, HttpHeaderParser.parseCacheHeaders(response))
} catch (e: Exception) {
Response.error(ParseError(e))
}
}
override fun deliverResponse(response: NetworkResponse) {
responseListener?.onResponse(response)
}
override fun deliverError(error: VolleyError) {
errorListener?.onErrorResponse(error)
}
@Throws(IOException::class)
private fun processParams(dataOutputStream: DataOutputStream, params: Map<String, String>, encoding: String) {
try {
params.forEach {
dataOutputStream.writeBytes(divider + boundary + ending)
dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"${it.key}\"$ending")
dataOutputStream.writeBytes(ending)
dataOutputStream.writeBytes(it.value + ending)
}
} catch (e: UnsupportedEncodingException) {
throw RuntimeException("Unsupported encoding not supported: $encoding with error: ${e.message}", e)
}
}
@Throws(IOException::class)
private fun processData(dataOutputStream: DataOutputStream, data: Map<String, FileDataPart>) {
data.forEach {
val dataFile = it.value
dataOutputStream.writeBytes("$divider$boundary$ending")
dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"${it.key}\"; filename=\"${dataFile.fileName}\"$ending")
if (dataFile.type.trim().isNotEmpty()) {
dataOutputStream.writeBytes("Content-Type: ${dataFile.type}$ending")
}
dataOutputStream.writeBytes(ending)
val fileInputStream = ByteArrayInputStream(dataFile.data)
var bytesAvailable = fileInputStream.available()
val maxBufferSize = 1024 * 1024
var bufferSize = min(bytesAvailable, maxBufferSize)
val buffer = ByteArray(bufferSize)
var bytesRead = fileInputStream.read(buffer, 0, bufferSize)
while (bytesRead > 0) {
dataOutputStream.write(buffer, 0, bufferSize)
bytesAvailable = fileInputStream.available()
bufferSize = min(bytesAvailable, maxBufferSize)
bytesRead = fileInputStream.read(buffer, 0, bufferSize)
}
dataOutputStream.writeBytes(ending)
}
}
}
class FileDataPart(var fileName: String?, var data: ByteArray, var type: String)
'안드로이드' 카테고리의 다른 글
Activity 높이 변경 (1) | 2022.09.26 |
---|---|
안드로이드 웹뷰(WebView) 동영상 화면 축소 기능 문제 (0) | 2022.05.16 |
ContentResolver 파일경로 가져오기 (0) | 2022.05.03 |
안드로이드 log4j 적용 (0) | 2022.01.27 |
네트워크 상태 체크 (0) | 2020.08.26 |
- Total
- Today
- Yesterday
- px to dp
- areNotificationsEnabled
- 음원파일재생
- 알림소리끄기
- 알림소리묵음처리
- SWIFT
- webview
- 한글깨짐
- app restart
- 알림허용
- 작업은했는데
- #gradle.properties
- height변경
- 이미지파일보기
- 웹뷰
- px -> dp
- 난왜테스트가안될까
- Kotlin
- 새로올린테스트앱이안보이네
- #buildconofig
- FileDataPart
- onShowCustomView
- base64
- 인앱 업데이트
- Activity 크기 변경
- nodejs
- Android
- 이미지파일공유
- 안드로이드
- VolleyFileUploadRequest
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |