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类型。
  • 在Kolitn中Map<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!

相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone4 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen4 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone4 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui