项目中有视频上传的功能,但一直没做断点续传,针对大视频上传不友好,最近的版本需求将断点续传加上了,服务端采用的是 multipart/form-data 编码方式来实现,那么客户端也依照这个方式来做就好了。
multipart/form-data
multipart/form-data 方式用于大数据 Post 请求,用做分片正好合适。它对数据有一定的格式,参照示例:
1 | POST http://www.example.com HTTP/1.1 |
每部分都是以--boundary
开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以--boundary--
标示结束。
原生实现
如果使用 Android 原生 HttpUrlConnection 来拼接数据,参照从原理角度解析Android (Java) http 文件上传贴出部分代码:
1 | private static final String BOUNDARY = "----WebKitFormBoundaryT1HoybnYeFOGFlBR"; |
拼接好字符串之后,转成 byte 数组,然后写入到 http connection 中即可。
OkHttp 实现
项目中有基于 OkHttp 封装网络请求,但是不支持 multipart/form-data,那么便根据 OkHttp 自己实现一个吧。
1 | // Http 请求 Client |
利用 MultipartBody.addFormDataPart 可以很方便的添加参数,而不用自己拼接了。MultipartBody.FORM
即对应multipart/form-data
:
1 | public static final MediaType FORM = MediaType.get("multipart/form-data") |
针对 client 返回的 response,想要获取数据只需要调用:
1 | response.body()?.string() |
但是此方法只能调用一次,调用之后 client 会关闭通道,这让我在调试返回结果的时候浪费不少时间。
data 作为数据来源,可以从 File 中读取:
1 | /** |
至此,数据便能使用 multipart/form-data Post 到服务器了。
进度监听
看到上述示例中的 VideoUploadRequestBody:
1 | class VideoUploadRequestBody(val contentType: MediaType?, val data: ByteArray, val listener: ProgressListener?) |
继承 RequestBody,在每一次 write 的时候回调一下,BufferedSink writeAll 方法内部会每次写入 8192 字节。
1 | @Override |
所以每写入 8192 字节会回调一次,对于进度监听有特殊需求的,可自行修改 writeTo 方法。
这个回调只是针对单片的,还需要一个针对整个文件的回调:
1 | // 当前上传的是第几片 |
如此,整个文件上传的进度监听即实现了。
注:要实现断点续传,则需要在每个分片上传完之后保存到本地数据库,然后续传的时候根据数据库保存的最新片段取出数据进行上传。
参考
四种常见的 POST 提交数据方式
从原理角度解析Android (Java) http 文件上传
OkHttp踩坑记:为何 response.body().string() 只能调用一次?