android volley multipart

最近主要从事android app方向开发工作,在项目里遇到了一个关于图片上传使用multipart的问题,所以拿出来说说。

项目的服务端用php写的,服务端童鞋在写关于图片上传api的时候直接用了 $_FILE,因此需要客户端实现multipart请求来完成图片上传。在android-async-http库里,只要params.add(“foo”, file) 就传入File对象,然后直接发送请求,底层就会帮你实现multipart。而在我刚接手这个项目的时候我选择了volley,其原因是因为android-async-http底层使用apache的HttpClient库,而这个库在6.0版本中已经被移除了(不是废弃)。

volley库在正常情况下其实是很好用的,但我没有找到关于multipart的支持,所以只好自己写一个简单拼接multipart的writer。

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package org.vizee.mime;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.Random;

/**
* Simple multipart body writer
* Created by vizee on 15-10-10.
*/
public class MultipartWriter {

private static final byte[] APPLICATION_OCTET_STREAM = "application/octet-stream".getBytes();
private static final byte[] CONTENT_TYPE = "Content-Type: ".getBytes();
private static final byte[] CONTENT_DISPOSITION_FORM_DATA = "Content-Disposition: form-data; ".getBytes();
private static final byte[] CONTENT_DISPOSITION_NAME = "name=\"".getBytes();
private static final byte[] CONTENT_DISPOSITION_FILENAME = "filename=\"".getBytes();
private static final byte[] CONTENT_DISPOSITION_SPLIT = "\"; ".getBytes();
private static final byte[] CONTENT_DISPOSITION_END = "\"\r\n".getBytes();
private static final byte[] CRLF = "\r\n".getBytes();
private static final byte[] TRANSFER_ENCODING_BINARY = ("Content-Transfer-Encoding: binary\r\n").getBytes();

private static final char[] MULTIPART_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();

private String boundary;
private LinkedList<byte[]> parts;

public MultipartWriter() {
this(randomBoundary(30));
}

public MultipartWriter(String boundary) {
this.boundary = boundary;
parts = new LinkedList<>();
}

public String getBoundary() {
return boundary;
}

public void add(String key, String value) throws IOException {
add(key, value, "text/plain; charset=UTF-8");
}

public void add(String key, String value, String type) throws IOException {
// magic capacity
ByteArrayOutputStream part = new ByteArrayOutputStream(100);
addContentDisposition(part, key);
addContentType(part, type);
part.write(CRLF);
part.write(value.getBytes());
part.write(CRLF);
parts.add(part.toByteArray());
}

public void add(String key, File file) throws IOException {
FileInputStream inputStream = new FileInputStream(file);
try {
add(key, file.getName(), inputStream, null);
} finally {
try {
inputStream.close();
} catch (IOException ignored) {
}
}
}

public void add(String key, String filename, InputStream inputStream, String type) throws IOException {
ByteArrayOutputStream part = new ByteArrayOutputStream(180);
addContentDisposition(part, key, filename);
addContentType(part, type);
part.write(TRANSFER_ENCODING_BINARY);
part.write(CRLF);
final byte[] buf = new byte[4096];
int n;
while ((n = inputStream.read(buf)) != -1) {
part.write(buf, 0, n);
}
part.write(CRLF);
parts.add(part.toByteArray());
}

public byte[] getBytes() {
byte[] boundaryDelim = ("--" + boundary + "\r\n").getBytes();
byte[] boundaryEnd = ("--" + boundary + "--\r\n").getBytes();
int total = parts.size() * boundaryDelim.length + boundaryEnd.length;
for (byte[] part : parts) {
total += part.length;
}
byte[] bytes = new byte[total];
int p = 0;
for (byte[] part : parts) {
System.arraycopy(boundaryDelim, 0, bytes, p, boundaryDelim.length);
p += boundaryDelim.length;
System.arraycopy(part, 0, bytes, p, part.length);
p += part.length;
}
System.arraycopy(boundaryEnd, 0, bytes, p, boundaryEnd.length);
return bytes;
}

private static String randomBoundary(int length) {
char[] chars = new char[length];
Random rand = new Random();
for (int i = 0; i < length; i++) {
chars[i] = MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)];
}
return String.valueOf(chars);
}

private static void addContentType(ByteArrayOutputStream header, String type) throws IOException {
header.write(CONTENT_TYPE);
if (type == null) {
header.write(APPLICATION_OCTET_STREAM);
} else {
header.write(type.getBytes());
}
header.write(CRLF);
}

private static void addContentDisposition(ByteArrayOutputStream header, String name) throws IOException {
addContentDisposition(header, name, null);
}

private static void addContentDisposition(ByteArrayOutputStream header, String name, String filename) throws IOException {
header.write(CONTENT_DISPOSITION_FORM_DATA);
header.write(CONTENT_DISPOSITION_NAME);
header.write(name.getBytes());
if (filename != null) {
header.write(CONTENT_DISPOSITION_SPLIT);
header.write(CONTENT_DISPOSITION_FILENAME);
header.write(filename.getBytes());
}
header.write(CONTENT_DISPOSITION_END);
}
}

然后需要重载volley.Request

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
package org.vizee.mime;

import com.android.volley.Request;
import com.android.volley.Response;

/**
* Volley multipart request
* Created by vizee on 15-10-10.
*/
public abstract class MultipartRequest<T> extends Request<T> {

private byte[] body;
private String contentType;

public MultipartRequest(int method, String url, MultipartWriter writer, Response.ErrorListener listener) {
super(method, url, listener);
body = writer.getBytes();
contentType = "multipart/form-data; boundary=" + writer.getBoundary();
}

@Override
public String getBodyContentType() {
return contentType;
}

@Override
public byte[] getBody() {
return body;
}
}

至于volley怎么用就不用说了吧~