Files
Aria/src/main/java/com/arialyy/downloadutil/DownLoadUtil.java
2016-02-19 22:42:25 +08:00

361 lines
15 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.arialyy.downloadutil;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import android.util.SparseArray;
import com.arialyy.downloadutil.inf.IDownloadListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Properties;
/**
* Created by lyy on 2015/8/25.
* 下载工具类
*/
public class DownLoadUtil {
private static final String TAG = "DownLoadUtil";
//下载监听
private IDownloadListener mListener;
/**
* 线程数
*/
private static final int THREAD_NUM = 3;
/**
* 已经完成下载任务的线程数量
*/
private int mCompleteThreadNum = 0;
private long mCurrentLocation;
private boolean isDownloading = false;
private boolean isStop = false;
private boolean isCancel = false;
private static final int TIME_OUT = 5000; //超时时间
boolean newTask = true;
private int mCancelNum = 0;
private int mStopNum = 0;
public DownLoadUtil() {
}
/**
* 获取当前下载位置
*
* @return
*/
public long getCurrentLocation() {
return mCurrentLocation;
}
public boolean isDownloading() {
return isDownloading;
}
/**
* 取消下载
*/
public void cancelDownload() {
isCancel = true;
}
/**
* 停止下载
*/
public void stopDownload() {
isStop = true;
}
/**
* 多线程断点续传下载文件,暂停和继续
*
* @param context 必须添加该参数不能使用全局变量的context
* @param downloadUrl 下载路径
* @param filePath 保存路径
* @param downloadListener 下载进度监听 {@link DownloadListener}
*/
public void download(final Context context, @NonNull final String downloadUrl, @NonNull final String filePath,
@NonNull final DownloadListener downloadListener) {
isDownloading = true;
mCurrentLocation = 0;
isStop = false;
isCancel = false;
mCancelNum = 0;
mStopNum = 0;
final File dFile = new File(filePath);
//读取已完成的线程数
final File configFile = new File(context.getFilesDir().getPath() + "/temp/" + dFile.getName() + ".properties");
try {
if (!configFile.exists()) { //记录文件被删除,则重新下载
newTask = true;
Util.createFile(configFile.getPath());
} else {
newTask = false;
}
} catch (Exception e) {
e.printStackTrace();
mListener.onFail();
return;
}
newTask = !dFile.exists();
new Thread(new Runnable() {
@Override
public void run() {
try {
mListener = downloadListener;
URL url = new URL(downloadUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Charset", "UTF-8");
conn.setConnectTimeout(TIME_OUT);
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn.connect();
int len = conn.getContentLength();
if (len < 0) { //网络被劫持时会出现这个问题
mListener.onFail();
return;
}
int code = conn.getResponseCode();
if (code == 200) {
int fileLength = conn.getContentLength();
//必须建一个文件
Util.createFile(filePath);
RandomAccessFile file = new RandomAccessFile(filePath, "rwd");
//设置文件长度
file.setLength(fileLength);
mListener.onPreDownload(conn);
//分配每条线程的下载区间
Properties pro = null;
pro = Util.loadConfig(configFile);
int blockSize = fileLength / THREAD_NUM;
SparseArray<Thread> tasks = new SparseArray<>();
for (int i = 0; i < THREAD_NUM; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize;
Object state = pro.getProperty(dFile.getName() + "_state_" + i);
if (state != null && Integer.parseInt(state + "") == 1) { //该线程已经完成
mCurrentLocation += endL - startL;
Log.d(TAG, "++++++++++ 线程_" + i + "_已经下载完成 ++++++++++");
mCompleteThreadNum++;
if (mCompleteThreadNum == THREAD_NUM) {
if (configFile.exists()) {
configFile.delete();
}
mListener.onComplete();
isDownloading = false;
System.gc();
return;
}
continue;
}
//分配下载位置
Object record = pro.getProperty(dFile.getName() + "_record_" + i);
if (!newTask && record != null && Long.parseLong(record + "") > 0) { //如果有记录,则恢复下载
Long r = Long.parseLong(record + "");
mCurrentLocation += r - startL;
Log.d(TAG, "++++++++++ 线程_" + i + "_恢复下载 ++++++++++");
mListener.onChildResume(r);
startL = r;
}
if (i == (THREAD_NUM - 1)) {
endL = fileLength;//如果整个文件的大小不为线程个数的整数倍,则最后一个线程的结束位置即为文件的总长度
}
DownloadEntity entity = new DownloadEntity(context, fileLength, downloadUrl, dFile, i, startL, endL);
DownLoadTask task = new DownLoadTask(entity);
tasks.put(i, new Thread(task));
}
if (mCurrentLocation > 0) {
mListener.onResume(mCurrentLocation);
} else {
mListener.onStart(mCurrentLocation);
}
for (int i = 0, count = tasks.size(); i < count; i++) {
Thread task = tasks.get(i);
if (task != null) {
task.start();
}
}
} else {
Log.e(TAG, "下载失败,返回码:" + code);
isDownloading = false;
System.gc();
mListener.onFail();
}
} catch (IOException e) {
Log.e(TAG, "下载失败【downloadUrl:" + downloadUrl + "\n【filePath:" + filePath + "" + Util.getPrintException(e));
isDownloading = false;
mListener.onFail();
}
}
}).start();
}
/**
* 多线程下载任务类,不能使用AsyncTask来进行多线程下载因为AsyncTask是串行执行的这种方式下载速度太慢了
*/
private class DownLoadTask implements Runnable {
private static final String TAG = "DownLoadTask";
private DownloadEntity dEntity;
private String configFPath;
public DownLoadTask(DownloadEntity downloadInfo) {
this.dEntity = downloadInfo;
configFPath = dEntity.context.getFilesDir().getPath() + "/temp/" + dEntity.tempFile.getName() + ".properties";
}
@Override
public void run() {
try {
Log.d(TAG, "线程_" + dEntity.threadId + "_正在下载【" + "开始位置 : " + dEntity.startLocation + ",结束位置:" + dEntity.endLocation + "");
URL url = new URL(dEntity.downloadUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//在头里面请求下载开始位置和结束位置
conn.setRequestProperty("Range", "bytes=" + dEntity.startLocation + "-" + dEntity.endLocation);
conn.setRequestMethod("GET");
conn.setRequestProperty("Charset", "UTF-8");
conn.setConnectTimeout(TIME_OUT);
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn.setReadTimeout(2000); //设置读取流的等待时间,必须设置该参数
InputStream is = conn.getInputStream();
//创建可设置位置的文件
RandomAccessFile file = new RandomAccessFile(dEntity.tempFile, "rwd");
//设置每条线程写入文件的位置
file.seek(dEntity.startLocation);
byte[] buffer = new byte[1024];
int len;
//当前子线程的下载位置
long currentLocation = dEntity.startLocation;
while ((len = is.read(buffer)) != -1) {
if (isCancel) {
Log.d(TAG, "++++++++++ thread_" + dEntity.threadId + "_cancel ++++++++++");
break;
}
if (isStop) {
break;
}
//把下载数据数据写入文件
file.write(buffer, 0, len);
synchronized (DownLoadUtil.this) {
mCurrentLocation += len;
mListener.onProgress(mCurrentLocation);
}
currentLocation += len;
}
file.close();
is.close();
if (isCancel) {
synchronized (DownLoadUtil.this) {
mCancelNum++;
if (mCancelNum == THREAD_NUM) {
File configFile = new File(configFPath);
if (configFile.exists()) {
configFile.delete();
}
if (dEntity.tempFile.exists()) {
dEntity.tempFile.delete();
}
Log.d(TAG, "++++++++++++++++ onCancel +++++++++++++++++");
isDownloading = false;
mListener.onCancel();
System.gc();
}
}
return;
}
//停止状态不需要删除记录文件
if (isStop) {
synchronized (DownLoadUtil.this) {
mStopNum++;
String location = String.valueOf(currentLocation);
Log.i(TAG, "thread_" + dEntity.threadId + "_stop, stop location ==> " + currentLocation);
writeConfig(dEntity.tempFile.getName() + "_record_" + dEntity.threadId, location);
if (mStopNum == THREAD_NUM) {
Log.d(TAG, "++++++++++++++++ onStop +++++++++++++++++");
isDownloading = false;
mListener.onStop(mCurrentLocation);
System.gc();
}
}
return;
}
Log.i(TAG, "线程【" + dEntity.threadId + "】下载完毕");
writeConfig(dEntity.tempFile.getName() + "_state_" + dEntity.threadId, 1 + "");
mListener.onChildComplete(dEntity.endLocation);
mCompleteThreadNum++;
if (mCompleteThreadNum == THREAD_NUM) {
File configFile = new File(configFPath);
if (configFile.exists()) {
configFile.delete();
}
mListener.onComplete();
isDownloading = false;
System.gc();
}
} catch (MalformedURLException e) {
e.printStackTrace();
isDownloading = false;
mListener.onFail();
} catch (IOException e) {
Log.e(TAG, "下载失败【" + dEntity.downloadUrl + "" + Util.getPrintException(e));
isDownloading = false;
mListener.onFail();
} catch (Exception e) {
Log.e(TAG, "获取流失败" + Util.getPrintException(e));
isDownloading = false;
mListener.onFail();
}
}
/**
* 将记录写入到配置文件
*
* @param record
*/
private void writeConfig(String key, String record) throws IOException {
File configFile = new File(configFPath);
Properties pro = Util.loadConfig(configFile);
pro.setProperty(key, record);
Util.saveConfig(configFile, pro);
}
}
/**
* 子线程下载信息类
*/
private class DownloadEntity {
//文件大小
long fileSize;
String downloadUrl;
int threadId;
long startLocation;
long endLocation;
File tempFile;
Context context;
public DownloadEntity(Context context, long fileSize, String downloadUrl, File file, int threadId, long startLocation, long endLocation) {
this.fileSize = fileSize;
this.downloadUrl = downloadUrl;
this.tempFile = file;
this.threadId = threadId;
this.startLocation = startLocation;
this.endLocation = endLocation;
this.context = context;
}
}
}