相关博文:
前言
通用模块封装
这里封装一些通用的代码,知道一下就可以了。
/**
* 上传状态机
*/
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系列博客哈。
创作不易,如有帮助一键三连咯🙆♀️。欢迎技术探讨噢!