Volley框架(四):使用Volley上传文件

来源:互联网 发布:rpc java 编辑:程序博客网 时间:2024/05/01 12:50

在上一篇博客《Volley框架(三):使用Volley提交表单数据》中,我们已经知道了使用Volley提交表单数据,这篇博客我们来说一下使用Volley实现上传文件。


首先,我们还是一样,通过网络抓包看看上传文件的格式是什么,与提交表单数据有什么不同。

POST http://www.aaa.com/?ashx/login.ashx HTTP/1.1Host: www.aaa.comUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3Accept-Encoding: gzip, deflateConnection: keep-aliveUpgrade-Insecure-Requests: 1Content-Type: multipart/form-data; boundary=---------------------------29518470621122Content-Length: 6462-----------------------------29518470621122Content-Disposition: form-data; name="login_username"abcde-----------------------------29518470621122Content-Disposition: form-data; name="login_pswd"12345-----------------------------29518470621122Content-Disposition: form-data; name="upfile"; filename="error_img.jpg"Content-Type: image/jpeg?? JFIF      ? C  $.' ",#(7),01444'9=82<.342? C2!!22222222222222222222222222222222222222222222222222?  -----------------------------29518470621122--
如果看了上一篇博客的分析,那么在这里我们很容易就知道先是提交的2条文字数据,然后这里多了一段,和提交文字数据的格式类似,只有部分不同的数据;没错,这里多的这一段就是上传文件的格式,乱码部分就是文件的二进制数据(算做一行),然后最后有一个结束符,和提交表单数据一样。
下面我们分析一下提交文件的格式:
第一行:"--" + boundary + "\r\n"
说明:”–”为数据开始标志,boundary为http实体头定义的边界分割线,boundary可以是任意的字符串,只要和Content-Type: multipart/form-data; boundary=----29518470621122中保持一直即可,”\r\n”为回车换行; 
第二行:Content-Disposition: form-data; name="参数的名称"; filename="上传的文件名" + "\r\n"
说明:这里比普通的表单多了一个filename=”上传的文件名”; 
第三行:Content-Type: 文件的 mime 类型 + "\r\n"
说明:这一行是文件上传必须要的,而普通的文字提交可有可无,mime 类型需要根据文档查询;
第四行:\r\n
说明:只有换行符;
第五行:文件的二进制数据 + "\r\n"
说明:文件的二进制数据加上换行符。

结束标志:"--" + boundary + "--" + "\r\n" 
说明:在所有的数据结束之后,需要有这个结尾标志。 

文件也可以同时上传多个文件,上传多个文件的时候重复1、2、3、4、5步,在最后的一个文件的末尾加上统一的结束行。


通过上面对格式的分析,我们发现文件上传和表单提交数据一样,所以我们也使用同样的方法实现文件上传,只需自定义一个Request就可以了,同样也需要重写两个方法:
① 需要重写获取实体的方法

