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/html HTML格式
text/plain 纯文本格式,空格转换为 “+” 加号,不对特殊字符编码
text/xml XML格式
text/x-markdown Markdown格式
image/gif gif图片格式
image/jpeg jpg图片格式
image/png png图片格式
application/xhtml+xml XHTML格式
application/xml XML数据格式
application/json 用来告诉服务端,消息主体是序列化后的JSON字符串
application/pdf pdf格式
application/msword Word文档格式
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系列博客哈。
创作不易,如有帮助一键三连咯?‍♀️。欢迎技术探讨噢!

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>