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 分钟前
Android Runtime增量编译与差分更新机制原理(45)
android·面试·android jetpack
程序员小刘1 小时前
鸿蒙跨平台开发:打通安卓、iOS生态
android·ios·harmonyos
移动开发者1号3 小时前
Protobuf序列化性能全面对比分析
android·kotlin
移动开发者1号3 小时前
Kotlinx序列化多平台兼容性详解
android·java·kotlin
用户20187928316716 小时前
通俗易懂的讲解:Android系统启动全流程与Launcher诞生记
android
二流小码农16 小时前
鸿蒙开发:资讯项目实战之项目框架设计
android·ios·harmonyos
用户20187928316718 小时前
WMS 的核心成员和窗口添加过程
android
用户20187928316718 小时前
PMS 创建之“软件包管理超级工厂”的建设
android
用户20187928316718 小时前
通俗易懂的讲解:Android APK 解析的故事
android
渣渣_Maxz18 小时前
使用 antlr 打造 Android 动态逻辑判断能力
android·设计模式