public byte[] getBody() throws AuthFailureError{}
② 在提交表单数的时候需要在 http 头部中声明内容类型为表单数据
重写下面的方法,返回声明的内容数据类型
public String getBodyContentType() {// multipart/form-data; boundary=----WebKitFormBoundarykR96Kta4gvMACHfqreturn MULTIPART_FORM_DATA + ";boundary=" + BOUNDARY;}
自定义Request在《Volley框架(一):使用Volley请求数据》这篇博客中有说到。


自定义文件上传实体:

public class FileEntity {    /**     * 参数名称     */    public String mName;    /**     * 上传的文件名     */    public String mFileName;    /**     * 需要上传的文件     */    public File mFile;    /**     * 文件的 mime,需要根据文档查询<br/>     * 默认使用 application/octet-stream  任意的二进制数据     */    public String mMime = "application/octet-stream";    public FileEntity() {    }    public FileEntity(String mName, String mFileName, File mFile, String mMime) {        this.mName = mName;        this.mFileName = mFileName;        this.mFile = mFile;        this.mMime = mMime;    }    public byte[] getFileBytes() {        FileInputStream fileInputStream = null;        ByteArrayOutputStream outputStream = null;        try {            fileInputStream = new FileInputStream(mFile);            outputStream = new ByteArrayOutputStream();            int len;            byte[] bytes = new byte[1024];            while ((len = fileInputStream.read(bytes)) != -1){                outputStream.write(bytes,0,len);            }            return outputStream.toByteArray();        } catch (IOException e) {            e.printStackTrace();        }finally{            if (fileInputStream != null) {                try {                    fileInputStream.close();                } catch (IOException e) {                    e.printStackTrace();                }            }            if (outputStream != null) {                try {                    outputStream.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }        return null;    }}

自定义上传文件的Request:
public class MultipartRequest extends Request<String> {    private final String MULTIPART_FORM_DATA = "multipart/form-data"; // 数据类型    private final String BOUNDARY = "---------" + UUID.randomUUID().toString(); // 随机生成边界分隔线    private final String NEW_LINE = "\r\n"; // 换行符    private Map<String, Object> mParams;    private List<FileEntity> mFileEntityList;    private FileEntity mFileEntity;    private Response.Listener<String> mListener;    private Charset mCharSet = Charset.defaultCharset();    private boolean isSingleFile = false;    public MultipartRequest(String url, Map<String, Object> params, Charset charSet, List<FileEntity> fileEntityList, Response.Listener<String> listener, Response.ErrorListener errorListener) {        super(Method.POST, url, errorListener);        this.mParams = params;        this.mCharSet = charSet;        this.mFileEntityList = fileEntityList;        this.mListener = listener;        this.isSingleFile = false;    }    public MultipartRequest(String url, Map<String, Object> params, Charset charSet, FileEntity fileEntity, Response.Listener<String> listener, Response.ErrorListener errorListener) {        super(Method.POST, url, errorListener);        this.mParams = params;        this.mCharSet = charSet;        this.mFileEntity = fileEntity;        this.mListener = listener;        this.isSingleFile = true;    }    public MultipartRequest(String url, Map<String, Object> params, List<FileEntity> fileEntityList, Response.Listener<String> listener, Response.ErrorListener errorListener) {        this(url, params, Charset.defaultCharset(), fileEntityList, listener, errorListener);    }    public MultipartRequest(String url, Map<String, Object> params, FileEntity fileEntity, Response.Listener<String> listener, Response.ErrorListener errorListener) {        this(url, params, Charset.defaultCharset(), fileEntity, listener, errorListener);    }    @Override    protected Response<String> parseNetworkResponse(NetworkResponse response) {        try {            Log.i("MultipartRequest", new String(response.data, "utf-8"));            return Response.success(new String(response.data, HttpHeaderParser.parseCharset(response.headers)), HttpHeaderParser.parseCacheHeaders(response));        } catch (UnsupportedEncodingException e) {            e.printStackTrace();            return Response.error(new ParseError(e));        }    }    @Override    protected void deliverResponse(String response) {        mListener.onResponse(response);    }    @Override    public byte[] getBody() throws AuthFailureError {        if (isSingleFile) { // 单文件上传            return singleFileUp();        } else { // 多个文件上传            return multipleFileUp();        }    }    /**     * 多个文件上传     *     * @return     * @throws AuthFailureError     */    private byte[] multipleFileUp() throws AuthFailureError {        if ((mParams == null || mParams.size() <= 0) && (mFileEntityList == null || mFileEntityList.size() <= 0)) {            // 没有参数也没有文件            return super.getBody();        } else {            ByteArrayOutputStream bos = new ByteArrayOutputStream();            if (mParams != null && mParams.size() > 0) {                // 有参数,先拼接参数                paramsFormat(bos);            }            if (mFileEntityList != null && mFileEntityList.size() > 0) {                // 有文件,提交文件                for (FileEntity fileEntity : mFileEntityList) {                    fileFormat(bos, fileEntity);                }            }            // 所有参数拼接完成,拼接结束行            endLine(bos);            return bos.toByteArray();        }    }    /**     * 单个文件上传     *     * @return     * @throws AuthFailureError     */    private byte[] singleFileUp() throws AuthFailureError {        if ((mParams == null || mParams.size() <= 0) && (mFileEntity == null)) {            // 没有参数也没有文件            return super.getBody();        } else {            ByteArrayOutputStream bos = new ByteArrayOutputStream();            if (mParams != null && mParams.size() > 0) {                // 有参数,先拼接参数                paramsFormat(bos);            }            if (mFileEntity != null) {                // 有文件,提交文件                fileFormat(bos, mFileEntity);            }            // 所有参数拼接完成,拼接结束行            endLine(bos);            return bos.toByteArray();        }    }    /**     * 结束行内容     *     * @param bos     */    private void endLine(ByteArrayOutputStream bos) {        String endLine = "--" + BOUNDARY + "--" + NEW_LINE;        try {            bos.write(endLine.getBytes(mCharSet));        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 格式化上传文件格式     *     * @param bos     * @param fileEntity     */    private void fileFormat(ByteArrayOutputStream bos, FileEntity fileEntity) {        StringBuilder stringBuilder = new StringBuilder();        stringBuilder.append("--").append(BOUNDARY).append(NEW_LINE);        stringBuilder.append("Content-Disposition: form-data; name=\"").append(fileEntity.mName).append("\"").append(";filename=\"").append(fileEntity.mFileName).append("\"").append(NEW_LINE);        stringBuilder.append("Content-Type: ").append(fileEntity.mMime).append(";charset=").append(mCharSet).append(NEW_LINE);        stringBuilder.append(NEW_LINE);        try {            bos.write(stringBuilder.toString().getBytes(mCharSet));            bos.write(fileEntity.getFileBytes());            bos.write(NEW_LINE.getBytes(mCharSet));        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 格式化上传参数格式     *     * @param bos     */    private void paramsFormat(ByteArrayOutputStream bos) {        StringBuilder stringBuilder = new StringBuilder();        for (String key : mParams.keySet()) {            Object value = mParams.get(key);            stringBuilder.append("--").append(BOUNDARY).append(NEW_LINE);            stringBuilder.append("Content-Disposition: form-data; name=\"").append(key).append("\"").append(NEW_LINE);            stringBuilder.append(NEW_LINE);            stringBuilder.append(value).append(NEW_LINE);        }        try {            bos.write(stringBuilder.toString().getBytes(mCharSet));        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 返回头信息,用于指定上传的内容类型     *     * @return     */    @Override    public String getBodyContentType() {        // 如果参数和文件都为null时,不会执行这个方法        // Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryS4nmHw9nb2Eeusll        return MULTIPART_FORM_DATA + ";boundary=" + BOUNDARY;    }}
自定义Request写完了,我们接下来测试一下效果,在这里我就使用自己写的一个简单的servlet来测试(只贴出上传单个文件的代码,所有的代码已上传到这里,点击下载):
File directory = Environment.getExternalStorageDirectory();// 参数Map<String, Object> params = new HashMap<>();params.put("name", "volley_single_file_name");params.put("value", "volley_single_file_value");FileEntity fileEntity = new FileEntity();fileEntity.mName = "filename";fileEntity.mFileName = "other.png";fileEntity.mFile = new File(directory, "Movies/other.png");RequestQueue requestQueue = Volley.newRequestQueue(this);String url = "http://192.168.0.100:8080/UpLoadFile/upload";MultipartRequest multipartRequest = new MultipartRequest(url, params, fileEntity, new Response.Listener<String>() {@Overridepublic void onResponse(String response) {Log.e("Succeed Result", response);}}, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError error) {Log.e("Error Result", error + "");}});requestQueue.add(multipartRequest);
运行结果查看(查看后台打印结果):

这样,我们就实现了使用Volley上传文件。同样我们可以进一步将代码封装,减少重复的代码。

点击下载全部封装代码。

下一篇《Volley框架(五):Volley源码分析》

0 0
原创粉丝点击