Android中利用volley同时上传文件和文本参数

前言

在做项目时利用了 Volley 网络库与服务器之间进行通信,但 Volley 并没有现成的上传文件的 Request 可以调用,网上搜索一圈发现很多都是基于 HttpClient 进行自定义 Request 封装,可是无奈 Android 6.0 之后移除了 HttpClient 的代码,只能自己基于 HttpURLConnection 封装一个 Request 使用。

继承 Request,重写一些方法

由于我的项目中服务器返回的结果都是 json 格式,所以我自定义一个返回结果是 json 格式的 request,你也可以换成 String 格式,或者其他你需要的格式。

1
2
3
4
5
6
7
8
9
10
11
12
public class PostUploadRequest extends Request<JSONObject>{
public PostUploadRequest(String url, Map<String, String[]> fileMap, Response.Listener<JSONObject> mListener, Response.ErrorListener listener) {
super(Method.POST, url, listener);
this.mListener = mListener;
this.fileMap = fileMap;
setShouldCache(false);
setRetryPolicy(new DefaultRetryPolicy(5000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
}
}

重写 parseNetworkResponse() 方法

将返回结果转换成 json 格式

1
2
3
4
5
6
7
8
try {
String je = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
return Response.success(new JSONObject(je), HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException var3) {
return Response.error(new ParseError(var3));
} catch (JSONException var4) {
return Response.error(new ParseError(var4));
}

重写 deliverResponse() 方法

为请求添加监听回调事件

1
2
3
4
@Override
protected void deliverResponse(JSONObject jsonObject) {
mListener.onResponse(jsonObject);
}

模仿浏览器上传文件请求

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
private void buildFilePart(DataOutputStream ds) {
StringBuilder sb = new StringBuilder();
Object[] key_arr = fileMap.keySet().toArray();
for (Object name : key_arr) {
String[] val = fileMap.get(name.toString());
String path = val[0];
String filename = val[1];
try {
sb.append(prefix);
sb.append(boundary);
sb.append(end);
sb.append("Content-Disposition: form-data;name=\"");
sb.append(name.toString());
sb.append("\";filename=\"");
sb.append(filename);
sb.append("\"" + end);//filename是文件名,如xxx.jpg,file是服务器传递参数的名字
sb.append("Content-Type: application/octet-stream;charset=UTF-8" + end);
sb.append(end);
ds.write(sb.toString().getBytes("UTF-8"));
/* 取得文件的FileInputStream */
FileInputStream fStream = new FileInputStream(path);//path是文件本地地址
/* 设置每次写入1024bytes */
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length;
/* 从文件读取数据至缓冲区 */
while ((length = fStream.read(buffer)) != -1) {
/* 将资料写入DataOutputStream中 */
ds.write(buffer, 0, length);
}
ds.write(end.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
}

模仿浏览器传递普通参数请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void buildTextPart(DataOutputStream ds) {
try {
Map<String, String> map = getParams();
Object[] key_arr = map.keySet().toArray();
for (Object name : key_arr) {
String val = map.get(name.toString());
ds.write((prefix + boundary + end).getBytes("UTF-8"));
ds.write(("Content-Disposition: form-data;name=\"" + name.toString() + "\"" + end).getBytes("UTF-8"));
ds.write(("Content-Type: text/plain;charset=UTF-8" + end).getBytes("UTF-8"));
ds.write((end).getBytes("UTF-8"));
ds.write((val + end).getBytes("UTF-8"));
}
} catch (IOException | AuthFailureError e) {
e.printStackTrace();
}
}

重写 getBody() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public byte[] getBody() throws AuthFailureError {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream ds = new DataOutputStream(bos);
buildTextPart(ds);
buildFilePart(ds);
ds.write((prefix + boundary + prefix + end).getBytes("UTF-8"));
ds.flush();
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

使用

和正常使用 Volley 提供的 Request 一样使用,不同的是构造方法中传递参数有略微不同,传递参数中 Map<String,String[]> fileMap
key 存放的是参数名
value 是长度为2的字符串数组, 下标0存放的是文件的本地路径,下标1存放的是传送到服务器中的文件名,通过 fileMap 可以一次性上传多个参数,如果需要同时上传普通文本参数,可以在构造 request 是重写 getParams() 方法以 Map<String,String> 形式传递多个文本参数和参数值,类似 StringRequest. 示例代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Response.Listener<JSONObject> listener = new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject jsonObject) {
//请求成功后的处理
}
};
Response.ErrorListener errorListener = new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
//请求失败后的处理
};
PostUploadRequest request = new PostUploadRequest(url, fileMap, listener, errorListener) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
return map;//map存放的是普通参数
}
};
//RequestQueue.add(request)
//发起请求

以下是 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
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
public class PostUploadRequest extends Request<JSONObject> {
private final String prefix = "--";
private final String end = "\r\n";
private final String boundary = "--------------" + System.currentTimeMillis();
private final String mimeType = "multipart/form-data;boundary=" + boundary;
private Response.Listener<JSONObject> mListener;
private Map<String, String[]> fileMap;
public PostUploadRequest(String url, Map<String, String[]> fileMap, Response.Listener<JSONObject> mListener, Response.ErrorListener listener) {
super(Method.POST, url, listener);
this.mListener = mListener;
this.fileMap = fileMap;
setShouldCache(false);
setRetryPolicy(new DefaultRetryPolicy(5000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
}
@Override
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
try {
String je = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
return Response.success(new JSONObject(je), HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException var3) {
return Response.error(new ParseError(var3));
} catch (JSONException var4) {
return Response.error(new ParseError(var4));
}
}
@Override
protected void deliverResponse(JSONObject jsonObject) {
mListener.onResponse(jsonObject);
}
@Override
public String getBodyContentType() {
return mimeType;
}
@Override
public byte[] getBody() throws AuthFailureError {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream ds = new DataOutputStream(bos);
buildTextPart(ds);
buildFilePart(ds);
ds.write((prefix + boundary + prefix + end).getBytes("UTF-8"));
ds.flush();
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private void buildFilePart(DataOutputStream ds) {
StringBuilder sb = new StringBuilder();
Object[] key_arr = fileMap.keySet().toArray();
for (Object name : key_arr) {
String[] val = fileMap.get(name.toString());
String path = val[0];
String filename = val[1];
try {
sb.append(prefix);
sb.append(boundary);
sb.append(end);
sb.append("Content-Disposition: form-data;name=\"");
sb.append(name.toString());
sb.append("\";filename=\"");
sb.append(filename);
sb.append("\"" + end);//filename是文件名,如xxx.jpg,file是服务器传递参数的名字
sb.append("Content-Type: application/octet-stream;charset=UTF-8" + end);
sb.append(end);
ds.write(sb.toString().getBytes("UTF-8"));
/* 取得文件的FileInputStream */
FileInputStream fStream = new FileInputStream(path);//path是文件本地地址
/* 设置每次写入1024bytes */
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length;
/* 从文件读取数据至缓冲区 */
while ((length = fStream.read(buffer)) != -1) {
/* 将资料写入DataOutputStream中 */
ds.write(buffer, 0, length);
}
ds.write(end.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void buildTextPart(DataOutputStream ds) {
try {
Map<String, String> map = getParams();
Object[] key_arr = map.keySet().toArray();
for (Object name : key_arr) {
String val = map.get(name.toString());
ds.write((prefix + boundary + end).getBytes("UTF-8"));
ds.write(("Content-Disposition: form-data;name=\"" + name.toString() + "\"" + end).getBytes("UTF-8"));
ds.write(("Content-Type: text/plain;charset=UTF-8" + end).getBytes("UTF-8"));
ds.write((end).getBytes("UTF-8"));
ds.write((val + end).getBytes("UTF-8"));
}
} catch (IOException | AuthFailureError e) {
e.printStackTrace();
}
}
}

一些话
代码上的实现主要是为了手上项目,通用性上可能会有所欠缺,如果使用过程中出现bug或者有一些疑问,欢迎评论指出,或者联系我的邮箱:13813005628@163.com