使用Volley上传文件
使用浏览器上传文件,然后通过Wireshark抓包分析,发现发送的数据大概是这个样子。
MIME Multipart Media Encapsulation, Type: multipart/form-data, Boundary: "----WebKitFormBoundary1UBMMKIkN58civN4" [Type: multipart/form-data] First boundary: ------WebKitFormBoundary1UBMMKIkN58civN4\r\n Encapsulated multipart part: Content-Disposition: form-data; name="name"\r\n\r\n Data (16 bytes) Boundary: \r\n------WebKitFormBoundary1UBMMKIkN58civN4\r\n Encapsulated multipart part: (image/png) Content-Disposition: form-data; name="photo[]"; filename="Screenshot (2).png"\r\n Content-Type: image/png\r\n\r\n Portable Network Graphics Boundary: \r\n------WebKitFormBoundary1UBMMKIkN58civN4\r\n Encapsulated multipart part: (image/png) Boundary: \r\n------WebKitFormBoundary1UBMMKIkN58civN4\r\n Encapsulated multipart part: (image/png) Boundary: \r\n------WebKitFormBoundary1UBMMKIkN58civN4\r\n Encapsulated multipart part: (image/png) Boundary: \r\n------WebKitFormBoundary1UBMMKIkN58civN4\r\n Encapsulated multipart part: (image/png) Last boundary: \r\n------WebKitFormBoundary1UBMMKIkN58civN4--\r\n
首先来自定义一个HttpEntity,
package cc.dewdrop.volleydemo.utils; import com.android.volley.VolleyLog; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.message.BasicHeader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.activation.MimetypesFileTypeMap; /** * Created by Tingkuo on 12/1/2015. */ public class FileUploadEntity implements HttpEntity { private static final String TAG = FileUploadEntity.class.getSimpleName(); private static final String BOUNDARY = "__FILE_UPLOAD_ENTITY__"; private ByteArrayOutputStream mOutputStream; public FileUploadEntity() { mOutputStream = new ByteArrayOutputStream(); try { writeFirstBoundary(); } catch (IOException e) { e.printStackTrace(); } } private void writeFirstBoundary() throws IOException { VolleyLog.e("writeFirstBoundary"); mOutputStream.write(("--" + BOUNDARY + "\r\n").getBytes()); mOutputStream.write(("Content-Disposition: form-data; name=\"" + "name" + "\"\r\n\r\n").getBytes()); mOutputStream.write("Content-Transfer-Encoding: binary\n\n".getBytes()); mOutputStream.flush(); } private void writeLastBoundary() throws IOException { VolleyLog.e("writeLastBoundary"); mOutputStream.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes()); } public void addFile(final String key, final File file) { VolleyLog.e("addFile"); InputStream inputStream = null; try { mOutputStream.write(("\r\n--" + BOUNDARY + "\r\n").getBytes()); StringBuilder stringBuilderContentDisposition = new StringBuilder(); stringBuilderContentDisposition.append("Content-Disposition: "); stringBuilderContentDisposition.append("form-data; "); stringBuilderContentDisposition.append("name=\"" + key + "\"; "); stringBuilderContentDisposition.append("filename=\"" + file.getName() + "\"\r\n"); mOutputStream.write(stringBuilderContentDisposition.toString().getBytes()); StringBuilder stringBuilderContentType = new StringBuilder(); stringBuilderContentType.append("Content-Type: "); stringBuilderContentType.append(new MimetypesFileTypeMap().getContentType(file).toString()); stringBuilderContentType.append("\r\n\r\n"); mOutputStream.write(stringBuilderContentType.toString().getBytes()); inputStream = new FileInputStream(file); final byte[] buffer = new byte[1024]; int len = 0; while ((len = inputStream.read(buffer)) != -1) { VolleyLog.e("len --> %s", String.valueOf(len)); mOutputStream.write(buffer, 0, len); } VolleyLog.e("===last====len --> %s", String.valueOf(len)); mOutputStream.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { closeSilently(inputStream); } } private void closeSilently(Closeable closeable) { try { if (closeable != null) { closeable.close(); } } catch (final IOException e) { e.printStackTrace(); } } @Override public boolean isRepeatable() { return false; } @Override public boolean isChunked() { return false; } @Override public long getContentLength() { return mOutputStream.toByteArray().length; } @Override public Header getContentType() { return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); } @Override public Header getContentEncoding() { return null; } @Override public InputStream getContent() throws IOException, UnsupportedOperationException { return new ByteArrayInputStream(mOutputStream.toByteArray()); } @Override public void writeTo(OutputStream outputStream) throws IOException { writeLastBoundary(); outputStream.write(mOutputStream.toByteArray()); } @Override public boolean isStreaming() { return false; } @Override public void consumeContent() throws IOException { } }
现在来解释一下,首先这是支持多文件上传的,数据格式一共包括四部分,Content-Type,First boundary,文件二进制数据[],及Last boundary。可以有多个文件,使用addFile方法插入,文件之间需要有分隔符Boundary。每个文件需要有Content-Disposition及Content-Type
然后再自定义一个Request,根据需要使用不同的构造方法
package cc.dewdrop.volleydemo.utils; import com.android.volley.AuthFailureError; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.Response.Listener; import com.android.volley.Response.ErrorListener; import com.android.volley.toolbox.HttpHeaderParser; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; /** * Created by Tingkuo on 12/2/2015. */ public class FileUploadRequest extends Request<String> { private final Listener<String> mListener; private FileUploadEntity mFileUploadEntity = new FileUploadEntity(); private Map<String, String> mHeaders = new HashMap<>(); public FileUploadRequest(String url, Listener<String> listener) { this(url, listener, null); } public FileUploadRequest(String url, Listener<String> listener, ErrorListener errorListener) { this(Method.POST, url, listener, errorListener); } public FileUploadRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) { super(method, url, errorListener); this.mListener = listener; } public FileUploadEntity getFileUploadEntity() { return mFileUploadEntity; } @Override public String getBodyContentType() { return mFileUploadEntity.getContentType().getValue(); } public void addHeader(String key, String value) { mHeaders.put(key, value); } @Override public Map<String, String> getHeaders() throws AuthFailureError { return mHeaders; } @Override public byte[] getBody() throws AuthFailureError { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { mFileUploadEntity.writeTo(outputStream); } catch (IOException e) { e.printStackTrace(); } return outputStream.toByteArray(); } @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { String parsed = ""; try { parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { parsed = new String(response.data); } return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); } @Override protected void deliverResponse(String response) { if (mListener != null) { mListener.onResponse(response); } } }
代码是放在Volley中其他类型Request来写的,没什么好说的。
最后就是如何调用
private void simpleUploadFile() { File file = new File(Environment.getExternalStorageDirectory().getPath() + "/upload.png"); fileUploadRequest = new FileUploadRequest( Request.Method.POST, urlList.get(2), new Response.Listener<String>() { @Override public void onResponse(String response) { textViewInfo.setText("Post Succeed:\n" + response.replace("<br>", "\n")); Log.e(TAG, response); dialog.dismiss(); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { textViewInfo.setText("Post Failed:\n" + error.getMessage()); Log.e(TAG, error.getMessage()); dialog.dismiss(); } } ); fileUploadRequest.addHeader("User-Agent", "Android 5.1.1"); FileUploadEntity fileUploadEntity = fileUploadRequest.getFileUploadEntity(); fileUploadEntity.addFile("file[]", file); fileUploadEntity.addFile("file[]", file); fileUploadEntity.addFile("file[]", file); fileUploadEntity.addFile("file[]", file); fileUploadEntity.addFile("file[]", file); requestQueue.add(fileUploadRequest); dialog.show(); }
实例化一个新的Request对象,传入Method,Url,然后通过Request对象来获取Entity,通过addFile()方法来传入需要上传的文件,最后加入requestQueue,使用方法与其他类型Request相同。
备注:
需要添加以下依赖:
compile 'org.apache.httpcomponents:httpcore:4.4.4' compile 'org.apache.httpcomponents:httpmime:4.5.1' compile files('libs/volley.jar') compile files('libs/activation.jar')