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 文件下载,支持任务暂停,恢复,断点续传;任务状态查询;任务并发控制的主要内容,如果未能解决你的问题,请参考以下文章