前言
网上有很多关于Volley实现Multipart表单多文件上传的文章,引用了其他第三方库,感觉有点多余,我也是参考了部分文章,而后进行总结优化。下面分享给大家。
分析
首先我们要了解MultiPart表单上传格式,网上很多资料,这里就不累赘了。接下来我们就要想办法往connection的OutputStream
通道里写入数据。
源码Request也提供了getBody()
方法让我们可以提供二进制数据。但是它这里是一次性的,如果文件太大,可能会撑爆内存。所以我们另寻其他实现方法。其实通过分析源码,我们不难发现数据提交最终都在HurlStack.java
的addBody(HttpURLConnection connection, Request<?> request, byte[] body)
方法中提交的。如此,我们就可以想办法拿到DataOutputStream对象,我们就可以写任何数据了。
1、我们复制一份HurlStack代码重命名为CustomHurlStack.java
。我们在addBody(HttpURLConnection connection, Request<?> request, byte[] body)
方法中把DataOutputStream对象回调给Request。
HurlStack.java
:
image.png
重写后
CustomHurlStack.java
:
image.png
IUpload.java
public interface IUpload {
void write(DataOutputStream out) throws IOException;
}
2、封装Request:MultipartRequest.java
package com.jsc.volleylibrary;
import android.webkit.MimeTypeMap;
import androidx.annotation.Nullable;
import com.android.volley.AuthFailureError;
import com.android.volley.Response;
import com.android.volley.toolbox.StringRequest;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class MultipartRequest extends StringRequest implements IUpload {
private final List<Part> parts = new ArrayList<>();
private final String twoHyphens = "--";
private final String lineEnd = "\r\n";
private final String BOUNDARY = "multipart";
private OnUploadListener uploadListener = null;
public MultipartRequest(String url, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener) {
super(Method.POST, url, listener, errorListener);
}
public MultipartRequest addPart(String name, String value) {
return addPart(Part.of(name, value));
}
public MultipartRequest addPart(String name, File file) {
return addPart(Part.of(name, file));
}
public MultipartRequest addPart(String name, String fileName, String mimeType, byte[] content) {
return addPart(Part.of(name, fileName, mimeType, content));
}
public MultipartRequest addPart(Part part) {
parts.add(part);
return this;
}
public MultipartRequest listenUpload(OnUploadListener uploadListener) {
this.uploadListener = uploadListener;
return this;
}
@Override
public String getBodyContentType() {
return "multipart/form-data; boundary=" + BOUNDARY;
}
@Override
public byte[] getBody() throws AuthFailureError {
return new byte[0];
}
@Override
public void write(DataOutputStream out) throws IOException {
if (uploadListener != null) {
uploadListener.onStart();
}
for (Part part : parts) {
if (part.type == 2) {
writeContentPart(out, part);
} else if (part.type == 1) {
writeFilePart(out, part);
} else {
writeStringPart(out, part);
}
}
if (!parts.isEmpty()) {
/*结尾行:"--" + boundary + "--" + "\r\n" ;*/
write(out, twoHyphens + BOUNDARY + twoHyphens + lineEnd);
}
if (uploadListener != null) {
uploadListener.onFinish();
}
}
private void writeContentPart(DataOutputStream out, Part part) throws IOException {
/*第一行:"--" + boundary + "\r\n" ;*/
write(out, twoHyphens + BOUNDARY + lineEnd);
/*第二行:"Content-Disposition: form-data; name="参数的名称"" + "\r\n" ;*/
write(out, String.format(Locale.US, "Content-Disposition: form-data; name=\"%1s\"; filename=\"%2s\"" + lineEnd, part.name, part.fileName));
/*第三行:Content-Type: 文件的 mime 类型 + "\r\n"*/
write(out, String.format(Locale.US, "Content-Type: %s" + lineEnd, part.mimeType));
/*第四行:"\r\n" ;*/
write(out, lineEnd);
/*第五行:文件的二进制数据 + "\r\n"*/
write(out, part.content);
write(out, lineEnd);
}
private void writeFilePart(DataOutputStream out, Part part) throws IOException {
/*第一行:"--" + boundary + "\r\n" ;*/
write(out, twoHyphens + BOUNDARY + lineEnd);
/*第二行:"Content-Disposition: form-data; name="参数的名称"" + "\r\n" ;*/
write(out, String.format(Locale.US, "Content-Disposition: form-data; name=\"%1s\"; filename=\"%2s\" " + lineEnd, part.name, part.file.getName()));
/*第三行:Content-Type: 文件的 mime 类型 + "\r\n"*/
write(out, String.format(Locale.US, "Content-Type: %s" + lineEnd, getMimeTypeFromExtension(part.file.getPath())));
/*第四行:"\r\n" ;*/
write(out, lineEnd);
/*第五行:文件的二进制数据 + "\r\n"*/
try (FileInputStream fis = new FileInputStream(part.file)) {
long total = part.file.length();
long progress = 0L;
if (uploadListener != null) {
uploadListener.onProgress(part.file, total, progress);
}
int count = 0;
byte[] buffer = new byte[4096];
int len = 0;
while ((len = fis.read(buffer)) != -1) {
out.write(buffer, 0, len);
progress += len;
count++;
if (count >= 25) {
if (uploadListener != null) {
uploadListener.onProgress(part.file, total, progress);
}
count = 0;
}
}
if (count > 0) {
if (uploadListener != null) {
uploadListener.onProgress(part.file, total, progress);
}
}
}
write(out, lineEnd);
}
private void writeStringPart(DataOutputStream out, Part part) throws IOException {
/*第一行:"--" + boundary + "\r\n" ;*/
write(out, twoHyphens + BOUNDARY + lineEnd);
/*第二行:"Content-Disposition: form-data; name="参数的名称"" + "\r\n" ;*/
write(out, String.format(Locale.US, "Content-Disposition: form-data; name=\"%s\"" + lineEnd, part.name));
/*第三行:"\r\n" ;*/
write(out, lineEnd);
/*第四行:"参数的值" + "\r\n" ;*/
write(out, part.value + lineEnd);
}
private String getMimeTypeFromExtension(String url) {
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
return type;
}
private static void write(DataOutputStream out, String content) throws IOException {
write(out, content.getBytes());
}
private static void write(DataOutputStream out, byte[] content) throws IOException {
out.write(content);
}
public static class Part {
String name;
String value;
File file;
String fileName;
String mimeType;
byte[] content;
int type = 0;
private Part() {
}
public static Part of(String name, String value) {
Part part = new Part();
part.name = name;
part.value = value;
part.type = 0;
return part;
}
public static Part of(String name, File file) {
Part part = new Part();
part.name = name;
part.file = file;
part.type = 1;
return part;
}
public static Part of(String name, String fileName, String mimeType, byte[] content) {
Part part = new Part();
part.name = name;
part.fileName = fileName;
part.mimeType = mimeType;
part.content = content;
part.type = 2;
return part;
}
}
}
3、上传监听器。
public interface OnUploadListener {
void onStart();
void onProgress(File file, long total, long progress);
void onFinish();
}
4、简单使用示例。
private void download() {
RequestQueue mRequestQueue = Volley.newRequestQueue(this, new CustomHurlStack());
RetryPolicy policy = new DefaultRetryPolicy(10_000, 1, 1);
File file = new File(getExternalFilesDir("collect"), "90926.jpg");
String url = "http://192.168.0.230/cims/api/collect/app_uploadKsxx";
MultipartRequest request = new MultipartRequest(url, response -> {
ImitateToast.show(response);
}, error -> {
ImitateToast.show("上传失败");
})
.addPart("ksjhid", "db10c32ee24b4fd5ac6531f67b90f18b")
.addPart("collectKsInfo.file", file)
.addPart("collectKsInfo.examinee.ksxm", "王五")
.addPart("collectKsInfo.examinee.xb", "0")
.addPart("collectKsInfo.examinee.mz", "汉")
.addPart("sbno", "0123456789")
.listenUpload(new OnUploadListener() {
@Override
public void onStart() {
}
@Override
public void onProgress(File file, long total, long progress) {
}
@Override
public void onFinish() {
}
});
request.setTag("download");
request.setRetryPolicy(policy);
request.setShouldCache(false);
mRequestQueue.add(request);
}