OkHttp初探3:简单文件上传表单文件一起上传带进度条的文件上传MediaType介绍。Kotlin版本

Posted pumpkin的玄学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OkHttp初探3:简单文件上传表单文件一起上传带进度条的文件上传MediaType介绍。Kotlin版本相关的知识,希望对你有一定的参考价值。

相关博文:

前言

通用模块封装

这里封装一些通用的代码,知道一下就可以了。

/**
 * 上传状态机
 */
sealed class UploadState 
    /**
     * 未开始
     */
    object UnStart : UploadState()

    /**
     * 文件不存在
     */
    object FileNotExist : UploadState()

    /**
     * 上传完成
     */
    object Complete : UploadState()

    /**
     * 上传中
     */
    class Progress(var totalNum: Long, var current: Long) : UploadState()

    /**
     * 失败
     */
    class Error(val e: Exception) : UploadState()

MediaType介绍

相信大多数人在写文件上传下载代码的时候,都不太明白MediaType的含义。这里详细列出MediaType含义。以及对应解释说明。

类型描述
text/htmlHTML格式
text/plain纯文本格式,空格转换为 “+” 加号,不对特殊字符编码
text/xmlXML格式
text/x-markdownMarkdown格式
image/gifgif图片格式
image/jpegjpg图片格式
image/pngpng图片格式
application/xhtml+xmlXHTML格式
application/xmlXML数据格式
application/json用来告诉服务端,消息主体是序列化后的JSON字符串
application/pdfpdf格式
application/mswordWord文档格式
application/octet-stream二进制流数据
application/x-www-form-urlencoded参数为键值对形式,在发送前编码所有字符(默认)。如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据
multipart/form-data不对字符编码,发送大量二进制数据或包含non-ASCII字符的文本,application/x-www-form-urlencoded是效率低下的(需要用更多字符表示一个non-ASCII字符)。需要设定“ <form enctype=‘multipart/form-data’

MediaType对象解析

text/html; charset=utf-8
//解析
type值是text,表示是文本这一大类;
后面的html是子类型,表示是文本这一大类下的html类型;
charset=utf-8 则表示采用UTF-8编码

上面介绍完了,下面正式代码封装就开始了。坐稳了,发车!

简单的文件上传

inline fun simpleUploadFile(
    url: String,
    filePath: String,
    contentType: MediaType? = null,
    crossinline block: (UploadState) -> Unit
) 
    var state: UploadState = UploadState.UnStart
    block(state)

    val file = File(filePath)
    if (!file.exists()) 
        //文件不存在则不上传
        state = UploadState.FileNotExist
        block(state)
        return
    

    val request = Request.Builder()
        .url(url)
        .post(file.asRequestBody(contentType))
        .build()

    val client = OkHttpClient.Builder()
        .dispatcher(dispatcher)
        .writeTimeout(30, TimeUnit.MINUTES)
        .readTimeout(30, TimeUnit.MINUTES)
        .connectTimeout(70, TimeUnit.SECONDS)
        .build()

    client.newCall(request).enqueue(object : Callback 
        override fun onFailure(call: Call, e: IOException) 
            log(e.message)
        

        override fun onResponse(call: Call, response: Response) 
            if (response.isSuccessful) 
                state = UploadState.Complete
                block(state)
             else 
                log("请求失败")
            
        

    )


表单和文件一起上传

inline fun multipartUpload(
    url: String,
    filePath: String,
    contentType: MediaType? = null,
    params: Map<String, String>? = null,
    crossinline block: (UploadState) -> Unit
) 
    var state: UploadState = UploadState.UnStart
    block(state)

    val file = File(filePath)

    val body = MultipartBody.Builder()
        .also 
            params?.forEach  (k, v) ->
                it.addFormDataPart(k, v)
            
        .also 
            if (file.exists()) 
                it.addFormDataPart("filename", file.name, file.asRequestBody(contentType))
            
        .build()

    val request = Request.Builder()
        .url(url)
        .post(body)
