使用OkHttp上傳圖片

簡介

上傳圖片是一個APP的常見功能,可以是通過OOS上傳到阿里雲,也可以直接上傳到Server後臺,OOS有提供相應的SDK,此處忽略。下面通過OkHttp來實現圖片的上傳

代碼

直接上代碼UploadFileHelper.kt

object UploadFileHelper {
    //--------ContentType
    private val MEDIA_OBJECT_STREAM = MediaType.parse("multipart/form-data")
    //--------上傳延時時間
    private val WRITE_TIME_OUT:Long  = 50
    private val mOkHttpClient by lazy { OkHttpClient() }
    //------基本參數----------
    val version = AppConstant.API_VERSION
    val platform = AppConstant.API_PLATFORM
    val methodName = AppConstant.API_UPLOADFILE_METHOD
    val token = ignoreException("") { UserModel.token() }
    val userId = ignoreException(0) { UserModel.id() }
    //------------------------
    //不帶參數同步上傳文件
    fun syncUploadFile(actionUrl: String = "",file: File,maxW: Int = 256,maxH: Int = 256):String?{
        val uploadFile = optionFileSize(file,maxW,maxH,null)
        if(uploadFile!=null){
            val response = createNoParamsOkHttpCall(actionUrl,uploadFile).execute()
            if(uploadFile.exists())
                uploadFile.delete()
            return getResponseToPath(response.body()!!.string())
        }
        return null
    }
    //不帶參數異步上傳文件
    fun asyncUploadFile(actionUrl:String = "", file: File,maxW: Int = 256,maxH: Int = 256,
                        uploadCallBackListener: UploadCallBackListener? = null){
        val uploadFile = optionFileSize(file,maxW,maxH,uploadCallBackListener)
        if(uploadFile!=null)
        createNoParamsOkHttpCall(actionUrl,uploadFile).enqueue(object: Callback{
            override fun onFailure(c: Call, e: IOException) {
                uploadCallBackListener?.onUploadFailure(e.toString())
            }
            override fun onResponse(c: Call, response: Response) {
                if(uploadFile.exists())
                uploadFile.delete()

                 uploadCallBackListener?.onUploadSuccess(getResponseToPath(response.body()!!.string()))
                response.body()!!.close()
            }
        })
    }
    //帶參數同步上傳文件
    fun syncParamsUploadFile(actionUrl: String= "",file: File,params:HashMap<String,Any>,
                       maxW: Int = 256,maxH: Int = 256):String?{
        val uploadFile = optionFileSize(file,maxW,maxH,null)
        if(uploadFile!=null){
            params.put("filename",uploadFile)
            val response = createParamsOkHttpCall(actionUrl,params,null,false).execute()
            if(uploadFile.exists())
                uploadFile.delete()
            return getResponseToPath(response.body()!!.string())
        }
        return null
    }
    //帶參數異步上傳文件
    fun asyncParamsUploadFile(actionUrl: String= "",file: File,params:HashMap<String,Any>,maxW: Int = 256,maxH: Int = 256,
                        uploadCallBackListener: UploadCallBackListener? = null, isProgress:Boolean = true){
        val uploadFile = optionFileSize(file,maxW,maxH,uploadCallBackListener)
        if(uploadFile!=null){
            params.put("filename",uploadFile)
            createParamsOkHttpCall(actionUrl,params,uploadCallBackListener,isProgress).enqueue(object :Callback{
                override fun onFailure(c: Call, e: IOException) {
                    uploadCallBackListener?.onUploadFailure(e.toString())
                }
                override fun onResponse(c: Call, response: Response) {
                      if(uploadFile.exists())
                        uploadFile.delete()
                    uploadCallBackListener?.onUploadSuccess(getResponseToPath(response.body()!!.string()))
                    response.body()!!.close()
                }
            })
        }
    }
    //------創建一個沒有帶參數的Call
    fun createNoParamsOkHttpCall(actionUrl: String,file: File):Call{
        val requestUrl = "${AppConstant.HOST}/$actionUrl"
        val requestBody = RequestBody.create(MEDIA_OBJECT_STREAM,file)
        val request = Request.Builder().url(requestUrl).post(requestBody).build()
        return mOkHttpClient.newBuilder().writeTimeout(WRITE_TIME_OUT,TimeUnit.SECONDS).build().newCall(request)
    }
    //------創建一個帶參數的Call
    fun createParamsOkHttpCall(actionUrl: String,params:Map<String,Any>,
                               uploadCallBackListener: UploadCallBackListener? = null,
                               isProgress:Boolean = true):Call{
        //-----AppConstant.HOST 上傳圖片的Server的BASE_URL http://xxx.com
        val requestUrl = "${AppConstant.HOST}/$actionUrl"
        val builder = MultipartBody.Builder()
        builder.setType(MultipartBody.FORM)
        val newParams = mutableMapOf(
                "version" to version,
                "platform" to platform,
                "methodName" to methodName,
                "token" to token,
                "user_id" to userId)
        newParams.putAll(params)
        newParams.forEach( action = {
            if(it.value is File){
                builder.addFormDataPart(it.key, (it.value as File).name,
                if(isProgress) createProgressRequestBody(MEDIA_OBJECT_STREAM!!,(it.value as File),uploadCallBackListener)
                else RequestBody.create(null, (it.value as File)))
            }else{
                builder.addFormDataPart(it.key,it.value.toString())
            }
        })
        val body = builder.build()
        val request = Request.Builder().url(requestUrl).post(body).build()
        return mOkHttpClient.newBuilder().writeTimeout(WRITE_TIME_OUT,TimeUnit.SECONDS).build().newCall(request)

    }

