Volley 简单使用(四) Volley+HttpComponents进行多文件上传

由于Volley在解析期间将所有数据保存在内存中,不适合文件下载或流式操作,因此Volley本身并没有提供上传及下载的Request,大文件上传下载是完全不适合使用的,但是小文件还是可以勉强使用的。
对于HttpComponents一定不会陌生,使用过HttpClient就一定知道,使用HttpComponents的原因只是为了简化操作,将复杂的流操作都由其进行处理。

首先需要添加HttpComponents相关依赖

1
2
3
4
implementation 'org.apache.httpcomponents:httpcore:4.4.11'
implementation('org.apache.httpcomponents:httpmime:4.3.1') {
exclude module: 'httpclient'
}

添加依赖的时候要注意的是需要排除’httpclient’,否则容易出现类冲突
另外还需要添加打包配置

1
2
3
4
5
6
7
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}

自定义Request

首先需要自定义一个Request,一般返回结果都为JSON或者String,所以为了偷懒,直接继承自StringRequest。
构建两个构造函数用于单文件上传和多文件上传,上传时需要传入文件以及监听文件上传时的进度

构造函数

1
2
3
public MultipartRequest(String url, File file, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener, MultipartProgressListener mProgressListener, String... key) {}

public MultipartRequest(String url, List<File> mFiles, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener, MultipartProgressListener mProgressListener, String... keys) {}

key为上传文件所对应的参数名称,如果传入参数为空,则以文件名作为该文件的参数名

进度回调

1
2
3
public interface MultipartProgressListener {
void transferred(long transferred, int progress);
}

文件写入

文件写入通过Volley的getBody()进行,将文件及参数转化为byte[]进行传输。
进行文件写入时就需要使用HttpComponents,通过MultipartEntityBuilder将文件转换为流,然后通过HttpEntity转换成Byte[] 。

将文件写入HttpEntity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void buildMultipartEntity() {
multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntityBuilder.setCharset(Charset.forName("UTF-8"));
if (mFiles != null && mFiles.size() > 0) {
// 判断文件数量和参数数量是否一致
if (keys.length != mFiles.size() && keys.length != 0) {
mErrorListener.onErrorResponse(new VolleyError("The number of keys differs from the number of files"));
this.cancel();
return;
}
for (int i = 0; i < mFiles.size(); i++) {
File file = mFiles.get(i);
mFileLength += file.length();
// 如果参数不存在,则将文件名称作为参数名
if (keys.length == 0) {
keys = new String[mFiles.size()];
keys[i] = file.getName().split("\\.")[0];
}
multipartEntityBuilder.addPart(keys[i], new FileBody(file));
}
httpEntity = multipartEntityBuilder.build();
}
}

通过getBody()将文件转化为byte[]传入

1
2
3
4
5
6
7
8
9
10
public byte[] getBody() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
httpEntity.writeTo(new CountingOutputStream(baos, mFileLength, mProgressListener));
} catch (IOException e) {
e.printStackTrace();
mErrorListener.onErrorResponse(new VolleyError(e));
}
return baos.toByteArray();
}

监听文件写入进度作为上传进度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class CountingOutputStream extends FilterOutputStream {
private final MultipartProgressListener progressListener;
private long transferred;
private long fileLength;

public CountingOutputStream(final OutputStream out, long fileLength, final MultipartProgressListener listener) {
super(out);
this.fileLength = fileLength;
this.progressListener = listener;
this.transferred = 0;
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
if (progressListener != null) {
this.transferred += len;
int progress = (int) (transferred * 100 / fileLength);
this.progressListener.transferred(this.transferred, progress);
}
}

@Override
public void write(int b) throws IOException {
out.write(b);
if (progressListener != null) {
this.transferred++;
int progress = (int) (transferred * 100 / fileLength);
this.progressListener.transferred(this.transferred, progress);
}
}
}

完整示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
public class MultipartRequest extends StringRequest {

private List<File> mFiles;
private Response.ErrorListener mErrorListener;
private long mFileLength;

private MultipartEntityBuilder multipartEntityBuilder;
private HttpEntity httpEntity;
private MultipartProgressListener mProgressListener;
private String[] keys;

public MultipartRequest(String url, File file, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener, MultipartProgressListener mProgressListener, String... keys) {
super(Method.POST, url, listener, errorListener);
this.mErrorListener = errorListener;
this.mProgressListener = mProgressListener;
this.keys = keys;
mFileLength = 0;
mFiles = new ArrayList<>();
if (file != null) {
mFiles.add(file);
}
buildMultipartEntity();
}

public MultipartRequest(String url, List<File> mFiles, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener, MultipartProgressListener mProgressListener, String... keys) {
super(Method.POST, url, listener, errorListener);
this.mErrorListener = errorListener;
this.mProgressListener = mProgressListener;
this.mFiles = mFiles;
this.keys = keys;
mFileLength = 0;
buildMultipartEntity();
}

private void buildMultipartEntity() {
multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntityBuilder.setCharset(Charset.forName("UTF-8"));

if (mFiles != null && mFiles.size() > 0) {
if (keys.length != mFiles.size() && keys.length != 0) {
mErrorListener.onErrorResponse(new VolleyError("The number of keys differs from the number of files"));
this.cancel();
return;
}
for (int i = 0; i < mFiles.size(); i++) {
File file = mFiles.get(i);
mFileLength += file.length();
if (keys.length == 0) {
keys = new String[mFiles.size()];
keys[i] = file.getName().split("\\.")[0];
}
multipartEntityBuilder.addPart(keys[i], new FileBody(file));
}
httpEntity = multipartEntityBuilder.build();
}
}

@Override
public String getBodyContentType() {
return httpEntity.getContentType().getValue();
}

@Override
public byte[] getBody() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
httpEntity.writeTo(new CountingOutputStream(baos, mFileLength, mProgressListener));
} catch (IOException e) {
e.printStackTrace();
mErrorListener.onErrorResponse(new VolleyError(e));
}
return baos.toByteArray();
}

class CountingOutputStream extends FilterOutputStream {
private final MultipartProgressListener progressListener;
private long transferred;
private long fileLength;

public CountingOutputStream(final OutputStream out, long fileLength, final MultipartProgressListener listener) {
super(out);
this.fileLength = fileLength;
this.progressListener = listener;
this.transferred = 0;
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
if (progressListener != null) {
this.transferred += len;
int progress = (int) (transferred * 100 / fileLength);
this.progressListener.transferred(this.transferred, progress);
}
}

@Override
public void write(int b) throws IOException {
out.write(b);
if (progressListener != null) {
this.transferred++;
int progress = (int) (transferred * 100 / fileLength);
this.progressListener.transferred(this.transferred, progress);
}
}

}

public interface MultipartProgressListener {
void transferred(long transferred, int progress);
}
}

调用

创建回调监听和进度监听

1
2
private Response.Listener<String> mResultListener = response -> {};
private MultipartRequest.MultipartProgressListener mProgressListener = (transferred, progress) -> {};

上传请求

1
2
3
4
5
6
7
8
9
10
11
String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/1.jpg";
File file = new File(filePath);
if (file.exists()) {
List<File> files = new ArrayList<>();
files.add(file);
files.add(file);
MultipartRequest uploadRequest = new MultipartRequest(URL_UPLOAD, files, mResultListener, mErrorListener, mProgressListener, "file", "file");
mQueue.add(uploadRequest);
} else {
Toast.makeText(mContext, "file not found", Toast.LENGTH_SHORT).show();
}