Retrofit多参数与文件上传

一、使用的注解搭配

方法 参数
@POST+@Multipart @Part@PartMap
@Post @Body

二、相关注解

1、@Part

表单字段和@Multipart结合

类型支持

  • MultiPartBody.Part-->内容会直接使用
  • RequestBody-->会使用RequestBody#contentType
  • 任意类型-->会通过retrofit#gsonConvert转换json结构。
kotlin 复制代码
public @interface Part {
  /**
   * The name of the part. Required for all parameter types except {@link
   * okhttp3.MultipartBody.Part}.
   */
  String value() default "";
  /** The {@code Content-Transfer-Encoding} of this part. */
  String encoding() default "binary";
}

除参数类型为okhttp3.MultipartBody.Part,都需要声明value;默认编码为binary

注意点

  • 参数可为空,为空时,将会忽略,请求体中不会包含该字段。
  • 需注意在除MultPartBody.Part参数类型之外,声明part名称。

2、@PartMap

表单字段和@Multipart结合

类型支持

  • RequestBody-->会使用RequestBody#contentType
  • 任意类型-->会通过retrofit#gsonConvert对象类型转换json结构
kotlin 复制代码
public @interface PartMap {
  /** The {@code Content-Transfer-Encoding} of the parts. */
  String encoding() default "binary";
}
  • 一般常规写法使用Map<K,RequestBody>

注意点

  • Map中的<k,v>均不能为空,否则会报错(IllegalArgumentException),K 必须为String类型。
  • KolitnMap<K,@JvmSuppressWildcards RequestBody>,注意需要声明@JvmSuppressWildcards注解,否则会报错。

3、@Body

kotlin 复制代码
public @interface Body {}

目前发现的用法,下述两种:

  • RequestBody-->会使用RequestBody#contentType
  • 任意类型-->会通过retrofit#gsonConvert对象类型转换json结构,作为请求体直接发送。

注意点

  • 参数不能为空,否则会报错。

三、文件上传实战

1、@POST,@Multipart,@Part@PartMap

kotlin 复制代码
# Service
@Post(host/path)
@Multipart
suspend fun syncParamsFiles(
    @Part("user_name") userName:String,
    @Part filePart:MulitPartBody.Part,
    @PartMap params:Map<String,@JvmSuppressWildcards RequestBody) 
)

# Repository
val userName="that's it"

val fileRequestBody=RequestBody.create(MediaType.parse("application/octet-stream"),File(filepath))

val filePart=MultipartBody.Part.createFormData(
    "file",
    file.nameWithoutExtension,
    fileRequestBody
)

val params=HashMap<String,RequestBody>()
params["gender"]=RequestBody.create(MediaType.parse("text/plain"),"female")

#invoke
apiService.syncParamsFiles(
    userName,
    filePart,
    params
)

2、@Post@Body

kotlin 复制代码
# Service
@Post(host/path)
suspend fun syncParamsFiles(
    @Body requestBody:RequestBody
)

# Repository
val fileBody=RequestBody.create(MediaType.parse("application/octet-stream"), File(filepath))

val requestBody=MultipartBody.Builder()
    .setType(MultipartBody.FORM)
    .apply {
        addFormDataPart("user_name", userName)
        addFormDataPart("gender", userGender)
        addFormDataPart(
            "file",
            file.nameWithoutExtension,
            fileRequestBody
        )
    }.build()

# invoke
apiService.syncParamsFiles(requestBody)

四、实现带进度的文件上传

实现思路

  • 委托
  • 重写RequestBody

以委托为例,实现文件上传的进度监听:

kotlin 复制代码
open class ProgressRequestBody(
    private val delegate:RequestBody,
    private val onRequestProgressListener: OnRequestProgressListener
):RequestBody() {

    override fun contentType(): MediaType?=delegate.contentType()

    override fun contentLength(): Long {
        try {
            return delegate.contentLength()
        }catch (ioException:IOException){
            ioException.printStackTrace()
        }
        return -1
    }

    @Throws(IOException::class)
    override fun writeTo(sink: BufferedSink) {
        val countingSink=CountingSink(sink,contentLength(),onRequestProgressListener)
        val bufferedSink=Okio.buffer(countingSink)
        delegate.writeTo(bufferedSink)
        bufferedSink.flush()
    }

    protected class CountingSink(
        delegateSink:Sink,
        val contentLength: Long,
        private val onRequestProgressListener: OnRequestProgressListener
    ):ForwardingSink(delegateSink){

        private var bytesWritten:Long=0L

        override fun write(source: Buffer, byteCount: Long) {
            super.write(source, byteCount)
            bytesWritten+=byteCount
            onRequestProgressListener.onRequestProgress(bytesWritten,contentLength)
        }
    }

    interface OnRequestProgressListener{

        fun onRequestProgress(
            bytesWritten:Long,
            contentLength:Long
        )
    }
}

# Repository 以3.2为例
val fileRequestBody = ProgressRequestBody(RequestBody.create(MediaType.parse("application/octet-stream"), File(filepath)),
            object : ProgressRequestBody.OnRequestProgressListener {
                override fun onRequestProgress(bytesWritten: Long, contentLength: Long) {
                    Log.d("junit", "onRequestProgress: bytesWritten->$bytesWritten," +
                            "contentLength->$contentLength,progress->${bytesWritten.times(1.0f)/contentLength}")
                    //dosomething
                }
            }
        )

val requestBody=MultipartBody.Builder()
    .setType(MultipartBody.FORM)
    .apply {
        addFormDataPart("user_name", userName)
        addFormDataPart("gender", userGender)
        addFormDataPart(
            "file",
            file.nameWithoutExtension,
            fileRequestBody
        )
    }.build()

# invoke
apiService.syncParamsFiles(requestBody)

ENDING

That's it!

相关推荐
找藉口是失败者的习惯2 小时前
Jetpack Compose 如何布局解析
android·xml·ui
Estar.Lee6 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh7 小时前
uiautomator案例
android
工业甲酰苯胺8 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做3438 小时前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee9 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯10 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey11 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!13 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟14 小时前
Android音频采集
android·音视频