Volley实现大文件下载(支持断点续传)进阶篇

前言

  前面写过一篇使用Volley下载大文件的文章,是直接在子线程中执行HurlStackexecuteRequest(Request<?> request, Map<String, String> additionalHeaders)方法获取HttpResponse,再操作HttpResponseInputStream。这种方式不能够使用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);
    }
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容