티스토리 뷰

서버에 POST로 요청해서 파일을 받아야하는 서버연동이 있어서

POST 방식으로 getParams에 값을 넣어줬더니 에러가 발생했다.

 

알고보니 params를 body에 multi-part 형식으로 넣어서 전송해야 파일을 받을 수 있다고 POSTMAN으로 테스트하고

전에 파일업로드하는걸 가져다가 적용했다.

 

NetworkHelper.kt

package kr.co.mdpeople.eocs.network

import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Environment
import android.util.Log
import android.util.LruCache
import com.android.volley.*
import com.android.volley.toolbox.ImageLoader
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import java.io.*

class NetworkHelper constructor(context: Context) {
    val imageLoader: ImageLoader by lazy {
        ImageLoader(requestQueue,
            object : ImageLoader.ImageCache {
                private val cache = LruCache<String, Bitmap>(20)
                override fun getBitmap(url: String): Bitmap {
                    return cache.get(url)
                }
                override fun putBitmap(url: String, bitmap: Bitmap) {
                    cache.put(url, bitmap)
                }
            })
    }
    private val requestQueue: RequestQueue by lazy {
        Volley.newRequestQueue(context.applicationContext)
    }
    private fun <T> addToRequestQueue(req: Request<T>) {
        requestQueue.add(req)
    }

    fun getPdhDownloadRequest2(context: Context,
                              tag: String,
                              fileInfoMap: Map<String, String>,
                              onResponseListener: OnResponseListener) {
        val url = "$ServerUrl"

        val request = object : VolleyFileUploadRequest(
            Method.POST,
            url,
            Response.Listener {
                val resultStr = String(it.data)

                val fileSize = fileInfoMap[KEY_FileSize]
                val responseFileSize = it.data.size.toString()

                if (fileSize == responseFileSize) {
                    // 파일저장하기
                    val filename = fileInfoMap.getValue(KEY_DownFileName)
                    val f = File(
                        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
                            .toString(), filename
                    )
                    val fos = FileOutputStream(f)
                    fos.write(it?.data)
                    fos.flush()
                    fos.close()

                    // 갤러리에 다운로드 받은 파일 갱신 요청
                    context.sendBroadcast(
                        Intent(
                            Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
                            Uri.fromFile(f)
                        )
                    )

                    onResponseListener.onResponseSuccessListener(tag, it.toString(), fileInfoMap)
                } else {
                    onResponseListener.onResponseFailListener(tag, VolleyError(), fileInfoMap)
                }
            },
            Response.ErrorListener {
                onResponseListener.onResponseFailListener(tag, it, fileInfoMap)
            }
        ) {
            override fun getHeaders(): MutableMap<String, String> {
                return super.getHeaders()
            }

            // 여기에서 입력한 params 값이 다음 파일의 body에 multi-part 형식으로 들어감
            override fun getParams(): MutableMap<String, String> {
                val params: MutableMap<String, String> = HashMap()

                params["param1"] = "param_value1"
                params["param2"] = "param_value2"
                params["param3"] = "param_value3"

                return params
            }
        }

        request.setShouldCache(false)
        getInstance(context).addToRequestQueue(request)
    }

    companion object {
        @Volatile
        private var INSTANCE: NetworkHelper? = null

        private const val ServerUrl = "server url을 여기에"

        const val RESULT_SUCCESS: String = "SUCCESS"
        const val RESULT_FAIL: String = "FAIL"

        fun getInstance(context: Context) =
            INSTANCE ?: synchronized(this) {
                INSTANCE ?: NetworkHelper(context).also {
                    INSTANCE = it
                }
            }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////

    interface OnResponseListener {
        fun onResponseSuccessListener(tag: String, it: String, fileInfoMap: Map<String, String>)
        fun onResponseFailListener(tag: String, it: VolleyError, fileInfoMap: Map<String, String>)
    }
}

 

VolleyFileUploadRequest.kt

import android.util.Log
import com.android.volley.*
import com.android.volley.toolbox.HttpHeaderParser
import java.io.*

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 {
            // 앞에서 getParams 입력한 값을 여기에서 body에 추가함
            if (params != null && params.isNotEmpty()) {
                processParams(dataOutputStream, params, paramsEncoding)
            }

            dataOutputStream.writeBytes(divider + boundary + divider + ending)
            return byteArrayOutputStream.toByteArray()

        } catch (e: IOException) {
            e.printStackTrace()
        }
        return super.getBody()
    }

    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)
        }
    }

}

 

다운받은 파일은 갤러리에 저장하고 성공/실패 동작은 이걸 호출하는 곳에서 처리함.

 

'안드로이드' 카테고리의 다른 글

미디어파일 저장  (0) 2020.08.25
이미지 zoom & Pinch-to-Zoom  (0) 2020.07.28
다이얼로그 배경 지우기  (0) 2020.07.01
Toolbar Home Indicator Icon 변경  (0) 2020.04.24
TabLayout의 Tab크기 균등분할(?)  (0) 2020.04.22