文件上传基于POST,因此和POST写法基本一致,但是不再使用FormBody,FormBody使用的MideaType(Content-Type)为"application/x-www-form-urlencoded; charset=utf-8"
,无法上传文件,而上传文件需要通过"application/form-data; charset=utf-8"
的方式进行
OkHttp还提供了一个MultipartBody类,同样继承自RequestBody,可以以"application/form-data; charset=utf-8"
进行文件上传,同时"application/form-data"
不仅可以上传文件,也可以用于普通数据的提交
普通参数提交
1 | OkHttpClient client = new OkHttpClient(); |
MultipartBody在普通参数提交的时候,使用方式与FormBody基本一样,不过MultipartBody请求需要设置ContentType
文件上传
1 | OkHttpClient client = new OkHttpClient(); |
文件依然是通过addFormDataPart进行添加,只不过多了一个RequestBody参数
将文件存放到RequestBody中,然后将该RequestBody添加到MultipartBody中,组成一个完整的RequestBody用于请求
“application/octet-stream”是以文件流方式传输,适合于所有文件,也可以使用指定的Content-Type进行传输,比如"application/x-png"
通过MultipartBody就可以轻松将文件上传至服务器,无需进行复杂的流操作,但是美中不足的是没有进度信息,如果文件较大的情况下,上传耗时比较久,只有文件完全上传后才有回调,十分不友好
文件上传-进度
OkHttp并没有提供进度的相关回调或者接口,但是POST都是通过RequestBody进行请求的,查看RequestBody类就可以发现RequestBody只有三个主要方法1
2
3
4
5
6
7
8
9
10
11
12
13/** Returns the Content-Type header for this body. */
public abstract @Nullable MediaType contentType();
/**
* Returns the number of bytes that will be written to {@code sink} in a call to {@link #writeTo},
* or -1 if that count is unknown.
*/
public long contentLength() throws IOException {
return -1;
}
/** Writes the content of this request to {@code sink}. */
public abstract void writeTo(BufferedSink sink) throws IOException;
根据注释可以明确各个方法的作用
- contentType() 返回Content-Type
- contentLength() 返回已写入数据大小
- writeTo 将数据写入BufferedSink
RequestBody还提供了几个请求的实现,当上传文件的时候的实现方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/** Returns a new request body that transmits the content of {@code file}. */
public static RequestBody create(final @Nullable MediaType contentType, final File file) {
if (file == null) throw new NullPointerException("file == null");
return new RequestBody() {
public MediaType contentType() {
return contentType;
}
public long contentLength() {
return file.length();
}
public void writeTo(BufferedSink sink) throws IOException {
Source source = null;
try {
source = Okio.source(file);
sink.writeAll(source);
} finally {
Util.closeQuietly(source);
}
}
};
}
因此只要获取BufferedSink的大小就可以获取文件传输的大小,但是OkHttp提供的方法中是将文件通过writeAll()进行写入的,无法确定每个时间段具体传输了多少,继续查看writeAll()1
2
3
4
5
6
7
8public long writeAll(Source source) throws IOException {
if (source == null) throw new IllegalArgumentException("source == null");
long totalBytesRead = 0;
for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) {
totalBytesRead += readCount;
}
return totalBytesRead;
}
到这就很明了了,writeAll每次写入的是Segment.SIZE(8192)字节,我们只要将writeAll提取出来就可以自己控制上传的进度了
1 | public class ProgressRequestBody extends RequestBody { |
调用的时候也只需要将1
RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
替换成1
RequestBody requestBody = new ProgressRequestBody(MediaType.parse("application/octet-stream"), file);
就可以了