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!

相关推荐
鸣弦artha14 分钟前
Flutter框架跨平台鸿蒙开发——Drawer抽屉导航组件详解
android·flutter
wqwqweee35 分钟前
Flutter for OpenHarmony 看书管理记录App实战:关于我们实现
android·javascript·python·flutter·harmonyos
峥嵘life43 分钟前
Android16 EDLA【GTS】GtsUnofficialApisUsageTestCases存在fail项
android·linux·运维·学习
studyForMokey1 小时前
【Android 源码】RecylerView的深入理解
android·学习
weixin_403810131 小时前
EasyClick 安卓自动化版本 如何自激活代理模式并且启动安卓的自动化服务
android·自动化·代理模式
独自破碎E2 小时前
【双指针+字符串】字符串变形
android·java
Whisper_Sy13 小时前
Flutter for OpenHarmony移动数据使用监管助手App实战 - 应用列表实现
android·开发语言·javascript·flutter·php
北海屿鹿14 小时前
【MySQL】内置函数
android·数据库·mysql
臻一14 小时前
rk3576+安卓14 ---上电时序调整
android
踢球的打工仔15 小时前
typescript-接口的基本使用(一)
android·javascript·typescript