    //創建帶進度RequestBody
    fun createProgressRequestBody(contentType:MediaType,file:File,
                                 uploadCallBackListener: UploadCallBackListener? = null):RequestBody{
        return object:RequestBody(){
            override fun contentType(): MediaType = contentType
            override fun contentLength() = file.length()
            override fun writeTo(sink: BufferedSink) {
                ignoreException {
                    val source = Okio.source(file)
                    val buf = Buffer()
                    val remaining = contentLength()
                    var current: Long = 0
                    var readCount: Long = source.read(buf, 2048)
                    while (readCount != -1L) {
                        sink.write(buf, readCount)
                        current += readCount
                        uploadCallBackListener?.onUploadProgress(current,remaining)
                        readCount = source.read(buf, 2048)
                    }

                }
            }
        }
    }
    //根據圖片大小簡單壓縮
    fun optionFileSize(file: File,maxW:Int,maxH:Int,uploadCallBackListener: UploadCallBackListener?):File?{
        try {
            val uploadFile = File(AppBridge.AppContext().externalCacheDir, file.hashCode().toString())
            ImageUtils.resize(file, maxW, maxH, uploadFile)
            return uploadFile
        } catch (e: Exception) {
            uploadCallBackListener?.onUploadFailure("壓縮圖片失敗")
            return null
        }

    }
   //解析Server返回的數據獲取圖片路徑,
    /*
       {"code":200,"msg":"上傳成功","data":{"path":""}}
   */
  fun getResponseToPath(response:String):String{
        val dataJsonObj = JSONObject(response).get("data") as JSONObject
        return dataJsonObj.get("path") as String
    }
    //回調方法
    interface UploadCallBackListener{
        fun onUploadFailure(error:String)
        fun onUploadProgress(currentSize:Long,totalSize:Long)
        fun onUploadSuccess(path:String)
    }
}
inline fun <T> ignoreException(def: T, f: () -> T): T {
    try {
        return f()
    } catch(e: Exception) {
        Timber.e(e, "")
        return def
    }
}

最後根據是否要帶參數、同步或異步調用其中對應的方法可以了

syncUploadFile(xxx)

asyncUploadFile(xxx)

syncParamsUploadFile(xxx)

asyncParamsUploadFile(xxx)

總結

  • 首先根據是否要帶參數上傳,如果不帶參數上傳,直接創建RequestBody;如果帶參數上傳,創建MultipartBody.Builder(),然後把所有參數addFormDataPart進去,其中addFormDataPart方法有個RequestBody參數通過是否要監聽進度創建,如果需要進度,需重寫RequestBodywriteTo()方法,如果不監聽進度,直接創建RequestBody,最後builder.build()得到RequestBody
  • 通過上步驟得到的RequestBody以及上傳圖片的Server路徑,可以配置出一個Request對象。

  • Request對象通過.newCall(request)配置在OkHttpClient得到Call對象

  • 最後Call調用同步.execute()或者異步.enqueue(callBack),在回調裏面處理返回的數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章