//        .addHeader() 可以增加请求头
        .build()

    val client = OkHttpClient.Builder()
        .dispatcher(dispatcher)
        .writeTimeout(30, TimeUnit.MINUTES)
        .readTimeout(30, TimeUnit.MINUTES)
        .connectTimeout(70, TimeUnit.SECONDS)
        .build()

    client.newCall(request).enqueue(object : Callback 
        override fun onFailure(call: Call, e: IOException) 
            log(e.message)
        

        override fun onResponse(call: Call, response: Response) 
            if (response.isSuccessful) 
                state = UploadState.Complete
                block(state)
             else 
                log("请求失败")
            
        
    )

文件上传带进度条,重点重写RequestBody

fun multipartUploadProgress(
    url: String,
    filePath: String,
    contentType: MediaType? = null,
    params: Map<String, String>? = null,
    block: (UploadState) -> Unit
) 
    var state: UploadState = UploadState.UnStart
    block(state)

    val file = File(filePath)

    val body = MultipartBody.Builder()
        .also 
            params?.forEach  (k, v) ->
                it.addFormDataPart(k, v)
            
        .also 
            if (file.exists()) 
                it.addFormDataPart("filename", file.name, file.asProgressRequestBody(contentType, block))
            
        .build()

    val request = Request.Builder()
        .url(url)
        .post(body)
//        .addHeader() 可以增加请求头
        .build()

    val client = OkHttpClient.Builder()
        .dispatcher(dispatcher)
        .writeTimeout(30, TimeUnit.MINUTES)
        .readTimeout(30, TimeUnit.MINUTES)
        .connectTimeout(70, TimeUnit.SECONDS)
        .build()

    client.newCall(request).enqueue(object : Callback 
        override fun onFailure(call: Call, e: IOException) 
            log(e.message)
        

        override fun onResponse(call: Call, response: Response) 
            if (response.isSuccessful) 
                state = UploadState.Complete
                block(state)
             else 
                log("请求失败")
            
        
    )


/**
 * 带进度条上传的功能
 */
private fun File.asProgressRequestBody(contentType: MediaType? = null, block: (UploadState) -> Unit?): RequestBody 
    return object : RequestBody() 
        override fun contentType() = contentType

        override fun contentLength() = length()

        override fun writeTo(sink: BufferedSink) 
            source().use  source ->
                val buffer = Buffer()
                var readCount = 0L
                var progress = 0L
                val progressBlock = UploadState.Progress(contentLength(), progress)
                try 
                    do 
                        if (readCount != 0L) 
                            progress += readCount
                            progressBlock.current = progress
                            sink.write(buffer, readCount)
                            block(progressBlock)
                        
                        readCount = source.read(buffer, 2048)
                     while (readCount != -1L)
                 catch (e: Exception) 
//                    e.printStackTrace()
                    block(UploadState.Error(e))
                
            
        
    


使用

multipartUploadProgress(
        "https://api.github.com/markdown/raw",
        "download/WeChatSetup.exe",
        "application/octet-stream".toMediaType(),
        mapOf(
            "name" to "blog",
            "auto_init" to "true",
            "private" to "true",
            "gitignore_template" to "nanoc"
        )
    )  s ->
        when (s) 
            UploadState.Complete -> 
                log("上传完成")
            
            UploadState.FileNotExist -> 
                log("上传失败,文件不存在")
            
            is UploadState.Progress -> 
                log("上传中  $(s.current.toFloat() / s.totalNum) * 100%")
            
            UploadState.UnStart -> 
                log("上传未开始")
            
            is UploadState.Error -> 
                log("上传失败  $s.e.message")
            
        
    

后面会陆续推出OkHttp高阶使用,以及OkHttp源码分析博客。觉得不错关注博主哈~😎
感兴趣可以查看博主之前的kotlin系列、kotlin协程flow、jetpack系列博客哈。
创作不易,如有帮助一键三连咯🙆‍♀️。欢迎技术探讨噢!

以上是关于OkHttp初探3:简单文件上传表单文件一起上传带进度条的文件上传MediaType介绍。Kotlin版本的主要内容,如果未能解决你的问题,请参考以下文章

带上传的 WordPress 3.0 自定义帖子类型

带文件的简单表单提交

[Layui]上传文件带进度条+表单提交功能优化

[Layui]上传文件带进度条+表单提交功能优化

android -------- OkGo (让网络请求更简单的框架)

php 带文件上传的表单(可选文件上传)