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

    xiaoxiao2021-03-26  18

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

     

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

    POST http://www.aaa.com/?ashx/login.ashx HTTP/1.1 Host: www.aaa.com User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Connection: keep-alive Upgrade-Insecure-Requests: 1 Content-Type: multipart/form-data; boundary=---------------------------29518470621122 Content-Length: 6462 -----------------------------29518470621122 Content-Disposition: form-data; name="login_username" abcde -----------------------------29518470621122 Content-Disposition: form-data; name="login_pswd" 12345 -----------------------------29518470621122 Content-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=----WebKitFormBoundarykR96Kta4gvMACHfq return 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>() { @Override public void onResponse(String response) { Log.e("Succeed Result", response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("Error Result", error + ""); } }); requestQueue.add(multipartRequest);

    运行结果查看(查看后台打印结果):

     

     

     

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

     

    下载、GitHub下载​​​​​​​

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

    转载请注明原文地址: https://ju.6miu.com/read-582598.html

    最新回复(0)