Volley源码分析之自定义MultiPartRequest(文件上传)
2016-11-12 22:21
330 查看
本篇内容目录:
使用HttpURLConnection上传文件到服务器案例
自定义支持文件上传的MultiPartRequest
Web后台接收文件的部分代码
2.上传文件的HTTP请求中的Content-Type:
在HTML中文件上传:
从上面可知,Content-Type的格式为multipart/form-data。无论是网页还是android app都是客户端,使用HttpURLConnection上传文件都是一致的。故,这里的Content-Type应该设置为:
3.来了解下multipart/form-data格式的数据:
这里,案例:一个名为file1的text文件和一个名为file2的gif文件,同时带有一段字符串(”Joe Blow”)共同在HTML中上传 。而在Http请求中显示的数据:
从以上资料可知:multipart/form-data由多个部分组成,每一部分都有一个content-disposition标题头,它的值是”form-data”,它的属性指明了其在表单内的字段名。
举例来说,’content-disposition: form-data; name=”xxxxx”’,这里的xxxxx就是对应于该字段的字段名。
对所有的多部分MIME类型来说,每一部分有一个可选的Content-Type,默认的值是text/plain。如果知道是什么类型的话,就定义为相应的媒体类型。否则的话,就标识为application/octet-stream。
文件名可以由标题头”content-disposition”中的filename参数所指定。
总之,multipart/form-data的媒体内容遵从RFC 1521所规定的多部分的数据流规则。
以上关于multipart/form-data的资料来源于RFC文档目录, HTML中基于表单的文件上传
4.设置multipart/form-data格式的数据:
5.支持文件上传的HttpURLConnection的完整代码如下:
在不修改HurlStack 源码的前提下,将file文件数据转成byte[],传入MultiPartRequest中。完整代码如下:
Web后台接口部分接收文件上传的代码:将文件写入F盘中,然后返回文件路径给客户端。
在Activity中使用图片上传功能:一些Volley的配置,阅读 Volley源码分析之自定义GsonRequest(带header,coockie,Json参数,Gson解析)
项目运行结果:
1.先进行拍照操作,效果图如下:
2.然后点击文件上传,web后台接收到文件,将文件写入F盘中文件夹里。
3.app上获取到服务器返回文件路径,效果图如下:
项目代码:http://download.csdn.net/detail/hexingen/9681762
相关知识点阅读:
Volley源码分析之自定义GsonRequest(带header,coockie,Json参数,Gson解析)
java网络编程之HttpURLConnection
使用HttpURLConnection上传文件到服务器案例
自定义支持文件上传的MultiPartRequest
Web后台接收文件的部分代码
先来看下HttpURLConnection来文件上传的案例:
1.传送数据到服务器,必定是使用POST请求://设置请求方式为post httpURLConnection.setDoOutput(true); httpURLConnection.setRequestMethod("POST");
2.上传文件的HTTP请求中的Content-Type:
在HTML中文件上传:
<form method="POST" enctype="multipart/form-data" action="fup.cgi"> File to upload: <input type="file" name="upfile"><br/> Notes about the file: <input type="text" name="note"><br/> <br/> <input type="submit" value="Press"> to upload the file! </form>
从上面可知,Content-Type的格式为multipart/form-data。无论是网页还是android app都是客户端,使用HttpURLConnection上传文件都是一致的。故,这里的Content-Type应该设置为:
multipart/form-data
3.来了解下multipart/form-data格式的数据:
这里,案例:一个名为file1的text文件和一个名为file2的gif文件,同时带有一段字符串(”Joe Blow”)共同在HTML中上传 。而在Http请求中显示的数据:
Content-type: multipart/form-data, boundary=AaB03x //普通数据 --AaB03x content-disposition: form-data; name="field1" Joe Blow //多个文件数据的格式 --AaB03x content-disposition: form-data; name="pics" Content-type: multipart/mixed, boundary=BbC04y //file1.text的数据 --BbC04y Content-disposition: attachment; filename="file1.txt" Content-Type: text/plain ... file1.txt 的内容... //file2.gif的数据结构 --BbC04y Content-disposition: attachment; filename="file2.gif" Content-type: image/gif Content-Transfer-Encoding: binary ... file2.gif的内容... --BbC04y-- --AaB03x--
从以上资料可知:multipart/form-data由多个部分组成,每一部分都有一个content-disposition标题头,它的值是”form-data”,它的属性指明了其在表单内的字段名。
举例来说,’content-disposition: form-data; name=”xxxxx”’,这里的xxxxx就是对应于该字段的字段名。
对所有的多部分MIME类型来说,每一部分有一个可选的Content-Type,默认的值是text/plain。如果知道是什么类型的话,就定义为相应的媒体类型。否则的话,就标识为application/octet-stream。
文件名可以由标题头”content-disposition”中的filename参数所指定。
总之,multipart/form-data的媒体内容遵从RFC 1521所规定的多部分的数据流规则。
以上关于multipart/form-data的资料来源于RFC文档目录, HTML中基于表单的文件上传
4.设置multipart/form-data格式的数据:
private static final String BOUNDARY = "----------" + System.currentTimeMillis(); /** * 请求的内容类型 */ private static final String PROTOCOL_CONTENT_TYPE = "multipart/form-data; boundary=" + BOUNDARY; /** * 多个文件间的间隔 */ private static final String FILEINTERVAL = "\r\n"; /** * 获取到文件的head * * @return */ public byte[] getFileHead(String fileName) { try { StringBuffer buffer = new StringBuffer(); buffer.append("--"); buffer.append(BOUNDARY); buffer.append("\r\n"); buffer.append("Content-Disposition: form-data;name=\"media\";filename=\""); buffer.append(fileName); buffer.append("\"\r\n"); buffer.append("Content-Type:application/octet-stream\r\n\r\n"); String s = buffer.toString(); return s.getBytes("utf-8"); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取文件的foot * * @return */ public byte[] getFileFoot() { try { StringBuffer buffer = new StringBuffer(); buffer.append("\r\n--"); buffer.append(BOUNDARY); buffer.append("--\r\n"); String s = buffer.toString(); return s.getBytes("utf-8"); } catch (Exception e) { return null; } }
5.支持文件上传的HttpURLConnection的完整代码如下:
/** * Created by ${新根} on 2016/11/6. * 博客:http://blog.csdn.net/hexingen * <p/> * 用途: * 使用httpUrlConnection上传文件到服务器 */ public class HttpUrlConnectionOpts { /** * 字符编码格式 */ private static final String PROTOCOL_CHARSET = "utf-8"; private static final String BOUNDARY = "----------" + System.currentTimeMillis(); /** * 请求的内容类型 */ private static final String PROTOCOL_CONTENT_TYPE = "multipart/form-data; boundary=" + BOUNDARY; /** * 多个文件间的间隔 */ private static final String FILEINTERVAL = "\r\n"; public HttpUrlConnectionOpts() { } public void fileUpLoad(String url, Map<String, File> files) { HttpURLConnection connection = createMultiPartConnection(url); addIfParameter(connection, files); String responeContent = getResponeFromService(connection); } /** * 获取从服务器相应的数据 * * @param connection * @return */ public String getResponeFromService(HttpURLConnection connection) { String responeContent = null; BufferedReader bufferedReader = null; try { if (connection != null) { connection.connect(); int responeCode = connection.getResponseCode(); if (responeCode == 200) { bufferedReader = new BufferedReader( new InputStreamReader(connection.getInputStream())); String line; StringBuffer stringBuffer = new StringBuffer(); while ((line = bufferedReader.readLine()) != null) { stringBuffer.append(line); } responeContent = stringBuffer.toString(); } } } catch (Exception e) { e.printStackTrace(); responeContent = null; } finally { try { if (bufferedReader != null) { bufferedReader.close(); } if (connection != null) { connection.disconnect(); } } catch (Exception e) { e.printStackTrace(); } } return responeContent; } /** * 若是文件列表不为空,则将文件列表上传。 * * @param connection * @param files */ public void addIfParameter(HttpURLConnection connection, Map<String, File> files) { if (files != null && connection != null) { DataOutputStream dataOutputStream = null; try { dataOutputStream = new DataOutputStream(connection.getOutputStream()); int i = 1; Set<Map.Entry<String, File>> set = files.entrySet(); for (Map.Entry<String, File> fileEntry : set) { byte[] contentHeader = getFileHead(fileEntry.getKey()); //添加文件的头部格式 dataOutputStream.write(contentHeader, 0, contentHeader.length); //添加文件数据 readFileData(fileEntry.getValue(), dataOutputStream); //添加文件间的间隔,若是一个文件则不用添加间隔。若是多个文件时,最后一个文件不用添加间隔。 if (set.size() > 1 && i < set.size()) { i++; dataOutputStream.write(FILEINTERVAL.getBytes(PROTOCOL_CHARSET)); } } //写入文件的尾部格式 byte[] contentFoot = getFileFoot(); dataOutputStream.write(contentFoot, 0, contentFoot.length); //刷新数据到流中 dataOutputStream.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (dataOutputStream != null) { dataOutputStream.close(); } } catch (Exception e) { e.printStackTrace(); } } } } /** * 将file数据写入流中 * * @param file * @param outputStream */ public void readFileData(File file, OutputStream outputStream) { FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(file); byte[] bytes = new byte[1024]; int length; while ((length = fileInputStream.read(bytes)) > 0) { outputStream.write(bytes, 0, length); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (fileInputStream != null) { fileInputStream.close(); } } catch (Exception e) { e.printStackTrace(); } } } /** * 创建和设置HttpUrlConnection的内容格式为文件上传格式 * * @param url * @return */ public HttpURLConnection createMultiPartConnection(String url) { HttpURLConnection httpURLConnection = null; try { httpURLConnection = (HttpURLConnection) new URL(url).openConnection(); //设置请求方式为post httpURLConnection.setDoOutput(true); httpURLConnection.setRequestMethod("POST"); //设置不使用缓存 httpURLConnection.setUseCaches(false); //设置数据字符编码格式 httpURLConnection.setRequestProperty("Charsert", PROTOCOL_CHARSET); //设置内容上传类型(multipart/form-data),这步是关键 httpURLConnection.setRequestProperty("Content-Type", PROTOCOL_CONTENT_TYPE); } catch (Exception e) { e.printStackTrace(); } return httpURLConnection; } /** * 获取到文件的head * * @return */ public byte[] getFileHead(String fileName) { try { StringBuffer buffer = new StringBuffer(); buffer.append("--"); buffer.append(BOUNDARY); buffer.append("\r\n"); buffer.append("Content-Disposition: form-data;name=\"media\";filename=\""); buffer.append(fileName); buffer.append("\"\r\n"); buffer.append("Content-Type:application/octet-stream\r\n\r\n"); String s = buffer.toString(); return s.getBytes("utf-8"); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取文件的foot * * @return */ public byte[] getFileFoot() { try { StringBuffer buffer = new StringBuffer(); buffer.append("\r\n--"); buffer.append(BOUNDARY); buffer.append("--\r\n"); String s = buffer.toString(); return s.getBytes("utf-8"); } catch (Exception e) { return null; } } }
自定义支持文件上传的MultiPartRequest
这里不再详细讲述怎么自定义Request,如何设置Header,Body等,可以阅读 Volley源码分析之自定义GsonRequest(带header,coockie,Json参数,Gson解析)在不修改HurlStack 源码的前提下,将file文件数据转成byte[],传入MultiPartRequest中。完整代码如下:
/** * Created by 新根 on 2016/8/9. * 用途: * 各种数据上传到服务器的内容格式: * <p/> * 文件上传(内容格式):multipart/form-data * String字符串传送(内容格式):application/x-www-form-urlencoded * json传递(内容格式):application/json */ public class MultiPartRequest<T> extends Request<T> { private static final String TAG=MultiPartRequest.class.getSimpleName(); /** * 解析后的实体类 */ private final Class<T> clazz; private final Response.Listener<T> listener; /** * 自定义header: */ private Map<String, String> headers; private final Gson gson = new Gson(); /** * 字符编码格式 */ private static final String PROTOCOL_CHARSET = "utf-8"; private static final String BOUNDARY = "----------" + System.currentTimeMillis(); /** * Content type for request. */ private static final String PROTOCOL_CONTENT_TYPE = "multipart/form-data; boundary=" + BOUNDARY; /** * 文件列表。参数1是文件名,参数2是文件编码成的byte[] */ private Map<String, byte[]> fileList; /** * 多个文件间的间隔 */ private static final String FILEINTERVAL = "\r\n"; public MultiPartRequest(int method, String url, Class<T> clazz, Response.Listener<T> listener, Response.ErrorListener errorListenerr) { super(method, url, errorListenerr); this.clazz = clazz; this.listener = listener; headers = new HashMap<>(); fileList = new HashMap<String, byte[]>(); } @Override protected Response<T> parseNetworkResponse(NetworkResponse response) { try { String json = new String( response.data, HttpHeaderParser.parseCharset(response.headers)); T t = gson.fromJson(json, clazz); return Response.success(t, HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } catch (JsonSyntaxException e) { return Response.error(new ParseError(e)); } } @Override protected void deliverResponse(T t) { listener.onResponse(t); } /** * 重写getHeaders(),添加自定义的header * * @return * @throws AuthFailureError */ @Override public Map<String, String> getHeaders() throws AuthFailureError { return headers; } public Map<String, String> setHeader(String key, String content) { if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(content)) { headers.put(key, content); } return headers; } /** * 默认添加图片数据 * * @param file */ public void addFile(byte[] file) { if (file != null) { addFile(getFileName(), file); } } /** * 添加文件名和文件数据 * * @param fileName * @param file */ public void addFile(String fileName, byte[] file) { if (!TextUtils.isEmpty(fileName) && file != null) { Log.i(TAG,fileName+" fileName"); fileList.put(fileName, file); } } /** * 重写Content-Type:设置为json */ @Override public String getBodyContentType() { return PROTOCOL_CONTENT_TYPE; } /** * post参数类型 */ @Override public String getPostBodyContentType() { return getBodyContentType(); } /** * post参数 */ @Override public byte[] getPostBody() throws AuthFailureError { return getBody(); } /** * 将string编码成byte * * @return * @throws AuthFailureError */ @Override public byte[] getBody() throws AuthFailureError { byte[] body; ByteArrayOutputStream outputStream = null; try { outputStream = new ByteArrayOutputStream(); Set<Map.Entry<String, byte[]>> set = fileList.entrySet(); Log.i(TAG,set.size()+"filesize"); int i=1; for (Map.Entry entry : set) { //添加文件的头部格式 writeByte(outputStream, getFileHead((String) entry.getKey())); //添加文件数据 writeByte(outputStream, (byte[]) entry.getValue()); //添加文件间的间隔 if (set.size() > 1&&i<set.size()) { i++; Log.i(TAG,"添加文件间隔"); writeByte(outputStream, FILEINTERVAL.getBytes(PROTOCOL_CHARSET)); } } writeByte(outputStream, getFileFoot()); outputStream.flush(); body = outputStream.toByteArray(); return body == null ? null : body; } catch (Exception e) { return null; } finally { try { if (outputStream != null) { outputStream.close(); } } catch (Exception e) { } } } public void writeByte(ByteArrayOutputStream outputStream, byte[] bytes) { Log.i(TAG,bytes.length+"byte长度"); outputStream.write(bytes, 0, bytes.length); } /** * 以当前时间为文件名, * 文件后缀".png" * * @return */ private int fileEnd=1; public String getFileName() { ++fileEnd; StringBuilder stringBuilder=new StringBuilder(); stringBuilder.append(new Date().getTime()); stringBuilder.append(fileEnd); stringBuilder.append(".png"); return stringBuilder.toString(); } /** * 获取到文件的head * * @return */ public byte[] getFileHead(String fileName) { try { StringBuffer buffer = new StringBuffer(); buffer.append("--"); buffer.append(BOUNDARY); buffer.append("\r\n"); buffer.append("Content-Disposition: form-data;name=\"media\";filename=\""); buffer.append(fileName); buffer.append("\"\r\n"); buffer.append("Content-Type:application/octet-stream\r\n\r\n"); String s = buffer.toString(); return s.getBytes("utf-8"); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取文件的foot * * @return */ public byte[] getFileFoot() { try { StringBuffer buffer = new StringBuffer(); buffer.append("\r\n--"); buffer.append(BOUNDARY); buffer.append("--\r\n"); String s = buffer.toString(); return s.getBytes("utf-8"); } catch (Exception e) { return null; } } }
Web后台接口部分接收文件上传的代码:将文件写入F盘中,然后返回文件路径给客户端。
/** * Commons FileUpload 方式:当个或者多个文件上传 * @param request * @param response */ @RequestMapping(value="/fileUpload",method=RequestMethod.POST) public void fileUpLoad(HttpServletRequest request,HttpServletResponse response){ // Check that we have a file upload request boolean isFile=ServletFileUpload.isMultipartContent(request); JSONObject jsonObject=new JSONObject(); if(isFile){ String result= writeFile(request); jsonObject.put("path", result); }else{ jsonObject.put("path", "这不是文件"); } setResponse(response, jsonObject); } public static final String CACHEFILE="F:"+File.separator +"WebProject"+File.separator+"fileUploadBitmap"; public String writeFile(HttpServletRequest request){ DiskFileItemFactory diskFileItemFactory=new DiskFileItemFactory(); //设置储存器的最大值(内存缓存值) diskFileItemFactory.setSizeThreshold(1000*1024); //设置超出部分的存储位置(临时存储位置) diskFileItemFactory.setRepository(new File("E:/")); // Create a new file upload handler ServletFileUpload upload=new ServletFileUpload(diskFileItemFactory); upload.setSizeMax(1000*1024); String result=null; try { List<FileItem> items= upload.parseRequest(request); System.out.println(items.size()+"个文件"); for (FileItem fileItem: items) { System.out.println(fileItem.getName()); if(!fileItem.isFormField()){ String fileName=fileItem.getName(); File file=new File(CACHEFILE); if(file!=null&&!file.exists()){ file.mkdir(); } //将数据写入文件 if(fileName.lastIndexOf("\\")>=0){ file=new File(file.getAbsoluteFile()+File.separator +fileName.substring(fileName.lastIndexOf("\\"))); }else{ file=new File(file.getAbsoluteFile()+File.separator +fileName.substring(fileName.lastIndexOf("\\")+1)); } //FileUpload 提供两种方式:一种是直接将内容写入文件中,一种是将内容写入IO流中 fileItem.write(file); result +=file.getAbsolutePath(); }else{ result= "这不是文件,是一个表单"; } } } catch (Exception e) { e.printStackTrace(); result= "发生异常"; } return result; } /** * 设置客户端返回值 * @param response * @param jsonObject */ public void setResponse(HttpServletResponse response,JSONObject jsonObject){ try { response.setCharacterEncoding("utf-8"); response.setContentType("charset=UTF-8"); String result=jsonObject.toString(); response.getWriter().write(result, 0, result.length());; } catch (Exception e) { e.printStackTrace(); } }
在Activity中使用图片上传功能:一些Volley的配置,阅读 Volley源码分析之自定义GsonRequest(带header,coockie,Json参数,Gson解析)
/** * 将bitmap编码成byte[],然后上传到服务器 * * @param bitmap */ public void sendFileUploadRequest(byte[] bitmap) { System.out.print("开始上传"); MultiPartRequest<JsonBean> request = new MultiPartRequest<>(Request.Method.POST, "http://192.168.1.101:8080/SSMProject/file/fileUpload", JsonBean.class , new Response.Listener<JsonBean>() { @Override public void onResponse(JsonBean jsonBean) { path_tv.setText("bitmap存储在:"+jsonBean.path); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Toast.makeText(MainActivity.this, volleyError.getMessage(), Toast.LENGTH_LONG).show(); } }); request.addFile(bitmap); request.setRetryPolicy(new DefaultRetryPolicy(50000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); request.setTag(TAG); VolleySingleton.getInstance().addToRequestQueue(request); }
项目运行结果:
1.先进行拍照操作,效果图如下:
2.然后点击文件上传,web后台接收到文件,将文件写入F盘中文件夹里。
3.app上获取到服务器返回文件路径,效果图如下:
项目代码:http://download.csdn.net/detail/hexingen/9681762
相关知识点阅读:
Volley源码分析之自定义GsonRequest(带header,coockie,Json参数,Gson解析)
java网络编程之HttpURLConnection
相关文章推荐
- ASP.NET上传视频文件同时转换为flv并且抓取第一帧生面图片源码分析
- hadoop 文件上传源码分析-------学习
- Hadoop之HDFS原理及文件上传下载源码分析(下)
- SpringMVC源码分析--文件上传
- FCKEditor ConnectorSerlvet源码 自定义上传文件修改
- PHP文件上传源码分析(RFC1867)
- SpringMVC源码分析--文件上传
- Hadoop之HDFS原理及文件上传下载源码分析(下)
- PHP文件上传源码分析(RFC1867)
- Dubbo源码分析(三):自定义Schema--基于Spring可扩展Schema提供自定义配置支持(spring配置文件中 配置标签支持)
- Android中使用Volley上传文件的源码
- SpringMVC 中文件上传 MultipartResolver两种使用方式及简单源码分析
- PHP 文件上传源码分析(RFC1867)
- Android 自定义View及其在布局文件中的使用示例(三):结合Android 4.4.2_r1源码分析onMeasure过程
- [volley文件上传]Android中自定义MultipartEntity实现文件上传以及使用Volley库实现文件上传
- SpringMVC源码分析--文件上传
- 分布式文件系统 fastdfs 源码分析 之 文件上传流程分析
- SpringMVC源码分析--文件上传
- hdfs上传文件的源码分析
- Android网络通信框架Volley——自定义Request(Get、Post、文件上传)