由于Volley在解析期间将所有数据保存在内存中,不适合文件下载或流式操作,因此Volley本身并没有提供上传及下载的Request,大文件上传下载是完全不适合使用的,但是小文件还是可以勉强使用的。
对于HttpComponents一定不会陌生,使用过HttpClient就一定知道,使用HttpComponents的原因只是为了简化操作,将复杂的流操作都由其进行处理。
首先需要添加HttpComponents相关依赖
1 2 3 4
| implementation 'org.apache.httpcomponents:httpcore:4.4.11' implementation('org.apache.httpcomponents:httpmime:4.3.1') { exclude module: 'httpclient' }
|
添加依赖的时候要注意的是需要排除’httpclient’,否则容易出现类冲突
另外还需要添加打包配置
1 2 3 4 5 6 7
| packagingOptions { exclude 'META-INF/DEPENDENCIES' exclude 'META-INF/NOTICE' exclude 'META-INF/LICENSE' exclude 'META-INF/LICENSE.txt' exclude 'META-INF/NOTICE.txt' }
|
自定义Request
首先需要自定义一个Request,一般返回结果都为JSON或者String,所以为了偷懒,直接继承自StringRequest。
构建两个构造函数用于单文件上传和多文件上传,上传时需要传入文件以及监听文件上传时的进度
构造函数
1 2 3
| public MultipartRequest(String url, File file, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener, MultipartProgressListener mProgressListener, String... key) {}
public MultipartRequest(String url, List<File> mFiles, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener, MultipartProgressListener mProgressListener, String... keys) {}
|
key为上传文件所对应的参数名称,如果传入参数为空,则以文件名作为该文件的参数名
进度回调
1 2 3
| public interface MultipartProgressListener { void transferred(long transferred, int progress); }
|
文件写入
文件写入通过Volley的getBody()进行,将文件及参数转化为byte[]进行传输。
进行文件写入时就需要使用HttpComponents,通过MultipartEntityBuilder将文件转换为流,然后通过HttpEntity转换成Byte[] 。
将文件写入HttpEntity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| private void buildMultipartEntity() { multipartEntityBuilder = MultipartEntityBuilder.create(); multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); multipartEntityBuilder.setCharset(Charset.forName("UTF-8")); if (mFiles != null && mFiles.size() > 0) { if (keys.length != mFiles.size() && keys.length != 0) { mErrorListener.onErrorResponse(new VolleyError("The number of keys differs from the number of files")); this.cancel(); return; } for (int i = 0; i < mFiles.size(); i++) { File file = mFiles.get(i); mFileLength += file.length(); if (keys.length == 0) { keys = new String[mFiles.size()]; keys[i] = file.getName().split("\\.")[0]; } multipartEntityBuilder.addPart(keys[i], new FileBody(file)); } httpEntity = multipartEntityBuilder.build(); } }
|
通过getBody()将文件转化为byte[]传入
1 2 3 4 5 6 7 8 9 10
| public byte[] getBody() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { httpEntity.writeTo(new CountingOutputStream(baos, mFileLength, mProgressListener)); } catch (IOException e) { e.printStackTrace(); mErrorListener.onErrorResponse(new VolleyError(e)); } return baos.toByteArray(); }
|
监听文件写入进度作为上传进度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class CountingOutputStream extends FilterOutputStream { private final MultipartProgressListener progressListener; private long transferred; private long fileLength;
public CountingOutputStream(final OutputStream out, long fileLength, final MultipartProgressListener listener) { super(out); this.fileLength = fileLength; this.progressListener = listener; this.transferred = 0; }
@Override public void write(byte[] b, int off, int len) throws IOException { out.write(b, off, len); if (progressListener != null) { this.transferred += len; int progress = (int) (transferred * 100 / fileLength); this.progressListener.transferred(this.transferred, progress); } }
@Override public void write(int b) throws IOException { out.write(b); if (progressListener != null) { this.transferred++; int progress = (int) (transferred * 100 / fileLength); this.progressListener.transferred(this.transferred, progress); } } }
|
完整示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| public class MultipartRequest extends StringRequest {
private List<File> mFiles; private Response.ErrorListener mErrorListener; private long mFileLength;
private MultipartEntityBuilder multipartEntityBuilder; private HttpEntity httpEntity; private MultipartProgressListener mProgressListener; private String[] keys;
public MultipartRequest(String url, File file, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener, MultipartProgressListener mProgressListener, String... keys) { super(Method.POST, url, listener, errorListener); this.mErrorListener = errorListener; this.mProgressListener = mProgressListener; this.keys = keys; mFileLength = 0; mFiles = new ArrayList<>(); if (file != null) { mFiles.add(file); } buildMultipartEntity(); }
public MultipartRequest(String url, List<File> mFiles, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener, MultipartProgressListener mProgressListener, String... keys) { super(Method.POST, url, listener, errorListener); this.mErrorListener = errorListener; this.mProgressListener = mProgressListener; this.mFiles = mFiles; this.keys = keys; mFileLength = 0; buildMultipartEntity(); }
private void buildMultipartEntity() { multipartEntityBuilder = MultipartEntityBuilder.create(); multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); multipartEntityBuilder.setCharset(Charset.forName("UTF-8"));
if (mFiles != null && mFiles.size() > 0) { if (keys.length != mFiles.size() && keys.length != 0) { mErrorListener.onErrorResponse(new VolleyError("The number of keys differs from the number of files")); this.cancel(); return; } for (int i = 0; i < mFiles.size(); i++) { File file = mFiles.get(i); mFileLength += file.length(); if (keys.length == 0) { keys = new String[mFiles.size()]; keys[i] = file.getName().split("\\.")[0]; } multipartEntityBuilder.addPart(keys[i], new FileBody(file)); } httpEntity = multipartEntityBuilder.build(); } }
@Override public String getBodyContentType() { return httpEntity.getContentType().getValue(); }
@Override public byte[] getBody() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { httpEntity.writeTo(new CountingOutputStream(baos, mFileLength, mProgressListener)); } catch (IOException e) { e.printStackTrace(); mErrorListener.onErrorResponse(new VolleyError(e)); } return baos.toByteArray(); }
class CountingOutputStream extends FilterOutputStream { private final MultipartProgressListener progressListener; private long transferred; private long fileLength;
public CountingOutputStream(final OutputStream out, long fileLength, final MultipartProgressListener listener) { super(out); this.fileLength = fileLength; this.progressListener = listener; this.transferred = 0; }
@Override public void write(byte[] b, int off, int len) throws IOException { out.write(b, off, len); if (progressListener != null) { this.transferred += len; int progress = (int) (transferred * 100 / fileLength); this.progressListener.transferred(this.transferred, progress); } }
@Override public void write(int b) throws IOException { out.write(b); if (progressListener != null) { this.transferred++; int progress = (int) (transferred * 100 / fileLength); this.progressListener.transferred(this.transferred, progress); } }
}
public interface MultipartProgressListener { void transferred(long transferred, int progress); } }
|
调用
创建回调监听和进度监听
1 2
| private Response.Listener<String> mResultListener = response -> {}; private MultipartRequest.MultipartProgressListener mProgressListener = (transferred, progress) -> {};
|
上传请求
1 2 3 4 5 6 7 8 9 10 11
| String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/1.jpg"; File file = new File(filePath); if (file.exists()) { List<File> files = new ArrayList<>(); files.add(file); files.add(file); MultipartRequest uploadRequest = new MultipartRequest(URL_UPLOAD, files, mResultListener, mErrorListener, mProgressListener, "file", "file"); mQueue.add(uploadRequest); } else { Toast.makeText(mContext, "file not found", Toast.LENGTH_SHORT).show(); }
|