分块下载实现

pull/330/head
laoyuyu 7 years ago
parent ac5e236664
commit 7bac199856
  1. 85
      Aria/src/main/java/com/arialyy/aria/core/ConfigHelper.java
  2. 31
      Aria/src/main/java/com/arialyy/aria/core/ConfigType.java
  3. 55
      Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java
  4. 32
      Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java
  5. 17
      Aria/src/main/java/com/arialyy/aria/core/common/StateConstance.java
  6. 11
      Aria/src/main/java/com/arialyy/aria/core/common/TaskRecord.java
  7. 78
      Aria/src/main/java/com/arialyy/aria/core/common/ThreadTaskManager.java
  8. 14
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java
  9. 50
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java
  10. 17
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java
  11. 2
      Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java
  12. 29
      Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java
  13. 55
      Aria/src/main/java/com/arialyy/aria/util/FileUtil.java
  14. 11
      app/src/main/assets/aria_config.xml
  15. 4
      app/src/main/java/com/arialyy/simple/test/AnyRunActivity.java
  16. 10
      app/src/main/java/com/arialyy/simple/test/AnyRunnModule.java
  17. 23
      aria/src/main/java/com/arialyy/aria/core/Configuration.java

@ -28,10 +28,10 @@ import org.xml.sax.helpers.DefaultHandler;
class ConfigHelper extends DefaultHandler { class ConfigHelper extends DefaultHandler {
private final String TAG = "ConfigHelper"; private final String TAG = "ConfigHelper";
private boolean isDownloadConfig = false, isUploadConfig = false, isAppConfig = false;
private Configuration.DownloadConfig mDownloadConfig = Configuration.DownloadConfig.getInstance(); private Configuration.DownloadConfig mDownloadConfig = Configuration.DownloadConfig.getInstance();
private Configuration.UploadConfig mUploadConfig = Configuration.UploadConfig.getInstance(); private Configuration.UploadConfig mUploadConfig = Configuration.UploadConfig.getInstance();
private Configuration.AppConfig mAppConfig = Configuration.AppConfig.getInstance(); private Configuration.AppConfig mAppConfig = Configuration.AppConfig.getInstance();
private @ConfigType int mType;
@Override public void startDocument() throws SAXException { @Override public void startDocument() throws SAXException {
super.startDocument(); super.startDocument();
@ -41,21 +41,19 @@ class ConfigHelper extends DefaultHandler {
public void startElement(String uri, String localName, String qName, Attributes attributes) public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException { throws SAXException {
super.startElement(uri, localName, qName, attributes); super.startElement(uri, localName, qName, attributes);
if (qName.equals("download")) { switch (qName) {
isDownloadConfig = true; case "download":
isUploadConfig = false; mType = ConfigType.DOWNLOAD;
isAppConfig = false; break;
} else if (qName.equals("upload")) { case "upload":
isUploadConfig = true; mType = ConfigType.UPLOAD;
isDownloadConfig = false; break;
isAppConfig = false; case "app":
} else if (qName.equals("app")) { mType = ConfigType.APP;
isUploadConfig = false; break;
isDownloadConfig = false; }
isAppConfig = true;
} if (mType == ConfigType.DOWNLOAD || mType == ConfigType.UPLOAD) {
if (isDownloadConfig || isUploadConfig) {
String value = attributes.getValue("value"); String value = attributes.getValue("value");
switch (qName) { switch (qName) {
@ -100,8 +98,11 @@ class ConfigHelper extends DefaultHandler {
case "notNetRetry": case "notNetRetry":
loadNotNetRetry(value); loadNotNetRetry(value);
break; break;
case "useBlock":
loadUseBlock(value);
break;
} }
} else if (isAppConfig) { } else if (mType == ConfigType.APP) {
String value = attributes.getValue("value"); String value = attributes.getValue("value");
switch (qName) { switch (qName) {
case "useAriaCrashHandler": case "useAriaCrashHandler":
@ -114,11 +115,17 @@ class ConfigHelper extends DefaultHandler {
} }
} }
private void loadUseBlock(String value) {
if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.useBlock = checkBoolean(value) ? Boolean.valueOf(value) : false;
}
}
private void loadNotNetRetry(String value) { private void loadNotNetRetry(String value) {
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.notNetRetry = checkBoolean(value) ? Boolean.valueOf(value) : false; mDownloadConfig.notNetRetry = checkBoolean(value) ? Boolean.valueOf(value) : false;
} }
if (isUploadConfig) { if (mType == ConfigType.UPLOAD) {
mUploadConfig.notNetRetry = checkBoolean(value) ? Boolean.valueOf(value) : false; mUploadConfig.notNetRetry = checkBoolean(value) ? Boolean.valueOf(value) : false;
} }
} }
@ -150,10 +157,10 @@ class ConfigHelper extends DefaultHandler {
private void loadUpdateInterval(String value) { private void loadUpdateInterval(String value) {
long temp = checkLong(value) ? Long.parseLong(value) : 1000; long temp = checkLong(value) ? Long.parseLong(value) : 1000;
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.updateInterval = temp; mDownloadConfig.updateInterval = temp;
} }
if (isUploadConfig) { if (mType == ConfigType.UPLOAD) {
mUploadConfig.updateInterval = temp; mUploadConfig.updateInterval = temp;
} }
} }
@ -164,17 +171,17 @@ class ConfigHelper extends DefaultHandler {
"wait"))) { "wait"))) {
mod = value; mod = value;
} }
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.queueMod = mod; mDownloadConfig.queueMod = mod;
} }
if (isUploadConfig) { if (mType == ConfigType.UPLOAD) {
mUploadConfig.queueMod = mod; mUploadConfig.queueMod = mod;
} }
} }
private void loadMaxSpeed(String value) { private void loadMaxSpeed(String value) {
int maxSpeed = checkInt(value) ? Integer.parseInt(value) : 0; int maxSpeed = checkInt(value) ? Integer.parseInt(value) : 0;
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.maxSpeed = maxSpeed; mDownloadConfig.maxSpeed = maxSpeed;
} }
} }
@ -185,10 +192,10 @@ class ConfigHelper extends DefaultHandler {
open = Boolean.parseBoolean(value); open = Boolean.parseBoolean(value);
} }
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.isConvertSpeed = open; mDownloadConfig.isConvertSpeed = open;
} }
if (isUploadConfig) { if (mType == ConfigType.UPLOAD) {
mUploadConfig.isConvertSpeed = open; mUploadConfig.isConvertSpeed = open;
} }
} }
@ -200,13 +207,13 @@ class ConfigHelper extends DefaultHandler {
time = 2 * 1000; time = 2 * 1000;
} }
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.reTryInterval = time; mDownloadConfig.reTryInterval = time;
} }
} }
private void loadCA(String name, String path) { private void loadCA(String name, String path) {
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.caName = name; mDownloadConfig.caName = name;
mDownloadConfig.caPath = path; mDownloadConfig.caPath = path;
} }
@ -219,11 +226,11 @@ class ConfigHelper extends DefaultHandler {
buffSize = 2048; buffSize = 2048;
} }
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.buffSize = buffSize; mDownloadConfig.buffSize = buffSize;
} }
if (isUploadConfig) { if (mType == ConfigType.UPLOAD) {
mUploadConfig.buffSize = buffSize; mUploadConfig.buffSize = buffSize;
} }
} }
@ -235,11 +242,11 @@ class ConfigHelper extends DefaultHandler {
time = 10 * 1000; time = 10 * 1000;
} }
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.iOTimeOut = time; mDownloadConfig.iOTimeOut = time;
} }
if (isUploadConfig) { if (mType == ConfigType.UPLOAD) {
mUploadConfig.iOTimeOut = time; mUploadConfig.iOTimeOut = time;
} }
} }
@ -247,10 +254,10 @@ class ConfigHelper extends DefaultHandler {
private void loadConnectTime(String value) { private void loadConnectTime(String value) {
int time = checkInt(value) ? Integer.parseInt(value) : 5 * 1000; int time = checkInt(value) ? Integer.parseInt(value) : 5 * 1000;
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.connectTimeOut = time; mDownloadConfig.connectTimeOut = time;
} }
if (isUploadConfig) { if (mType == ConfigType.UPLOAD) {
mUploadConfig.connectTimeOut = time; mUploadConfig.connectTimeOut = time;
} }
} }
@ -258,10 +265,10 @@ class ConfigHelper extends DefaultHandler {
private void loadReTry(String value) { private void loadReTry(String value) {
int num = checkInt(value) ? Integer.parseInt(value) : 0; int num = checkInt(value) ? Integer.parseInt(value) : 0;
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.reTryNum = num; mDownloadConfig.reTryNum = num;
} }
if (isUploadConfig) { if (mType == ConfigType.UPLOAD) {
mUploadConfig.reTryNum = num; mUploadConfig.reTryNum = num;
} }
} }
@ -272,10 +279,10 @@ class ConfigHelper extends DefaultHandler {
ALog.w(TAG, "任务队列数不能小于 1"); ALog.w(TAG, "任务队列数不能小于 1");
num = 2; num = 2;
} }
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.maxTaskNum = num; mDownloadConfig.maxTaskNum = num;
} }
if (isUploadConfig) { if (mType == ConfigType.UPLOAD) {
mUploadConfig.maxTaskNum = num; mUploadConfig.maxTaskNum = num;
} }
} }
@ -286,7 +293,7 @@ class ConfigHelper extends DefaultHandler {
ALog.e(TAG, "下载线程数不能小于 1"); ALog.e(TAG, "下载线程数不能小于 1");
num = 1; num = 1;
} }
if (isDownloadConfig) { if (mType == ConfigType.DOWNLOAD) {
mDownloadConfig.threadNum = num; mDownloadConfig.threadNum = num;
} }
} }

