前言
前面写过一篇使用Volley下载大文件的文章,是直接在子线程中执行HurlStack的executeRequest(Request<?> request, Map<String, String> additionalHeaders)方法获取HttpResponse,再操作HttpResponse的InputStream。这种方式不能够使用RequestQueue调用Request,不够优雅。本篇实现RequestQueue调用方式。
分析
我们还是想办法拿到网络响应最原始的Inputstream,我们要想办法把HurlStack.java类方法executeRequest(Request<?> request, Map<String, String> additionalHeaders)返回HttpResponse的Inputstream回调给Request,这样我们就可以在Request里处理Inputstream了。
1、我们复制一份HurlStack代码重命名为CustomHurlStack.java。我们在executeRequest(Request<?> request, Map<String, String> additionalHeaders)方法返回的时候把HttpResponse回调给Request。
HurlStack.java:

image.png
重写后
CustomHurlStack.java:

image.png
IDownload.java
public interface IDownload {
void read(HttpResponse response);
}
2、封装Request:DownloadFileRequest.java
public class DownloadFileRequest extends Request<Void> implements IDownload {
private File file = null;
private String mETag = "";
private OnDownloadListener downloadListener = null;
public DownloadFileRequest(String url, @NonNull File dst) {
this(url, dst, "0123456789");
}
public DownloadFileRequest(String url, @NonNull File dst, String eTag) {
super(Method.GET, url, null);
if (dst.isDirectory()) {
int index = url.lastIndexOf("/");
if (index >= 0) {
String fileName = url.substring(index + 1);
file = new File(dst, fileName);
}
} else {
file = dst;
}
mETag = eTag;
}
public DownloadFileRequest listenDownload(OnDownloadListener downloadListener) {
this.downloadListener = downloadListener;
return this;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
long range = file != null && file.exists() ? file.length() : 0L;
Map<String, String> headers = new HashMap<>();
headers.put("Range", String.format(Locale.CHINA, "bytes=%d-", range));
headers.put("If-Range", mETag);
return headers;
}
@Override
protected Response<Void> parseNetworkResponse(NetworkResponse response) {
return Response.success(null, HttpHeaderParser.parseCacheHeaders(response));
}
@Override
protected void deliverResponse(Void response) {
}
@Override
public void read(HttpResponse response) {
downloadListener.onStart();
try {
int statusCode = response.getStatusCode();
if (statusCode < 200 || statusCode > 299) {
downloadListener.onError(statusCode, "下载出错。");
downloadListener.onEnd();
return;
}
File dir = file.getParentFile();
if (dir != null) {
boolean mkdirs = dir.mkdirs();
}
if (statusCode != 206) {
boolean delete = file.delete();
}
if (!file.exists()) {
boolean newFile = file.createNewFile();
}
long range = file.length();
RandomAccessFile raf = new RandomAccessFile(file, "rw");
raf.seek(range);
FileChannel fc = raf.getChannel();
ReadableByteChannel rbc = Channels.newChannel(response.getContent());
//通道没有办法传输数据,必须依赖缓冲区
//分配指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
long total = range + response.getContentLength();
long progress = range;
int count = 0;
int len = 0;
while (!isCanceled() && (len = rbc.read(byteBuffer)) != -1) {
//切换成读数据模式
byteBuffer.flip();
//将缓冲区中的数据写入通道
fc.write(byteBuffer);
//分发下载进度
progress += len;
count++;
if (count >= 25) {
downloadListener.onProgress(progress, total);
count = 0;
}
//清空缓冲区
byteBuffer.clear();
}
if (count > 0) {
downloadListener.onProgress(progress, total);
}
fc.close();
rbc.close();
raf.close();
if (isCanceled()) {
downloadListener.onCancelled("已取消下载。");
} else if (progress != total) {
downloadListener.onError(-1, "下载出错。");
} else {
downloadListener.onSuccess(file.getPath());
}
downloadListener.onEnd();
} catch (IOException e) {
if (isCanceled()) {
downloadListener.onCancelled("已取消下载。");
} else {
downloadListener.onError(-1, e.getLocalizedMessage());
}
}
}
}
3、下载监听器。
public interface OnDownloadListener {
void onStart();
void onCancelled(String message);
void onProgress(long progress, long total);
void onError(int code, String message);
void onSuccess(String filePath);
void onEnd();
}
4、简单使用示例。
private void download() {
RequestQueue mRequestQueue = Volley.newRequestQueue(this, new CustomHurlStack());
RetryPolicy policy = new DefaultRetryPolicy(10_000, 1, 1);
File dir = getExternalFilesDir("download");
if (dir == null) {
return;
}
if (dir.exists()) {
boolean mkdirs = dir.mkdirs();
}
DownloadFileRequest request = new DownloadFileRequest(fileUrl, dir)
.listenDownload(new OnDownloadListener() {
@Override
public void onStart() {
runOnUiThread(new Runnable() {
@Override
public void run() {
showProgressDialog();
}
});
}
@Override
public void onCancelled(String message) {
Log.d(TAG, "onCancelled: " + message);
runOnUiThread(new Runnable() {
@Override
public void run() {
ImitateToast.show(message);
}
});
}
@Override
public void onProgress(long progress, long total) {
Log.d(TAG, String.format(Locale.CHINA, "onProgress: %d/%d", progress, total));
runOnUiThread(new Runnable() {
@Override
public void run() {
if (progressDialog != null) {
progressDialog.updateProgress(progress, total);
}
}
});
}
@Override
public void onError(int code, String message) {
Log.d(TAG, String.format(Locale.CHINA, "onError: %s(%d)", message, code));
runOnUiThread(new Runnable() {
@Override
public void run() {
ImitateToast.show(message);
}
});
}
@Override
public void onSuccess(String filePath) {
Log.d(TAG, "onSuccess: " + filePath);
runOnUiThread(new Runnable() {
@Override
public void run() {
ImitateToast.show("下载成功:" + filePath);
}
});
}
@Override
public void onEnd() {
runOnUiThread(new Runnable() {
@Override
public void run() {
hideProgressDialog();
}
});
}
});
request.setTag("download");
request.setRetryPolicy(policy);
request.setShouldCache(false);
mRequestQueue.add(request);
}