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!

相关推荐
apihz几秒前
域名WHOIS信息查询免费API使用指南
android·开发语言·数据库·网络协议·tcp/ip
问道飞鱼18 分钟前
【移动端知识】移动端多 WebView 互访方案:Android、iOS 与鸿蒙实现
android·ios·harmonyos·多webview互访
aningxiaoxixi1 小时前
Android 之 audiotrack
android
枷锁—sha1 小时前
【DVWA系列】——CSRF——Medium详细教程
android·服务器·前端·web安全·网络安全·csrf
Cao_Shixin攻城狮5 小时前
Flutter运行Android项目时显示java版本不兼容(Unsupported class file major version 65)的处理
android·java·flutter
呼啦啦呼啦啦啦啦啦啦8 小时前
利用pdfjs实现的pdf预览简单demo(包含翻页功能)
android·javascript·pdf
idjl9 小时前
Mysql测试题
android·adb
游戏开发爱好者812 小时前
iOS App 电池消耗管理与优化 提升用户体验的完整指南
android·ios·小程序·https·uni-app·iphone·webview
人生游戏牛马NPC1号12 小时前
学习 Flutter (四):玩安卓项目实战 - 中
android·学习·flutter
星辰也为你祝福h14 小时前
Android原生Dialog
android