@ -0,0 +1,31 @@
/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.core;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@IntDef({
ConfigType.DOWNLOAD,
ConfigType.UPLOAD,
ConfigType.APP
})
@Retention(RetentionPolicy.SOURCE) @interface ConfigType {
int DOWNLOAD = 1;
int UPLOAD = 2;
int APP = 3;
}

@ -18,6 +18,7 @@ package com.arialyy.aria.core.common;
import android.content.Context; import android.content.Context;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadTaskEntity;
import com.arialyy.aria.core.inf.AbsNormalEntity; import com.arialyy.aria.core.inf.AbsNormalEntity;
import com.arialyy.aria.core.inf.AbsTaskEntity; import com.arialyy.aria.core.inf.AbsTaskEntity;
import com.arialyy.aria.core.inf.IDownloadListener; import com.arialyy.aria.core.inf.IDownloadListener;
@ -47,6 +48,10 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_ENTITY exte
implements Runnable, IUtil { implements Runnable, IUtil {
private static final String STATE = "_state_"; private static final String STATE = "_state_";
private static final String RECORD = "_record_"; private static final String RECORD = "_record_";
/**
* 分块文件路径
*/
public static final String SUB_PATH = "%s.%s.part";
/** /**
* 小于1m的文件不启用多线程 * 小于1m的文件不启用多线程
*/ */
@ -74,7 +79,7 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_ENTITY exte
* 进度刷新间隔 * 进度刷新间隔
*/ */
private long mUpdateInterval = 1000; private long mUpdateInterval = 1000;
private TaskRecord mRecord; protected TaskRecord mRecord;
protected AbsFileer(IEventListener listener, TASK_ENTITY taskEntity) { protected AbsFileer(IEventListener listener, TASK_ENTITY taskEntity) {
mListener = listener; mListener = listener;
@ -117,23 +122,36 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_ENTITY exte
if (!mTaskEntity.isSupportBP()) { if (!mTaskEntity.isSupportBP()) {
mTotalThreadNum = 1; mTotalThreadNum = 1;
mStartThreadNum = 1; mStartThreadNum = 1;
handleNoSupportBP();
} else { } else {
mTotalThreadNum = mTotalThreadNum =
mTaskEntity.isNewTask() ? (mStartThreadNum = setNewTaskThreadNum()) : mTotalThreadNum; mTaskEntity.isNewTask() ? (mStartThreadNum = setNewTaskThreadNum()) : mTotalThreadNum;
handleBreakpoint();
} }
mConstance.START_THREAD_NUM = mTotalThreadNum; mConstance.START_THREAD_NUM = mTotalThreadNum;
// 处理分块和动态文件参数
if (mTaskEntity.isNewTask() && mTaskEntity instanceof DownloadTaskEntity) {
AriaManager manager = AriaManager.getInstance(AriaManager.APP);
mRecord.isBlock = mTotalThreadNum > 1 && manager.getDownloadConfig().isUseBlock();
// 线程数不等1并且没有使用块下载,则认为没有使用动态文件
mRecord.isOpenDynamicFile = mTotalThreadNum == 1 || mRecord.isBlock;
}
/* /*
* mTaskEntity.getEntity().getFileSize() != mTempFile.length()为兼容以前老版本代码 * mTaskEntity.getEntity().getFileSize() != mTempFile.length()为兼容以前老版本代码
* 动态长度条件 * 动态长度条件
* 1总线程数为1并且是新任务 * 1总线程数为1并且是新任务
* 2总线程数为1不是新任务但是长度不是文件全长度 * 2总线程数为1不是新任务但是长度不是文件全长度
*/ */
if (mTotalThreadNum == 1 && (mTaskEntity.isNewTask() if (mTotalThreadNum == 1 && (mTaskEntity.getEntity().getFileSize() != mTempFile.length())) {
|| mTaskEntity.getEntity().getFileSize() != mTempFile.length())) { mRecord.isOpenDynamicFile = true;
mConstance.isOpenDynamicFile = true; }
if (!mTaskEntity.isSupportBP()) {
handleNoSupportBP();
} else {
handleBreakpoint();
} }
startTimer(); startTimer();
} }
@ -254,17 +272,16 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_ENTITY exte
} else { } else {
mRecord = DbHelper.getTaskRecord(mTaskEntity.getKey()); mRecord = DbHelper.getTaskRecord(mTaskEntity.getKey());
if (mRecord == null) { if (mRecord == null) {
initRecord(); initRecord(true);
mTaskEntity.setNewTask(true);
} else { } else {
if (mRecord.threadRecords == null || mRecord.threadRecords.isEmpty()) { if (mRecord.threadRecords == null || mRecord.threadRecords.isEmpty()) {
initRecord(); initRecord(true);
mTaskEntity.setNewTask(true); }
} else if (mTempFile.length() == 0) { //else if (mTempFile.length() == 0) {
mRecord.deleteData(); // mRecord.deleteData();
initRecord(); // initRecord(true);
mTaskEntity.setNewTask(true); //}
} else { else {
for (ThreadRecord tr : mRecord.threadRecords) { for (ThreadRecord tr : mRecord.threadRecords) {
if (tr.isComplete) { if (tr.isComplete) {
mCompleteThreadNum++; mCompleteThreadNum++;
@ -294,7 +311,7 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_ENTITY exte
mTaskEntity.setNewTask(true); mTaskEntity.setNewTask(true);
return; return;
} }
initRecord(); initRecord(false);
Set<Object> keys = pro.keySet(); Set<Object> keys = pro.keySet();
// 老版本记录是5s存一次,但是5s中内,如果线程执行完成,record记录是没有的,只有state记录... // 老版本记录是5s存一次,但是5s中内,如果线程执行完成,record记录是没有的,只有state记录...
// 第一步应该是record 和 state去重取正确的线程数 // 第一步应该是record 和 state去重取正确的线程数
@ -338,7 +355,7 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_ENTITY exte
/** /**
* 初始化记录 * 初始化记录
*/ */
private void initRecord() { private void initRecord(boolean isNewTask) {
mRecord = new TaskRecord(); mRecord = new TaskRecord();
mRecord.fileName = mEntity.getFileName(); mRecord.fileName = mEntity.getFileName();
mRecord.filePath = mTaskEntity.getKey(); mRecord.filePath = mTaskEntity.getKey();
@ -349,6 +366,7 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_ENTITY exte
mRecord.dGroupName = ((DownloadEntity) mTaskEntity.getEntity()).getGroupName(); mRecord.dGroupName = ((DownloadEntity) mTaskEntity.getEntity()).getGroupName();
} }
} }
mTaskEntity.setNewTask(isNewTask);
} }
/** /**
@ -399,7 +417,8 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_ENTITY exte
SubThreadConfig<TASK_ENTITY> config = new SubThreadConfig<>(); SubThreadConfig<TASK_ENTITY> config = new SubThreadConfig<>();
config.FILE_SIZE = fileLength; config.FILE_SIZE = fileLength;
config.URL = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl(); config.URL = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl();
config.TEMP_FILE = mTempFile; config.TEMP_FILE =
mRecord.isBlock ? new File(String.format(SUB_PATH, mTempFile.getPath(), i)) : mTempFile;
config.THREAD_ID = i; config.THREAD_ID = i;
config.START_LOCATION = startL; config.START_LOCATION = startL;
config.END_LOCATION = endL; config.END_LOCATION = endL;

@ -23,8 +23,12 @@ import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.upload.UploadEntity; import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.ErrorHelp; import com.arialyy.aria.util.ErrorHelp;
import com.arialyy.aria.util.FileUtil;
import com.arialyy.aria.util.NetUtils; import com.arialyy.aria.util.NetUtils;
import java.io.File;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -69,10 +73,10 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_ENTITY
}); });
protected AbsThreadTask(StateConstance constance, IEventListener listener, protected AbsThreadTask(StateConstance constance, IEventListener listener,
SubThreadConfig<TASK_ENTITY> info) { SubThreadConfig<TASK_ENTITY> config) {
STATE = constance; STATE = constance;
mListener = listener; mListener = listener;
mConfig = info; mConfig = config;
mTaskEntity = mConfig.TASK_ENTITY; mTaskEntity = mConfig.TASK_ENTITY;
mEntity = mTaskEntity.getEntity(); mEntity = mTaskEntity.getEntity();
mLastSaveTime = System.currentTimeMillis(); mLastSaveTime = System.currentTimeMillis();
@ -134,6 +138,30 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_ENTITY
return STATE.isCancel || STATE.isStop || taskBreak; return STATE.isCancel || STATE.isStop || taskBreak;
} }
/**
* 合并文件
*
* @return {@code true} 合并成功{@code false}合并失败
*/
protected boolean mergeFile() {
List<String> partPath = new ArrayList<>();
for (int i = 0, len = STATE.TASK_RECORD.threadNum; i < len; i++) {
partPath.add(String.format(AbsFileer.SUB_PATH, STATE.TASK_RECORD.filePath, i));
}
boolean isSuccess = FileUtil.mergeFile(STATE.TASK_RECORD.filePath, partPath);
if (isSuccess) {
for (String pp : partPath) {
File f = new File(pp);
if (f.exists()) {
f.delete();
}
}
return true;
} else {
return false;
}
}
/** /**
* 停止任务 * 停止任务
*/ */

@ -17,7 +17,7 @@ package com.arialyy.aria.core.common;
/** /**
* Created by lyy on 2017/1/18. * Created by lyy on 2017/1/18.
* 下载状态常量 * 状态常量
*/ */
public class StateConstance { public class StateConstance {
public int CANCEL_NUM = 0; public int CANCEL_NUM = 0;
@ -30,13 +30,8 @@ public class StateConstance {
public boolean isCancel = false; public boolean isCancel = false;
public boolean isStop = false; public boolean isStop = false;
public TaskRecord TASK_RECORD; public TaskRecord TASK_RECORD;
/**
* 是否是使用虚拟文件下载的
* {@code true}{@code false}不是
*/
public boolean isOpenDynamicFile = false;
public StateConstance() { StateConstance() {
} }
public void resetState() { public void resetState() {
@ -50,14 +45,14 @@ public class StateConstance {
} }
/** /**
* 所有子线程是否都已经停止下载 * 所有子线程是否都已经停止
*/ */
public boolean isStop() { public boolean isStop() {
return STOP_NUM == START_THREAD_NUM; return STOP_NUM == START_THREAD_NUM;
} }
/** /**
* 所有子线程是否都已经下载失败 * 所有子线程是否都已经失败
*/ */
public boolean isFail() { public boolean isFail() {
return COMPLETE_THREAD_NUM != START_THREAD_NUM return COMPLETE_THREAD_NUM != START_THREAD_NUM
@ -65,14 +60,14 @@ public class StateConstance {
} }
/** /**
* 所有子线程是否都已经完成下载 * 所有子线程是否都已经完成
*/ */
public boolean isComplete() { public boolean isComplete() {
return COMPLETE_THREAD_NUM >= START_THREAD_NUM; return COMPLETE_THREAD_NUM >= START_THREAD_NUM;
} }
/** /**
* 所有子线程是否都已经取消下载 * 所有子线程是否都已经取消
*/ */
public boolean isCancel() { public boolean isCancel() {
return CANCEL_NUM == START_THREAD_NUM; return CANCEL_NUM == START_THREAD_NUM;

@ -73,4 +73,15 @@ public class TaskRecord extends DbEntity {
@Ignore @Ignore
@Deprecated @Deprecated
public String uGroupName; public String uGroupName;
/**
* 是否是分块{@code true}{@code false} 不是
*/
public boolean isBlock = false;
/**
* 是否是使用虚拟文件下载的
* {@code true}{@code false}不是
*/
public boolean isOpenDynamicFile = false;
} }

@ -0,0 +1,78 @@
/*
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.arialyy.aria.core.common;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 线程任务管理器
*/
class ThreadTaskManager {
private static volatile ThreadTaskManager INSTANCE = null;
private Map<String, List<AbsThreadTask>> mThreadTasks = new ConcurrentHashMap<>();
private ThreadTaskManager() {
}
public static ThreadTaskManager getInstance() {
if (INSTANCE == null) {
synchronized (ThreadTaskManager.class) {
INSTANCE = new ThreadTaskManager();
}
}
return INSTANCE;
}
/**
* 添加单条线程记录
*
* @param key 任务对应的key
* @param threadTask 线程任务
*/
public void addTask(String key, AbsThreadTask threadTask) {
if (mThreadTasks.get(key) == null) {
mThreadTasks.put(key, new ArrayList<AbsThreadTask>());
}
mThreadTasks.get(key).add(threadTask);
}
/**
* 删除对应的任务的线程记录
*
* @param key 任务对应的key
*/
public void removeTask(String key) {
for (Iterator<Map.Entry<String, List<AbsThreadTask>>> iter = mThreadTasks.entrySet().iterator();
iter.hasNext(); ) {
Map.Entry<String, List<AbsThreadTask>> entry = iter.next();
if (key.equals(entry.getKey())) {
List<AbsThreadTask> list = mThreadTasks.get(key);
if (list != null && !list.isEmpty()) {
list.clear();
}
iter.remove();
}
}
}
}

@ -57,22 +57,20 @@ class Downloader extends AbsFileer<DownloadEntity, DownloadTaskEntity> {
} }
@Override protected boolean handleNewTask() { @Override protected boolean handleNewTask() {
CommonUtil.createFile(mTempFile.getPath()); if (!mRecord.isBlock) {
CommonUtil.createFile(mTempFile.getPath());
}
BufferedRandomAccessFile file = null; BufferedRandomAccessFile file = null;
try { try {
if (mTotalThreadNum > 1) { if (mTotalThreadNum > 1 && !mRecord.isBlock) {
file = new BufferedRandomAccessFile(new File(mTempFile.getPath()), "rwd", 8192); file = new BufferedRandomAccessFile(new File(mTempFile.getPath()), "rwd", 8192);
//设置文件长度 //设置文件长度
file.setLength(mEntity.getFileSize()); file.setLength(mEntity.getFileSize());
} }
return true; return true;
} catch (IOException e) { } catch (IOException e) {
failDownload("下载失败【downloadUrl:" failDownload(String.format("下载失败【downloadUrl:%s】;【filePath:%s】\n %S", mEntity.getUrl(),
+ mEntity.getUrl() mEntity.getDownloadPath(), ALog.getExceptionString(e)));
+ "】\n【filePath:"
+ mEntity.getDownloadPath()
+ "】\n"
+ ALog.getExceptionString(e));
} finally { } finally {
if (file != null) { if (file != null) {
try { try {

@ -41,6 +41,7 @@ import org.apache.commons.net.ftp.FTPReply;
class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DownloadTaskEntity> { class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DownloadTaskEntity> {
private final String TAG = "FtpThreadTask"; private final String TAG = "FtpThreadTask";
private boolean isOpenDynamicFile; private boolean isOpenDynamicFile;
private boolean isBlock;
FtpThreadTask(StateConstance constance, IDownloadListener listener, FtpThreadTask(StateConstance constance, IDownloadListener listener,
SubThreadConfig<DownloadTaskEntity> downloadInfo) { SubThreadConfig<DownloadTaskEntity> downloadInfo) {
@ -50,11 +51,16 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DownloadTaskEntity>
mReadTimeOut = manager.getDownloadConfig().getIOTimeOut(); mReadTimeOut = manager.getDownloadConfig().getIOTimeOut();
mBufSize = manager.getDownloadConfig().getBuffSize(); mBufSize = manager.getDownloadConfig().getBuffSize();
isNotNetRetry = manager.getDownloadConfig().isNotNetRetry(); isNotNetRetry = manager.getDownloadConfig().isNotNetRetry();
isOpenDynamicFile = STATE.isOpenDynamicFile; isOpenDynamicFile = STATE.TASK_RECORD.isOpenDynamicFile;
isBlock = STATE.TASK_RECORD.isBlock;
setMaxSpeed(manager.getDownloadConfig().getMaxSpeed()); setMaxSpeed(manager.getDownloadConfig().getMaxSpeed());
} }
@Override public void run() { @Override public void run() {
if (mConfig.THREAD_RECORD.isComplete) {
handleComplete();
return;
}
//当前子线程的下载位置 //当前子线程的下载位置
mChildCurrentLocation = mConfig.START_LOCATION; mChildCurrentLocation = mConfig.START_LOCATION;
FTPClient client = null; FTPClient client = null;
@ -100,19 +106,7 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DownloadTaskEntity>
if (isBreak()) { if (isBreak()) {
return; return;
} }
ALog.i(TAG, handleComplete();
String.format("任务【%s】线程__%s__下载完毕", mConfig.TEMP_FILE.getName(), mConfig.THREAD_ID));
writeConfig(true, mConfig.END_LOCATION);
STATE.COMPLETE_THREAD_NUM++;
if (STATE.isComplete()) {
STATE.TASK_RECORD.deleteData();
STATE.isRunning = false;
mListener.onComplete();
}
if (STATE.isFail()) {
STATE.isRunning = false;
mListener.onFail(false);
}
} catch (IOException e) { } catch (IOException e) {
fail(mChildCurrentLocation, String.format("下载失败【%s】", mConfig.URL), e); fail(mChildCurrentLocation, String.format("下载失败【%s】", mConfig.URL), e);
} catch (Exception e) { } catch (Exception e) {
@ -131,6 +125,34 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DownloadTaskEntity>
} }
} }
/**
* 处理线程完成的情况
*/
private void handleComplete() {
ALog.i(TAG,
String.format("任务【%s】线程__%s__下载完毕", mConfig.TEMP_FILE.getName(), mConfig.THREAD_ID));
writeConfig(true, mConfig.END_LOCATION);
STATE.COMPLETE_THREAD_NUM++;
if (STATE.isComplete()) {
if (isBlock) {
boolean success = mergeFile();
if (!success) {
ALog.e(TAG, String.format("任务【%s】分块文件合并失败", mConfig.TEMP_FILE.getName()));
STATE.isRunning = false;
mListener.onFail(false);
return;
}
}
STATE.TASK_RECORD.deleteData();
STATE.isRunning = false;
mListener.onComplete();
}
if (STATE.isFail()) {
STATE.isRunning = false;
mListener.onFail(false);
}
}
/** /**
* 动态长度文件读取方式 * 动态长度文件读取方式
*/ */

@ -44,6 +44,7 @@ import java.nio.channels.ReadableByteChannel;
final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DownloadTaskEntity> { final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DownloadTaskEntity> {
private final String TAG = "HttpThreadTask"; private final String TAG = "HttpThreadTask";
private boolean isOpenDynamicFile; private boolean isOpenDynamicFile;
private boolean isBlock;
HttpThreadTask(StateConstance constance, IDownloadListener listener, HttpThreadTask(StateConstance constance, IDownloadListener listener,
SubThreadConfig<DownloadTaskEntity> downloadInfo) { SubThreadConfig<DownloadTaskEntity> downloadInfo) {
@ -53,11 +54,16 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DownloadTaskEnt
mReadTimeOut = manager.getDownloadConfig().getIOTimeOut(); mReadTimeOut = manager.getDownloadConfig().getIOTimeOut();
mBufSize = manager.getDownloadConfig().getBuffSize(); mBufSize = manager.getDownloadConfig().getBuffSize();
isNotNetRetry = manager.getDownloadConfig().isNotNetRetry(); isNotNetRetry = manager.getDownloadConfig().isNotNetRetry();
isOpenDynamicFile = STATE.isOpenDynamicFile; isOpenDynamicFile = STATE.TASK_RECORD.isOpenDynamicFile;
isBlock = STATE.TASK_RECORD.isBlock;
setMaxSpeed(manager.getDownloadConfig().getMaxSpeed()); setMaxSpeed(manager.getDownloadConfig().getMaxSpeed());
} }
@Override public void run() { @Override public void run() {
if (mConfig.THREAD_RECORD.isComplete) {
handleComplete();
return;
}
HttpURLConnection conn = null; HttpURLConnection conn = null;
BufferedInputStream is = null; BufferedInputStream is = null;
BufferedRandomAccessFile file = null; BufferedRandomAccessFile file = null;
@ -209,6 +215,15 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DownloadTaskEnt
writeConfig(true, mConfig.END_LOCATION); writeConfig(true, mConfig.END_LOCATION);
STATE.COMPLETE_THREAD_NUM++; STATE.COMPLETE_THREAD_NUM++;
if (STATE.isComplete()) { if (STATE.isComplete()) {
if (isBlock) {
boolean success = mergeFile();
if (!success) {
ALog.e(TAG, String.format("任务【%s】分块文件合并失败", mConfig.TEMP_FILE.getName()));
STATE.isRunning = false;
mListener.onFail(false);
return;
}
}
STATE.TASK_RECORD.deleteData(); STATE.TASK_RECORD.deleteData();
STATE.isRunning = false; STATE.isRunning = false;
mListener.onComplete(); mListener.onComplete();

@ -35,7 +35,7 @@ class DBConfig {
/*adb pull /mnt/sdcard/Android/data/com.arialyy.simple/files/DB/AriaLyyDb d:/db*/ /*adb pull /mnt/sdcard/Android/data/com.arialyy.simple/files/DB/AriaLyyDb d:/db*/
static Map<String, Class> mapping = new HashMap<>(); static Map<String, Class> mapping = new HashMap<>();
static String DB_NAME; static String DB_NAME;
static int VERSION = 34; static int VERSION = 35;
/** /**
* 是否将数据库保存在Sd卡{@code true} * 是否将数据库保存在Sd卡{@code true}

@ -30,6 +30,7 @@ import com.arialyy.aria.core.command.group.AbsGroupCmd;
import com.arialyy.aria.core.command.group.GroupCmdFactory; import com.arialyy.aria.core.command.group.GroupCmdFactory;
import com.arialyy.aria.core.command.normal.AbsNormalCmd; import com.arialyy.aria.core.command.normal.AbsNormalCmd;
import com.arialyy.aria.core.command.normal.NormalCmdFactory; import com.arialyy.aria.core.command.normal.NormalCmdFactory;
import com.arialyy.aria.core.common.AbsFileer;
import com.arialyy.aria.core.common.TaskRecord; import com.arialyy.aria.core.common.TaskRecord;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.core.download.DownloadGroupEntity;
@ -411,8 +412,17 @@ public class CommonUtil {
if (records == null || records.isEmpty()) { if (records == null || records.isEmpty()) {
ALog.w(TAG, "组任务记录删除失败,记录为null"); ALog.w(TAG, "组任务记录删除失败,记录为null");
} else { } else {
for (TaskRecord tr : records) { for (TaskRecord record : records) {
tr.deleteData(); // 删除分块文件
if (record.isBlock) {
for (int i = 0, len = record.threadNum; i < len; i++) {
File partFile = new File(String.format(AbsFileer.SUB_PATH, record.filePath, i));
if (partFile.exists()) {
partFile.delete();
}
}
}
record.deleteData();
} }
} }
@ -449,8 +459,19 @@ public class CommonUtil {
ALog.w(TAG, "删除记录失败,未知类型"); ALog.w(TAG, "删除记录失败,未知类型");
return; return;
} }
if (file.exists() && (removeFile || !dEntity.isComplete())) { if (removeFile || !dEntity.isComplete()) {
file.delete(); // 删除分块文件
if (record.isBlock) {
for (int i = 0, len = record.threadNum; i < len; i++) {
File partFile = new File(String.format(AbsFileer.SUB_PATH, record.filePath, i));
if (partFile.exists()) {
partFile.delete();
}
}
}
if (file.exists()) {
file.delete();
}
} }
if (record != null) { if (record != null) {

@ -43,42 +43,44 @@ public class FileUtil {
* *
* @param targetPath 目标文件 * @param targetPath 目标文件
* @param subPaths 碎片文件路径 * @param subPaths 碎片文件路径
* @return {@code true} 合并成功{@code false}合并失败
*/ */
public static void mergeFile(String targetPath, List<String> subPaths) { public static boolean mergeFile(String targetPath, List<String> subPaths) {
File file = new File(targetPath); File file = new File(targetPath);
FileOutputStream fos = null; FileOutputStream fos = null;
FileChannel foc = null; FileChannel foc = null;
try { try {
if (!file.exists()) { if (!file.exists()) {
file.createNewFile(); file.createNewFile();
fos = new FileOutputStream(targetPath); }
foc = fos.getChannel(); fos = new FileOutputStream(targetPath);
List<FileInputStream> streams = new LinkedList<>(); foc = fos.getChannel();
for (String subPath : subPaths) { List<FileInputStream> streams = new LinkedList<>();
File f = new File(subPath); for (String subPath : subPaths) {
if (!f.exists()) { File f = new File(subPath);
ALog.d(TAG, String.format("合并文件失败,文件【%s】不存在", subPath)); if (!f.exists()) {
for (FileInputStream fis : streams) { ALog.d(TAG, String.format("合并文件失败,文件【%s】不存在", subPath));
fis.close(); for (FileInputStream fis : streams) {
} fis.close();
streams.clear();
return;
} }
streams.add(new FileInputStream(subPath)); streams.clear();
}
Enumeration<FileInputStream> en = Collections.enumeration(streams); return false;
SequenceInputStream sis = new SequenceInputStream(en);
ReadableByteChannel fic = Channels.newChannel(sis);
ByteBuffer bf = ByteBuffer.allocate(8196);
while (fic.read(bf) != -1) {
bf.flip();
foc.write(bf);
bf.compact();
} }
fic.close(); streams.add(new FileInputStream(subPath));
sis.close();
} }
Enumeration<FileInputStream> en = Collections.enumeration(streams);
SequenceInputStream sis = new SequenceInputStream(en);
ReadableByteChannel fic = Channels.newChannel(sis);
ByteBuffer bf = ByteBuffer.allocate(8196);
while (fic.read(bf) != -1) {
bf.flip();
foc.write(bf);
bf.compact();
}
fic.close();
sis.close();
return true;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
@ -93,6 +95,7 @@ public class FileUtil {
e.printStackTrace(); e.printStackTrace();
} }
} }
return false;
} }
/** /**

@ -11,6 +11,15 @@
<!--注意,修改该配置文件中的属性会覆盖代码中所设置的属性--> <!--注意,修改该配置文件中的属性会覆盖代码中所设置的属性-->
<download> <download>
<!--
多线程下载是否使用块下载模式,{@code true}使用,{@code false}不使用
注意:
1、使用分块模式,在读写性能底下的手机上,合并文件需要的时间会更加长;
2、优点是使用多线程的块下载,初始化时,文件初始化时将不会预占用对应长度的空间;
3、只对新的多线程下载任务有效
4、只对多线程的任务有效
-->
<useBlock value="true"/>
<!--断网的时候是否重试,true:断网也重试;false:断网不重试,直接走失败的回调--> <!--断网的时候是否重试,true:断网也重试;false:断网不重试,直接走失败的回调-->
<notNetRetry value="true"/> <notNetRetry value="true"/>
@ -22,7 +31,7 @@
3、从3.4.1开始,如果线程数为1,文件初始化时将不再预占用对应长度的空间,下载多少byte,则占多大的空间; 3、从3.4.1开始,如果线程数为1,文件初始化时将不再预占用对应长度的空间,下载多少byte,则占多大的空间;
对于采用多线程的任务或旧任务,依然采用原来的文件空间占用方式; 对于采用多线程的任务或旧任务,依然采用原来的文件空间占用方式;
--> -->
<threadNum value="1"/> <threadNum value="3"/>
<!--设置下载队列最大任务数, 默认为2--> <!--设置下载队列最大任务数, 默认为2-->
<maxTaskNum value="1"/> <maxTaskNum value="1"/>

@ -18,10 +18,10 @@ public class AnyRunActivity extends BaseActivity<ActivityTestBinding> {
AnyRunnModule module; AnyRunnModule module;
String[] urls; String[] urls;
int index = 0; int index = 0;
//String URL = "http://static.gaoshouyou.com/d/12/0d/7f120f50c80d2e7b8c4ba24ece4f9cdd.apk"; String URL = "http://static.gaoshouyou.com/d/12/0d/7f120f50c80d2e7b8c4ba24ece4f9cdd.apk";
//String URL = "http://58.213.157.242:8081/sims_file/rest/v1/file/mshd_touchscreen_ms/guideFile/41c33556-dc4a-4d78-bb76-b9f627f94448.mp4/%E5%85%AB%E5%8D%A6%E6%B4%B2%E5%8D%97%E4%BA%AC%E5%86%9C%E4%B8%9A%E5%98%89%E5%B9%B4%E5%8D%8E0511.mp4"; //String URL = "http://58.213.157.242:8081/sims_file/rest/v1/file/mshd_touchscreen_ms/guideFile/41c33556-dc4a-4d78-bb76-b9f627f94448.mp4/%E5%85%AB%E5%8D%A6%E6%B4%B2%E5%8D%97%E4%BA%AC%E5%86%9C%E4%B8%9A%E5%98%89%E5%B9%B4%E5%8D%8E0511.mp4";
//String URL = "http://d1.showself.com/download/showself_android-s236279_release.apk"; //String URL = "http://d1.showself.com/download/showself_android-s236279_release.apk";
String URL = "http://static.gaoshouyou.com/d/22/94/822260b849944492caadd2983f9bb624.apk"; //String URL = "http://static.gaoshouyou.com/d/22/94/822260b849944492caadd2983f9bb624.apk";
//private final String URL = "ftp://192.168.29.140:21/download/AriaPrj.rar"; //private final String URL = "ftp://192.168.29.140:21/download/AriaPrj.rar";
//String URL = "https://dl.genymotion.com/releases/genymotion-2.12.1/genymotion-2.12.1-vbox.exe"; //String URL = "https://dl.genymotion.com/releases/genymotion-2.12.1/genymotion-2.12.1-vbox.exe";
//String URL = "ftp://192.168.29.140:21/download/SDK_Demo-release.apk"; //String URL = "ftp://192.168.29.140:21/download/SDK_Demo-release.apk";

@ -65,11 +65,11 @@ public class AnyRunnModule {
void start(String url) { void start(String url) {
mUrl = url; mUrl = url;
String path = Environment.getExternalStorageDirectory().getPath() + "/abcds.exe"; String path = Environment.getExternalStorageDirectory().getPath() + "/abcds.apk";
File file = new File(path); //File file = new File(path);
if (file.exists()) { //if (file.exists()) {
file.delete(); // file.delete();
} //}
Aria.download(this) Aria.download(this)
.load(url) .load(url)
//.addHeader("Accept-Encoding", "gzip") //.addHeader("Accept-Encoding", "gzip")

@ -379,6 +379,26 @@ class Configuration {
*/ */
int maxSpeed = 0; int maxSpeed = 0;
/**
* 多线程下载是否使用块下载模式{@code true}使用{@code false}不使用
* 注意
* 1使用分块模式在读写性能底下的手机上合并文件需要的时间会更加长
* 2优点是使用多线程的块下载初始化时文件初始化时将不会预占用对应长度的空间
* 3只对新的多线程下载任务有效
* 4只对多线程的任务有效
*/
boolean useBlock = false;
public boolean isUseBlock() {
return useBlock;
}
public DownloadConfig setUseBlock(boolean useBlock) {
this.useBlock = useBlock;
saveKey("useBlock", String.valueOf(useBlock));
return this;
}
public DownloadConfig setMaxTaskNum(int maxTaskNum) { public DownloadConfig setMaxTaskNum(int maxTaskNum) {
oldMaxTaskNum = this.maxTaskNum; oldMaxTaskNum = this.maxTaskNum;
this.maxTaskNum = maxTaskNum; this.maxTaskNum = maxTaskNum;
@ -398,9 +418,10 @@ class Configuration {
return this; return this;
} }
public void setThreadNum(int threadNum) { public DownloadConfig setThreadNum(int threadNum) {
this.threadNum = threadNum; this.threadNum = threadNum;
saveKey("threadNum", String.valueOf(threadNum)); saveKey("threadNum", String.valueOf(threadNum));
return this;
} }
public String getCaPath() { public String getCaPath() {

Loading…
Cancel
Save