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

3 篇文章 0 订阅

相关博文:

前言

通用模块封装

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

/**
 * 上传状态机
 */
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系列博客哈。
创作不易,如有帮助一键三连咯🙆‍♀️。欢迎技术探讨噢!

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
好的,对于这个问题,我可以给出以下的代码示例: ```java private void uploadFileWithProgress(String url, File file, final MediaType mediaType, final ProgressListener progressListener) { OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request.Builder requestBuilder = originalRequest.newBuilder() .method(originalRequest.method(), new ProgressRequestBody(originalRequest.body(), progressListener)); return chain.proceed(requestBuilder.build()); } }) .build(); RequestBody requestBody = RequestBody.create(mediaType, file); Request request = new Request.Builder() .url(url) .post(requestBody) .build(); try { Response response = client.newCall(request).execute(); // 处理响应结果 } catch (IOException e) { e.printStackTrace(); } } ``` 其中,`ProgressRequestBody` 是一个自定义的 `RequestBody`,用于在上传文件时监听上传进度。它的代码实现如下: ```java public class ProgressRequestBody extends RequestBody { private RequestBody requestBody; private ProgressListener progressListener; private CountingSink countingSink; public ProgressRequestBody(RequestBody requestBody, ProgressListener progressListener) { this.requestBody = requestBody; this.progressListener = progressListener; } @Override public MediaType contentType() { return requestBody.contentType(); } @Override public long contentLength() { try { return requestBody.contentLength(); } catch (IOException e) { e.printStackTrace(); } return -1; } @Override public void writeTo(BufferedSink sink) throws IOException { countingSink = new CountingSink(sink); BufferedSink bufferedSink = Okio.buffer(countingSink); requestBody.writeTo(bufferedSink); bufferedSink.flush(); } private final class CountingSink extends ForwardingSink { private long totalBytesWritten = 0L; public CountingSink(Sink delegate) { super(delegate); } @Override public void write(Buffer source, long byteCount) throws IOException { super.write(source, byteCount); totalBytesWritten += byteCount; progressListener.onProgress(totalBytesWritten, contentLength()); } } } ``` 然后,在调用 `uploadFileWithProgress` 方法时,只需要传入文件、媒体类型、进度监听器等参数即可: ```java uploadFileWithProgress(url, file, MediaType.parse("image/png"), new ProgressListener() { @Override public void onProgress(long currentBytes, long contentLength) { // 更新进度条 } }); ``` 注意,这里的 `MediaType` 必须要正确指定,否则可能会导致上传失败。另外,由于上传文件是一个比较耗时的操作,建议在子线程中执行。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pumpkin的玄学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值