Volley实现Multipart表单多文件上传进阶篇

前言

  网上有很多关于Volley实现Multipart表单多文件上传的文章,引用了其他第三方库,感觉有点多余,我也是参考了部分文章,而后进行总结优化。下面分享给大家。

分析

  首先我们要了解MultiPart表单上传格式,网上很多资料,这里就不累赘了。接下来我们就要想办法往connection的OutputStream通道里写入数据。
源码Request也提供了getBody()方法让我们可以提供二进制数据。但是它这里是一次性的,如果文件太大,可能会撑爆内存。所以我们另寻其他实现方法。其实通过分析源码,我们不难发现数据提交最终都在HurlStack.javaaddBody(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);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容