您的位置:首页 > 产品设计 > UI/UE

Volley源码分析之自定义MultiPartRequest(文件上传)

2016-11-12 22:21 330 查看
本篇内容目录:

使用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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: