java 文件下载,支持任务暂停,恢复,断点续传;任务状态查询;任务并发控制

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java 文件下载,支持任务暂停,恢复,断点续传;任务状态查询;任务并发控制相关的知识,希望对你有一定的参考价值。

package everphoto.bean;

import android.content.Context;
import android.support.v4.util.ArrayMap;

import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;

import everphoto.App;
import solid.download.DownloadTask;
import solid.download.DownloadTaskListener;
import solid.util.PathUtils;

/**
 * 管理应用所有下载任务,提供以下功能
 * 1.任务状态查询
 * 2.任务并发控制
 * 3.任务暂停、恢复、断点续传
 */
public class FileDownloadMgr {
    private static final int MAX_CONCURRENT_DOWNLOAD_TASK_NUM = 5;

    private static Context context;
    private static FileDownloadMgr instance = new FileDownloadMgr();

    public static FileDownloadMgr getInstance(Context c) {
        if (c != null) {
            context = c;
        }
        return instance;
    }

    /**
     * 必须和taskList保持一致性
     */
    private Map<String, DownloadTask> tasks = new ArrayMap<>();
    /**
     * 任务队列
     */
    private LinkedList<String> taskList = new LinkedList<>();
    private int concurrentTaskNum = 0;

    private FileDownloadMgr() {
    }

    public boolean pasue(String url) {
        DownloadTask task = tasks.get(url);
        if (null == task) {
            return false;
        }

        task.onCancelled();
        finish(url);
        return true;
    }

    public DownloadTask getTask(String url) {
        return tasks.get(url);
    }

    public boolean putTask(final String url, final DownloadTaskListener listener) {
        return putTask(url, PathUtils.PATH_DOWNLOAD, null, listener, false);
    }

    public boolean putTask(final String url, String fileName, final DownloadTaskListener listener) {
        return putTask(url, PathUtils.PATH_DOWNLOAD, fileName, listener, false);
    }

    public boolean putTask(final String url, String fileName, final DownloadTaskListener listener, boolean force) {
        return putTask(url, PathUtils.PATH_DOWNLOAD, fileName, listener, force);
    }

    /**
     * 生成并执行一个下载任务,支持恢复一个被暂停或异常中断的下载任务。
     *
     * @param url      下载url
     * @param path     保存路径
     * @param fileName 保存文件的文件名;如果为空,则使用url.getFile()作为文件名
     * @param listener DownloadTaskListener
     * @param force    是否无视最大下载并发的限制,强制下载
     * @return 是否成功生成任务
     */
    public boolean putTask(final String url, String path, String fileName, final DownloadTaskListener listener, boolean force) {
        try {
            DownloadTask task = new DownloadTask(context, url, path, fileName, new DownloadTaskListener() {
                @Override
                public void updateProcess(DownloadTask task) {
                    listener.updateProcess(task);
                }

                @Override
                public void finishDownload(DownloadTask task, File file) {
                    try {
                        listener.finishDownload(task, file);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    finish(url);
                }

                @Override
                public void preDownload() {
                    listener.preDownload();
                }

                @Override
                public void errorDownload(int error) {
                    listener.errorDownload(error);
                }
            }, App.getInstance().provideMediaImageHttpClient());

            tasks.put(url, task);
            taskList.add(url);
            execute(task, force);
            return true;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 提供一键暂停、一键恢复功能
     */
    public void showNotification() {

    }

    /**
     * @param task  下载任务
     * @param force 强制执行下载,忽略最大并发限制
     */
    private synchronized void execute(DownloadTask task, boolean force) {
        if (concurrentTaskNum >= MAX_CONCURRENT_DOWNLOAD_TASK_NUM && !force) {
            return;
        }
        concurrentTaskNum++;
        task.execute();
    }

    private synchronized void finish(String url) {
        taskList.remove(url);
        tasks.remove(url);
        concurrentTaskNum--;

        // 找到队列最前面的未执行任务
        Iterator iterator = taskList.iterator();
        while (iterator.hasNext()) {
            String taskUrl = (String) iterator.next();
            DownloadTask task = tasks.get(taskUrl);
            if (task != null && task.getStatus() == DownloadTask.Status.PENDING) {
                execute(task, false);
            }
        }
    }
}
package solid.download;

import java.io.File;

public interface DownloadTaskListener {
    void updateProcess(DownloadTask task);

    void finishDownload(DownloadTask task, File file);

    void preDownload();

    void errorDownload(int error);
}
package solid.download;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;

import com.squareup.okhttp.Call;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;

import solid.util.FileUtils;
import solid.util.IOUtils;
import solid.util.L;

public class DownloadTask extends AsyncTask<Void, Integer, Long> {
    public final static int ERROR_NONE = 0;
    public final static int ERROR_SD_NO_MEMORY = 1;
    public final static int ERROR_BLOCK_INTERNET = 2;
    public final static int ERROR_UNKONW = 3;
    public final static int TIME_OUT = 30000;

    private final static int BUFFER_SIZE = 1024 * 8;

    private URL url;
    private File file;
    private String urlString;
    private Throwable exception;
    private DownloadTaskListener listener;
    private Context context;
    private Call call = null;
    private OkHttpClient client;

    private long downloadSize;
    private long previousFileSize;
    private long totalSize;
    private int downloadPercent;
    private long networkSpeed; // 网速
    private long previousTime;
    private long totalTime;
    private int errStatusCode = ERROR_NONE;
    private boolean interrupt = false;

    public DownloadTask(Context context, String urlString, String path)
            throws MalformedURLException, FileNotFoundException {
        this(context, urlString, path, null, null, null);
    }

    public DownloadTask(Context context, String urlString, String path, String fileName, DownloadTaskListener listener, OkHttpClient client)
            throws MalformedURLException, FileNotFoundException {
        super();
        this.context = context;
        this.urlString = urlString;
        this.url = new URL(urlString);
        this.listener = listener;
        this.client = client;

        if (TextUtils.isEmpty(fileName)) {
            fileName = new File(url.getFile()).getName();
            fileName = FileUtils.fixFileName(fileName);
        }
        L.v(null, "fileName: " + fileName);

        if (!FileUtils.makeSurePath(path)) {
            throw new FileNotFoundException("makeSurePath " + path);
        }

        this.file = new File(path, fileName);
    }

    public DownloadTaskListener getListener() {
        return listener;
    }

    public File getFile() {
        return file;
    }

    public String getUrlString() {
        return urlString;
    }

    public int getDownloadPercent() {
        return downloadPercent;
    }

    public long getDownloadSize() {
        return downloadSize + previousFileSize;
    }

    public long getTotalSize() {
        return totalSize;
    }

    public long getDownloadSpeed() {
        return this.networkSpeed;
    }

    public long getTotalTime() {
        return this.totalTime;
    }

    @Override
    public void onCancelled() {
        super.onCancelled();
        interrupt = true;
    }

    public int copy(InputStream input, RandomAccessFile out) throws Exception {
        byte[] buffer = new byte[BUFFER_SIZE];

        BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE);
        L.v(null, "length" + out.length());
        out.seek(out.length());

        int count = 0, n = 0;
        long errorBlockTimePreviousTime = -1, expireTime = 0;
        try {
            while (!interrupt) {
                n = in.read(buffer, 0, BUFFER_SIZE);
                if (n == -1) {
                    break;
                }
                out.write(buffer, 0, n);

                count += n;
                if (!isOnline()) {
                    interrupt = true;
                    errStatusCode = ERROR_BLOCK_INTERNET;
                    break;
                }

                if (networkSpeed == 0) {
                    if (errorBlockTimePreviousTime > 0) {
                        expireTime = System.currentTimeMillis() - errorBlockTimePreviousTime;
                        if (expireTime > TIME_OUT) {
                            errStatusCode = ERROR_BLOCK_INTERNET;
                            interrupt = true;
                        }
                    } else {
                        errorBlockTimePreviousTime = System.currentTimeMillis();
                    }
                } else {
                    expireTime = 0;
                    errorBlockTimePreviousTime = -1;
                }
            }
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                errStatusCode = ERROR_UNKONW;
                Log.e(null, e.getMessage(), e);
            }
            try {
                in.close();
            } catch (IOException e) {
                errStatusCode = ERROR_UNKONW;
                Log.e(null, e.getMessage(), e);
            }
        }
        return count;
    }

    @Override
    protected void onPreExecute() {
        previousTime = System.currentTimeMillis();
        if (listener != null)
            listener.preDownload();
    }

    @Override
    protected Long doInBackground(Void... params) {
        try {
            return download();
        } catch (Exception e) {
            if (call != null) {
                call.cancel();
            }
            exception = e;
            errStatusCode = ERROR_UNKONW;
            return null;
        }
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        if (progress.length > 1) {
            totalSize = progress[1];
            if (totalSize == -1) {
                if (listener != null)
                    listener.errorDownload(ERROR_UNKONW);
            }
        } else {
            totalTime = System.currentTimeMillis() - previousTime;
            downloadSize = progress[0];
            downloadPercent = (int) ((downloadSize + previousFileSize) * 100 / totalSize);
            networkSpeed = downloadSize / totalTime;
            if (listener != null)
                listener.updateProcess(this);
        }
    }

    @Override
    protected void onPostExecute(Long result) {
        if (interrupt) {
            if (errStatusCode != ERROR_NONE) {
                if (listener != null)
                    listener.errorDownload(errStatusCode);
            }

            return;
        }

        if (exception != null) {
            Log.v(null, "Download failed.", exception);
        }
        if (listener != null) {
            listener.finishDownload(this, file);
        }
    }

    private long download() throws Exception {
        L.v(null, "totalSize: " + totalSize);

        if (null == client) {
            client = new OkHttpClient();
        }

        Request request = new Request.Builder().url(urlString).build();
        Call call = client.newCall(request);
        Response response = call.execute();
        totalSize = response.body().contentLength();

        if (file.length() > 0 && totalSize > 0 && totalSize > file.length()) {
            // 此处要求服务端支持断点续传
            request = request.newBuilder().addHeader("Range", "bytes=" + file.length() + "-").build();
            previousFileSize = file.length();
            call.cancel();
            call = client.newCall(request);
            response = call.execute();
            L.v(null, "File is not complete, download now.");
            L.v(null, "File length:" + file.length() + " totalSize:" + totalSize);
        } else if (file.exists() && totalSize <= file.length()) {
            L.v(null, "Output file already exists. Skipping download.");
            publishProgress((int) totalSize);
            return 0l;
        }


        long storage = FileUtils.getAvailableStorage();
        L.i(null, "storage:" + storage + " totalSize:" + totalSize);
        if (totalSize - file.length() > storage) {
            errStatusCode = ERROR_SD_NO_MEMORY;
            interrupt = true;
            call.cancel();
            return 0l;
        }
        RandomAccessFile outputStream;
        try {
            outputStream = new ProgressReportingRandomAccessFile(file, "rw");
        } catch (FileNotFoundException e) {
            L.v(null, "OutputStream Error");
            throw e;
        }

        int bytesCopied = 0;
        try {
            publishProgress(0, (int) totalSize);

            InputStream input = null;
            input = response.body().byteStream();
//        try {
////            input = response.getEntity().getContent();
//        } catch (IOException ex) {
//            errStatusCode = ERROR_UNKONW;
//            call.cancel();
//            Log.v(null, "InputStream Error" + ex.getMessage());
//            return 0;
//        }

            bytesCopied = copy(input, outputStream);

            if ((previousFileSize + bytesCopied) != totalSize && totalSize != -1 && !interrupt) {
                throw new IOException("Download incomplete: " + bytesCopied + " != " + totalSize);
            }

            call.cancel();
            call = null;
            L.v(null, "Download completed successfully.");
        } finally {
            IOUtils.close(outputStream);
        }
        return bytesCopied;
    }

    private boolean isOnline() {
        try {
            ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
                    Context.CONNECTIVITY_SERVICE);
            NetworkInfo ni = cm.getActiveNetworkInfo();
            return ni != null && ni.isConnectedOrConnecting();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    private final class ProgressReportingRandomAccessFile extends RandomAccessFile {
        private int progress = 0;

        public ProgressReportingRandomAccessFile(File file, String mode)
                throws FileNotFoundException {
            super(file, mode);
        }

        @Override
        public void write(byte[] buffer, int offset, int count) throws IOException {
            super.write(buffer, offset, count);
            progress += count;
            publishProgress(progress);
        }
    }
}

以上是关于java 文件下载,支持任务暂停,恢复,断点续传;任务状态查询;任务并发控制的主要内容,如果未能解决你的问题,请参考以下文章

断点续传基本原理初了解

讲讲断点续传那点儿事

谷歌浏览器下载软件可以断点续传吗?

网络编程 下载任务,支持断点续传

Python实现下载界面(带进度条,断点续传,多线程多任务下载等)

Android开发——断点续传原理以及实现