修复一个非分块模式下导致下载失败的问题 https://github.com/AriaLyy/Aria/issues/571
修复一个服务器端无法创建socket连接,却没有返回码导致客户端卡住的问题 https://github.com/AriaLyy/Aria/issues/569
修复文件删除后,组合任务没有重新下载的问题 https://github.com/AriaLyy/Aria/issues/574
优化缓存队列和执行队列
pull/617/head
laoyuyu 5 years ago
parent 27c889e171
commit 8dc16ce302
  1. 2
      AppFrame/build.gradle
  2. 12
      Aria/src/main/java/com/arialyy/aria/core/queue/AbsTaskQueue.java
  3. 47
      Aria/src/main/java/com/arialyy/aria/core/queue/DTaskQueue.java
  4. 77
      Aria/src/main/java/com/arialyy/aria/core/queue/pool/BaseCachePool.java
  5. 91
      Aria/src/main/java/com/arialyy/aria/core/queue/pool/BaseExecutePool.java
  6. 3
      Aria/src/main/java/com/arialyy/aria/core/queue/pool/DGLoadExecutePool.java
  7. 31
      Aria/src/main/java/com/arialyy/aria/core/queue/pool/DLoadExecutePool.java
  8. 8
      DEV_LOG.md
  9. 4
      FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPClient.java
  10. 3
      FtpComponent/src/main/java/com/arialyy/aria/ftp/BaseFtpThreadTaskAdapter.java
  11. 10
      FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpUFileInfoThread.java
  12. 51
      FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpUThreadTaskAdapter.java
  13. 5
      HttpComponent/src/main/java/com/arialyy/aria/http/HttpFileInfoThread.java
  14. 1
      HttpComponent/src/main/java/com/arialyy/aria/http/download/DGroupLoaderUtil.java
  15. 1
      HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDThreadTaskAdapter.java
  16. 26
      PublicComponent/src/main/java/com/arialyy/aria/core/common/RecordHelper.java
  17. 13
      PublicComponent/src/main/java/com/arialyy/aria/core/group/AbsGroupUtil.java
  18. 5
      PublicComponent/src/main/java/com/arialyy/aria/core/listener/BaseListener.java
  19. 17
      PublicComponent/src/main/java/com/arialyy/aria/core/manager/ThreadTaskManager.java
  20. 2
      PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsTask.java
  21. 5
      PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/ITaskWrapper.java
  22. 55
      PublicComponent/src/main/java/com/arialyy/aria/orm/AbsDelegate.java
  23. 242
      PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateCommon.java
  24. 4
      PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateFind.java
  25. 8
      PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateUpdate.java
  26. 7
      PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateWrapper.java
  27. 35
      PublicComponent/src/main/java/com/arialyy/aria/orm/SqlHelper.java
  28. 283
      PublicComponent/src/main/java/com/arialyy/aria/orm/SqlUtil.java
  29. 1
      PublicComponent/src/main/java/com/arialyy/aria/util/ComponentUtil.java
  30. 35
      README.md
  31. 2
      app/src/main/assets/aria_config.xml
  32. 161
      app/src/main/assets/aria_config_1.xml
  33. 2
      app/src/main/java/com/arialyy/simple/MainActivity.java
  34. 2
      app/src/main/java/com/arialyy/simple/core/download/group/DownloadGroupActivity.java
  35. 3
      app/src/main/java/com/arialyy/simple/modlue/CommonModule.java
  36. 15
      app/src/main/res/drawable/ic_sftp.xml
  37. 8
      app/src/main/res/values/strings.xml
  38. 4
      build.gradle

@ -35,7 +35,7 @@ dependencies {
api 'com.google.code.gson:gson:2.8.2' api 'com.google.code.gson:gson:2.8.2'
api 'io.reactivex:rxandroid:1.2.0' api 'io.reactivex:rxandroid:1.2.0'
api 'io.reactivex:rxjava:1.1.5' api 'io.reactivex:rxjava:1.1.5'
implementation 'com.squareup.okhttp3:okhttp:3.2.0' implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.retrofit2:retrofit:2.1.0' implementation 'com.squareup.retrofit2:retrofit:2.1.0'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0' implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
implementation 'com.squareup.retrofit2:converter-gson:2.1.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0'

@ -95,8 +95,7 @@ public abstract class AbsTaskQueue<TASK extends AbsTask, TASK_WRAPPER extends Ab
* 停止所有任务 * 停止所有任务
*/ */
@Override public void stopAllTask() { @Override public void stopAllTask() {
for (String key : mExecutePool.getAllTask().keySet()) { for (TASK task : mExecutePool.getAllTask()) {
TASK task = mExecutePool.getAllTask().get(key);
if (task != null) { if (task != null) {
int state = task.getState(); int state = task.getState();
if (task.isRunning() || (state != IEntity.STATE_COMPLETE if (task.isRunning() || (state != IEntity.STATE_COMPLETE
@ -106,8 +105,13 @@ public abstract class AbsTaskQueue<TASK extends AbsTask, TASK_WRAPPER extends Ab
} }
} }
for (String key : mCachePool.getAllTask().keySet()) { //for (String key : mCachePool.getAllTask().keySet()) {
TASK task = mCachePool.getAllTask().get(key); // TASK task = mCachePool.getAllTask().get(key);
// if (task != null) {
// task.stop(TaskSchedulerType.TYPE_STOP_NOT_NEXT);
// }
//}
for (TASK task : mCachePool.getAllTask()) {
if (task != null) { if (task != null) {
task.stop(TaskSchedulerType.TYPE_STOP_NOT_NEXT); task.stop(TaskSchedulerType.TYPE_STOP_NOT_NEXT);
} }

@ -26,7 +26,7 @@ import com.arialyy.aria.core.scheduler.TaskSchedulers;
import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.aria.core.task.DownloadTask;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map; import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
@ -68,11 +68,10 @@ public class DTaskQueue extends AbsTaskQueue<DownloadTask, DTaskWrapper> {
*/ */
public void setTaskHighestPriority(DownloadTask task) { public void setTaskHighestPriority(DownloadTask task) {
task.setHighestPriority(true); task.setHighestPriority(true);
Map<String, DownloadTask> exeTasks = mExecutePool.getAllTask(); //Map<String, DownloadTask> exeTasks = mExecutePool.getAllTask();
List<DownloadTask> exeTasks = mExecutePool.getAllTask();
if (exeTasks != null && !exeTasks.isEmpty()) { if (exeTasks != null && !exeTasks.isEmpty()) {
Set<String> keys = exeTasks.keySet(); for (DownloadTask temp : exeTasks) {
for (String key : keys) {
DownloadTask temp = exeTasks.get(key);
if (temp != null && temp.isRunning() && temp.isHighestPriorityTask() && !temp.getKey() if (temp != null && temp.isRunning() && temp.isHighestPriorityTask() && !temp.getKey()
.equals(task.getKey())) { .equals(task.getKey())) {
ALog.e(TAG, "设置最高优先级任务失败,失败原因【任务中已经有最高优先级任务,请等待上一个最高优先级任务完成,或手动暂停该任务】"); ALog.e(TAG, "设置最高优先级任务失败,失败原因【任务中已经有最高优先级任务,请等待上一个最高优先级任务完成,或手动暂停该任务】");
@ -80,28 +79,28 @@ public class DTaskQueue extends AbsTaskQueue<DownloadTask, DTaskWrapper> {
return; return;
} }
} }
} int maxSize = AriaConfig.getInstance().getDConfig().getMaxTaskNum();
int maxSize = AriaConfig.getInstance().getDConfig().getMaxTaskNum(); int currentSize = mExecutePool.size();
int currentSize = mExecutePool.size(); if (currentSize == 0 || currentSize < maxSize) {
if (currentSize == 0 || currentSize < maxSize) { startTask(task);
startTask(task); } else {
} else { Set<DownloadTask> tempTasks = new LinkedHashSet<>();
Set<DownloadTask> tempTasks = new LinkedHashSet<>(); for (int i = 0; i < maxSize; i++) {
for (int i = 0; i < maxSize; i++) { DownloadTask oldTsk = mExecutePool.pollTask();
DownloadTask oldTsk = mExecutePool.pollTask(); if (oldTsk != null && oldTsk.isRunning()) {
if (oldTsk != null && oldTsk.isRunning()) { if (i == maxSize - 1) {
if (i == maxSize - 1) { oldTsk.stop(TaskSchedulerType.TYPE_STOP_AND_WAIT);
oldTsk.stop(TaskSchedulerType.TYPE_STOP_AND_WAIT); mCachePool.putTaskToFirst(oldTsk);
mCachePool.putTaskToFirst(oldTsk); break;
break; }
tempTasks.add(oldTsk);
} }
tempTasks.add(oldTsk);
} }
} startTask(task);
startTask(task);
for (DownloadTask temp : tempTasks) { for (DownloadTask temp : tempTasks) {
mExecutePool.putTask(temp); mExecutePool.putTask(temp);
}
} }
} }
} }

@ -20,65 +20,43 @@ import android.text.TextUtils;
import com.arialyy.aria.core.task.AbsTask; import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import java.util.LinkedHashSet; import java.util.ArrayList;
import java.util.Map; import java.util.Deque;
import java.util.Set; import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/** /**
* Created by lyy on 2016/8/14. 任务缓存池所有下载任务最先缓存在这个池中 * Created by lyy on 2016/8/14. 任务缓存池所有下载任务最先缓存在这个池中
*/ */
public class BaseCachePool<TASK extends AbsTask> implements IPool<TASK> { public class BaseCachePool<TASK extends AbsTask> implements IPool<TASK> {
private static final String TAG = "BaseCachePool"; private final String TAG = CommonUtil.getClassName(this);
private static final int MAX_NUM = Integer.MAX_VALUE; //最大下载任务数 private static final int MAX_NUM = Integer.MAX_VALUE; //最大下载任务数
private static final long TIME_OUT = 1000;
private static final Object LOCK = new Object(); private static final Object LOCK = new Object();
private Map<String, TASK> mCacheMap; private Deque<TASK> mCacheQueue;
private LinkedBlockingQueue<TASK> mCacheQueue;
BaseCachePool() { BaseCachePool() {
mCacheQueue = new LinkedBlockingQueue<>(MAX_NUM); mCacheQueue = new LinkedBlockingDeque<>(MAX_NUM);
mCacheMap = new ConcurrentHashMap<>();
} }
/** /**
* 获取被缓存的任务 * 获取被缓存的任务
*/ */
public Map<String, TASK> getAllTask() { public List<TASK> getAllTask() {
return mCacheMap; return new ArrayList<>(mCacheQueue);
} }
/** /**
* 清除所有缓存的任务 * 清除所有缓存的任务
*/ */
public void clear() { public void clear() {
for (String key : mCacheMap.keySet()) { mCacheQueue.clear();
TASK task = mCacheMap.get(key);
mCacheQueue.remove(task);
mCacheMap.remove(key);
}
} }
/** /**
* 将任务放在队首 * 将任务放在队首
*/ */
public boolean putTaskToFirst(TASK task) { public boolean putTaskToFirst(TASK task) {
if (mCacheQueue.isEmpty()) { return mCacheQueue.offerFirst(task);
return putTask(task);
} else {
Set<TASK> temps = new LinkedHashSet<>();
temps.add(task);
for (int i = 0, len = size(); i < len; i++) {
TASK temp = pollTask();
temps.add(temp);
}
for (TASK t : temps) {
putTask(t);
}
return true;
}
} }
@Override public boolean putTask(TASK task) { @Override public boolean putTask(TASK task) {
@ -87,16 +65,12 @@ public class BaseCachePool<TASK extends AbsTask> implements IPool<TASK> {
ALog.e(TAG, "任务不能为空!!"); ALog.e(TAG, "任务不能为空!!");
return false; return false;
} }
String key = task.getKey();
if (mCacheQueue.contains(task)) { if (mCacheQueue.contains(task)) {
ALog.w(TAG, "任务【" + task.getTaskName() + "】进入缓存队列失败,原因:已经在缓存队列中"); ALog.w(TAG, "任务【" + task.getTaskName() + "】进入缓存队列失败,原因:已经在缓存队列中");
return false; return false;
} else { } else {
boolean s = mCacheQueue.offer(task); boolean s = mCacheQueue.offer(task);
ALog.d(TAG, "任务【" + task.getTaskName() + "】进入缓存队列" + (s ? "成功" : "失败")); ALog.d(TAG, "任务【" + task.getTaskName() + "】进入缓存队列" + (s ? "成功" : "失败"));
if (s) {
mCacheMap.put(CommonUtil.keyToHashKey(key), task);
}
return s; return s;
} }
} }
@ -104,19 +78,8 @@ public class BaseCachePool<TASK extends AbsTask> implements IPool<TASK> {
@Override public TASK pollTask() { @Override public TASK pollTask() {
synchronized (LOCK) { synchronized (LOCK) {
try { return mCacheQueue.pollFirst();
TASK task;
task = mCacheQueue.poll(TIME_OUT, TimeUnit.MICROSECONDS);
if (task != null) {
String url = task.getKey();
mCacheMap.remove(CommonUtil.keyToHashKey(url));
}
return task;
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
return null;
} }
@Override public TASK getTask(String key) { @Override public TASK getTask(String key) {
@ -125,12 +88,17 @@ public class BaseCachePool<TASK extends AbsTask> implements IPool<TASK> {
ALog.e(TAG, "key 为null"); ALog.e(TAG, "key 为null");
return null; return null;
} }
return mCacheMap.get(CommonUtil.keyToHashKey(key)); for (TASK task : mCacheQueue) {
if (task.getKey().equals(key)) {
return task;
}
}
} }
return null;
} }
@Override public boolean taskExits(String key) { @Override public boolean taskExits(String key) {
return mCacheMap.containsKey(CommonUtil.keyToHashKey(key)); return getTask(key) != null;
} }
@Override public boolean removeTask(TASK task) { @Override public boolean removeTask(TASK task) {
@ -139,8 +107,6 @@ public class BaseCachePool<TASK extends AbsTask> implements IPool<TASK> {
ALog.e(TAG, "任务不能为空"); ALog.e(TAG, "任务不能为空");
return false; return false;
} else { } else {
String key = CommonUtil.keyToHashKey(task.getKey());
mCacheMap.remove(key);
return mCacheQueue.remove(task); return mCacheQueue.remove(task);
} }
} }
@ -152,10 +118,7 @@ public class BaseCachePool<TASK extends AbsTask> implements IPool<TASK> {
ALog.e(TAG, "请传入有效的下载链接"); ALog.e(TAG, "请传入有效的下载链接");
return false; return false;
} }
String temp = CommonUtil.keyToHashKey(key); return mCacheQueue.remove(getTask(key));
TASK task = mCacheMap.get(temp);
mCacheMap.remove(temp);
return mCacheQueue.remove(task);
} }
} }

@ -21,26 +21,23 @@ import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.task.AbsTask; import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import java.util.Map; import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue; import java.util.Deque;
import java.util.concurrent.ConcurrentHashMap; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.LinkedBlockingDeque;
/** /**
* Created by lyy on 2016/8/15. 任务执行池所有当前下载任务都该任务池中默认下载大小为2 * Created by lyy on 2016/8/15. 任务执行池所有当前下载任务都该任务池中默认下载大小为2
*/ */
public class BaseExecutePool<TASK extends AbsTask> implements IPool<TASK> { public class BaseExecutePool<TASK extends AbsTask> implements IPool<TASK> {
private final String TAG = "BaseExecutePool"; private final String TAG = CommonUtil.getClassName(this);
private static final Object LOCK = new Object(); private static final Object LOCK = new Object();
final long TIME_OUT = 1000; Deque<TASK> mExecuteQueue;
ArrayBlockingQueue<TASK> mExecuteQueue;
Map<String, TASK> mExecuteMap;
int mSize; int mSize;
BaseExecutePool() { BaseExecutePool() {
mSize = getMaxSize(); mSize = getMaxSize();
mExecuteQueue = new ArrayBlockingQueue<>(mSize); mExecuteQueue = new LinkedBlockingDeque<>(mSize);
mExecuteMap = new ConcurrentHashMap<>();
} }
/** /**
@ -55,8 +52,8 @@ public class BaseExecutePool<TASK extends AbsTask> implements IPool<TASK> {
/** /**
* 获取所有正在执行的任务 * 获取所有正在执行的任务
*/ */
public Map<String, TASK> getAllTask() { public List<TASK> getAllTask() {
return mExecuteMap; return new ArrayList<>(mExecuteQueue);
} }
@Override public boolean putTask(TASK task) { @Override public boolean putTask(TASK task) {
@ -88,17 +85,13 @@ public class BaseExecutePool<TASK extends AbsTask> implements IPool<TASK> {
*/ */
public void setMaxNum(int maxNum) { public void setMaxNum(int maxNum) {
synchronized (LOCK) { synchronized (LOCK) {
try { Deque<TASK> temp = new LinkedBlockingDeque<>(maxNum);
ArrayBlockingQueue<TASK> temp = new ArrayBlockingQueue<>(maxNum); TASK task;
TASK task; while ((task = mExecuteQueue.poll()) != null) {
while ((task = mExecuteQueue.poll(TIME_OUT, TimeUnit.MICROSECONDS)) != null) { temp.offer(task);
temp.offer(task);
}
mExecuteQueue = temp;
mSize = maxNum;
} catch (InterruptedException e) {
e.printStackTrace();
} }
mExecuteQueue = temp;
mSize = maxNum;
} }
} }
@ -109,12 +102,8 @@ public class BaseExecutePool<TASK extends AbsTask> implements IPool<TASK> {
*/ */
boolean putNewTask(TASK newTask) { boolean putNewTask(TASK newTask) {
synchronized (LOCK) { synchronized (LOCK) {
String url = newTask.getKey();
boolean s = mExecuteQueue.offer(newTask); boolean s = mExecuteQueue.offer(newTask);
ALog.d(TAG, "任务【" + newTask.getTaskName() + "】进入执行队列" + (s ? "成功" : "失败")); ALog.d(TAG, "任务【" + newTask.getTaskName() + "】进入执行队列" + (s ? "成功" : "失败"));
if (s) {
mExecuteMap.put(CommonUtil.keyToHashKey(url), newTask);
}
return s; return s;
} }
} }
@ -124,37 +113,19 @@ public class BaseExecutePool<TASK extends AbsTask> implements IPool<TASK> {
*/ */
boolean pollFirstTask() { boolean pollFirstTask() {
synchronized (LOCK) { synchronized (LOCK) {
try { TASK oldTask = mExecuteQueue.pollFirst();
TASK oldTask = mExecuteQueue.poll(TIME_OUT, TimeUnit.MICROSECONDS); if (oldTask == null) {
if (oldTask == null) { ALog.w(TAG, "移除任务失败,原因:任务为null");
ALog.w(TAG, "移除任务失败,原因:任务为null");
return false;
}
oldTask.stop();
String key = CommonUtil.keyToHashKey(oldTask.getKey());
mExecuteMap.remove(key);
} catch (InterruptedException e) {
e.printStackTrace();
return false; return false;
} }
oldTask.stop();
return true; return true;
} }
} }
@Override public TASK pollTask() { @Override public TASK pollTask() {
synchronized (LOCK) { synchronized (LOCK) {
try { return mExecuteQueue.poll();
TASK task;
task = mExecuteQueue.poll(TIME_OUT, TimeUnit.MICROSECONDS);
if (task != null) {
String url = task.getKey();
mExecuteMap.remove(CommonUtil.keyToHashKey(url));
}
return task;
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
} }
} }
@ -164,12 +135,18 @@ public class BaseExecutePool<TASK extends AbsTask> implements IPool<TASK> {
ALog.e(TAG, "key为null"); ALog.e(TAG, "key为null");
return null; return null;
} }
return mExecuteMap.get(CommonUtil.keyToHashKey(key)); for (TASK task : mExecuteQueue) {
if (task.getKey().equals(key)) {
return task;
}
}
return null;
} }
} }
@Override public boolean taskExits(String key) { @Override public boolean taskExits(String key) {
return mExecuteMap.containsKey(CommonUtil.keyToHashKey(key)); return getTask(key) != null;
} }
@Override public boolean removeTask(TASK task) { @Override public boolean removeTask(TASK task) {
@ -189,16 +166,8 @@ public class BaseExecutePool<TASK extends AbsTask> implements IPool<TASK> {
ALog.e(TAG, "key 为null"); ALog.e(TAG, "key 为null");
return false; return false;
} }
String convertKey = CommonUtil.keyToHashKey(key);
TASK task = mExecuteMap.get(convertKey); return mExecuteQueue.remove(getTask(key));
final int oldQueueSize = mExecuteQueue.size();
boolean isSuccess = mExecuteQueue.remove(task);
final int newQueueSize = mExecuteQueue.size();
if (isSuccess && newQueueSize != oldQueueSize) {
mExecuteMap.remove(convertKey);
return true;
}
return false;
} }
} }

@ -18,13 +18,14 @@ package com.arialyy.aria.core.queue.pool;
import com.arialyy.aria.core.AriaConfig; import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.task.AbsTask; import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.util.CommonUtil;
/** /**
* Created by AriaL on 2017/6/29. * Created by AriaL on 2017/6/29.
* 单个下载任务的执行池 * 单个下载任务的执行池
*/ */
class DGLoadExecutePool<TASK extends AbsTask> extends DLoadExecutePool<TASK> { class DGLoadExecutePool<TASK extends AbsTask> extends DLoadExecutePool<TASK> {
private final String TAG = "DGLoadExecutePool"; private final String TAG = CommonUtil.getClassName(this);
@Override protected int getMaxSize() { @Override protected int getMaxSize() {
return AriaConfig.getInstance().getDGConfig().getMaxTaskNum(); return AriaConfig.getInstance().getDGConfig().getMaxTaskNum();

@ -18,9 +18,6 @@ package com.arialyy.aria.core.queue.pool;
import com.arialyy.aria.core.AriaConfig; import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.core.task.AbsTask; import com.arialyy.aria.core.task.AbsTask;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/** /**
* Created by AriaL on 2017/6/29. * Created by AriaL on 2017/6/29.
@ -45,9 +42,10 @@ class DLoadExecutePool<TASK extends AbsTask> extends BaseExecutePool<TASK> {
return false; return false;
} else { } else {
if (mExecuteQueue.size() >= mSize) { if (mExecuteQueue.size() >= mSize) {
Set<String> keys = mExecuteMap.keySet(); for (TASK temp : mExecuteQueue) {
for (String key : keys) { if (temp.isHighestPriorityTask()) {
if (mExecuteMap.get(key).isHighestPriorityTask()) return false; return false;
}
} }
if (pollFirstTask()) { if (pollFirstTask()) {
return putNewTask(task); return putNewTask(task);
@ -61,22 +59,15 @@ class DLoadExecutePool<TASK extends AbsTask> extends BaseExecutePool<TASK> {
} }
@Override boolean pollFirstTask() { @Override boolean pollFirstTask() {
try { TASK oldTask = mExecuteQueue.pollFirst();
TASK oldTask = mExecuteQueue.poll(TIME_OUT, TimeUnit.MICROSECONDS); if (oldTask == null) {
if (oldTask == null) { ALog.w(TAG, "移除任务失败,错误原因:任务为null");
ALog.w(TAG, "移除任务失败,错误原因:任务为null"); return false;
return false; }
} if (oldTask.isHighestPriorityTask()) {
if (oldTask.isHighestPriorityTask()) {
return false;
}
oldTask.stop();
String key = CommonUtil.keyToHashKey(oldTask.getKey());
mExecuteMap.remove(key);
} catch (InterruptedException e) {
e.printStackTrace();
return false; return false;
} }
oldTask.stop();
return true; return true;
} }
} }

@ -1,4 +1,10 @@
## 开发日志 ## 开发日志
+ v_3.8.1 (2019/12/22)
- 修复一个表创建失败的问题 https://github.com/AriaLyy/Aria/issues/570
- 修复一个非分块模式下导致下载失败的问题 https://github.com/AriaLyy/Aria/issues/571
- 修复一个服务器端无法创建socket连接,却没有返回码导致客户端卡住的问题 https://github.com/AriaLyy/Aria/issues/569
- 修复文件删除后,组合任务没有重新下载的问题 https://github.com/AriaLyy/Aria/issues/574
- 优化缓存队列和执行队列
+ v_3.8 (2019/12/17) + v_3.8 (2019/12/17)
- 移除androidx和support的依赖,现在无论是哪个版本的appcompat包都可以使用本框架 - 移除androidx和support的依赖,现在无论是哪个版本的appcompat包都可以使用本框架
- 修复一个在xml中使用fragment导致的内存泄漏问题 - 修复一个在xml中使用fragment导致的内存泄漏问题
@ -15,7 +21,7 @@
+ v_3.7.10 (2019/12/3) + v_3.7.10 (2019/12/3)
- fix bug https://github.com/AriaLyy/Aria/issues/543#issuecomment-559733124 - fix bug https://github.com/AriaLyy/Aria/issues/543#issuecomment-559733124
- fix bug https://github.com/AriaLyy/Aria/issues/542 - fix bug https://github.com/AriaLyy/Aria/issues/542
- fix bug https://github.com/AriaLyy/Aria/issues/547 - fix bug https://github.com/AriaLyy/Azria/issues/547
- 修复下载失败时,中断重试无效的问题 - 修复下载失败时,中断重试无效的问题
- 增加忽略权限检查的api,`ignoreCheckPermissions()` - 增加忽略权限检查的api,`ignoreCheckPermissions()`
- 增加通用的的忽略文件路径被占用的api,`isIgnoreFilePathOccupy()` - 增加通用的的忽略文件路径被占用的api,`isIgnoreFilePathOccupy()`

@ -3662,7 +3662,6 @@ public class FTPClient extends FTP implements Configurable {
private long time = System.currentTimeMillis(); private long time = System.currentTimeMillis();
private int notAcked; private int notAcked;
private OnFtpInputStreamListener listener; private OnFtpInputStreamListener listener;
private long lTime = time;
CSL(FTPClient parent, long idleTime, int maxWait) throws SocketException { CSL(FTPClient parent, long idleTime, int maxWait) throws SocketException {
this(parent, idleTime, maxWait, null); this(parent, idleTime, maxWait, null);
@ -3696,12 +3695,9 @@ public class FTPClient extends FTP implements Configurable {
time = now; time = now;
} }
//if (now - lTime > 100) {
if (listener != null) { if (listener != null) {
listener.onFtpInputStream(parent, totalBytesTransferred, bytesTransferred, streamSize); listener.onFtpInputStream(parent, totalBytesTransferred, bytesTransferred, streamSize);
} }
//lTime = now;
//}
} }
void cleanUp() throws IOException { void cleanUp() throws IOException {

@ -117,7 +117,8 @@ public abstract class BaseFtpThreadTaskAdapter extends AbsThreadTaskAdapter {
} }
} }
client.setControlEncoding(charSet); client.setControlEncoding(charSet);
client.setDataTimeout(getTaskConfig().getIOTimeOut()); //client.setDataTimeout(getTaskConfig().getIOTimeOut());
client.setDataTimeout(1000);
client.setConnectTimeout(getTaskConfig().getConnectTimeOut()); client.setConnectTimeout(getTaskConfig().getConnectTimeOut());
if (mTaskOption.getConnMode() == FtpConnectionMode.DATA_CONNECTION_MODE_ACTIVITY) { if (mTaskOption.getConnMode() == FtpConnectionMode.DATA_CONNECTION_MODE_ACTIVITY) {
client.enterLocalActiveMode(); client.enterLocalActiveMode();

@ -42,10 +42,6 @@ class FtpUFileInfoThread extends AbsFtpInfoThread<UploadEntity, UTaskWrapper> {
static final int CODE_COMPLETE = 0xab1; static final int CODE_COMPLETE = 0xab1;
private boolean isComplete = false; private boolean isComplete = false;
private String remotePath; private String remotePath;
/**
* true 使用拦截器false 不使用拦截器
*/
private boolean useInterceptor = false;
FtpUFileInfoThread(UTaskWrapper taskEntity, OnFileInfoCallback callback) { FtpUFileInfoThread(UTaskWrapper taskEntity, OnFileInfoCallback callback) {
super(taskEntity, callback); super(taskEntity, callback);
@ -65,7 +61,9 @@ class FtpUFileInfoThread extends AbsFtpInfoThread<UploadEntity, UTaskWrapper> {
try { try {
IFtpUploadInterceptor interceptor = mTaskOption.getUploadInterceptor(); IFtpUploadInterceptor interceptor = mTaskOption.getUploadInterceptor();
if (interceptor != null) { if (interceptor != null) {
useInterceptor = true; /**
* true 使用拦截器false 不使用拦截器
*/
List<String> files = new ArrayList<>(); List<String> files = new ArrayList<>();
for (FTPFile ftpFile : ftpFiles) { for (FTPFile ftpFile : ftpFiles) {
if (ftpFile.isDirectory()) { if (ftpFile.isDirectory()) {
@ -118,7 +116,7 @@ class FtpUFileInfoThread extends AbsFtpInfoThread<UploadEntity, UTaskWrapper> {
*/ */
@Override protected void handleFile(String remotePath, FTPFile ftpFile) { @Override protected void handleFile(String remotePath, FTPFile ftpFile) {
super.handleFile(remotePath, ftpFile); super.handleFile(remotePath, ftpFile);
if (ftpFile != null && !useInterceptor) { if (ftpFile != null) {
//远程文件已完成 //远程文件已完成
if (ftpFile.getSize() == mEntity.getFileSize()) { if (ftpFile.getSize() == mEntity.getFileSize()) {
isComplete = true; isComplete = true;

@ -28,6 +28,8 @@ import com.arialyy.aria.util.BufferedRandomAccessFile;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/** /**
* Created by Aria.Lao on 2017/7/28. D_FTP 单线程上传任务需要FTP 服务器给用户打开append和write的权限 * Created by Aria.Lao on 2017/7/28. D_FTP 单线程上传任务需要FTP 服务器给用户打开append和write的权限
@ -35,13 +37,16 @@ import java.io.UnsupportedEncodingException;
class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter { class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
private String dir, remotePath; private String dir, remotePath;
private boolean storeFail = false; private boolean storeFail = false;
private ScheduledThreadPoolExecutor timer;
private FTPClient client = null;
private boolean isTimeOut = true;
private FtpFISAdapter fa;
FtpUThreadTaskAdapter(SubThreadConfig config) { FtpUThreadTaskAdapter(SubThreadConfig config) {
super(config); super(config);
} }
@Override protected void handlerThreadTask() { @Override protected void handlerThreadTask() {
FTPClient client = null;
BufferedRandomAccessFile file = null; BufferedRandomAccessFile file = null;
try { try {
ALog.d(TAG, ALog.d(TAG,
@ -71,7 +76,7 @@ class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
if (getThreadRecord().startLocation > 0) { if (getThreadRecord().startLocation > 0) {
file.seek(getThreadRecord().startLocation); file.seek(getThreadRecord().startLocation);
} }
boolean complete = upload(client, file); boolean complete = upload(file);
if (!complete || getThreadTask().isBreak()) { if (!complete || getThreadTask().isBreak()) {
return; return;
} }
@ -94,6 +99,7 @@ class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
e.printStackTrace(); e.printStackTrace();
} }
closeClient(client); closeClient(client);
closeTimer();
} }
} }
@ -112,15 +118,46 @@ class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
String.format("%s/%s", mTaskOption.getUrlEntity().remotePath, fileName)); String.format("%s/%s", mTaskOption.getUrlEntity().remotePath, fileName));
} }
/**
* 启动监听定时器当网络断开时如果该任务的FTP服务器的传输线程没有断开当客户端重新连接时客户端将无法发送数据到服务端
* 每隔10s检查一次
*/
private void startTimer() {
timer = new ScheduledThreadPoolExecutor(1);
timer.scheduleWithFixedDelay(new Runnable() {
@Override public void run() {
try {
if (isTimeOut) {
fail(new AriaIOException(TAG, "socket连接失败,该问题一般出现于网络断开,客户端重新连接,"
+ "但是服务器端无法创建socket缺没有返回错误码的情况。"), false);
}
if (fa != null) {
fa.close();
}
isTimeOut = true;
} catch (IOException e) {
e.printStackTrace();
}
}
}, 10, 10, TimeUnit.SECONDS);
}
private void closeTimer() {
if (timer != null && !timer.isShutdown()) {
timer.shutdown();
}
}
/** /**
* 上传 * 上传
* *
* @return {@code true}上传成功{@code false} 上传失败 * @return {@code true}上传成功{@code false} 上传失败
*/ */
private boolean upload(final FTPClient client, final BufferedRandomAccessFile bis) private boolean upload(final BufferedRandomAccessFile bis)
throws IOException { throws IOException {
final FtpFISAdapter fa = new FtpFISAdapter(bis); fa = new FtpFISAdapter(bis);
storeFail = false; storeFail = false;
startTimer();
try { try {
ALog.d(TAG, String.format("remotePath: %s", remotePath)); ALog.d(TAG, String.format("remotePath: %s", remotePath));
client.storeFile(remotePath, fa, new OnFtpInputStreamListener() { client.storeFile(remotePath, fa, new OnFtpInputStreamListener() {
@ -129,6 +166,7 @@ class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
@Override public void onFtpInputStream(FTPClient client, long totalBytesTransferred, @Override public void onFtpInputStream(FTPClient client, long totalBytesTransferred,
int bytesTransferred, long streamSize) { int bytesTransferred, long streamSize) {
try { try {
isTimeOut = false;
if (getThreadTask().isBreak() && !isStoped) { if (getThreadTask().isBreak() && !isStoped) {
isStoped = true; isStoped = true;
client.abor(); client.abor();
@ -150,9 +188,10 @@ class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
} }
}); });
} catch (IOException e) { } catch (IOException e) {
String msg = String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", client.getReplyCode(), String msg = String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", client.getReply(),
client.getReplyString(), getEntity().getFilePath()); client.getReplyString(), getEntity().getFilePath());
closeClient(client); closeClient(client);
e.printStackTrace();
if (e.getMessage().contains("AriaIOException caught while copying")) { if (e.getMessage().contains("AriaIOException caught while copying")) {
e.printStackTrace(); e.printStackTrace();
} else { } else {
@ -163,7 +202,7 @@ class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter {
if (storeFail) { if (storeFail) {
return false; return false;
} }
int reply = client.getReplyCode(); int reply = client.getReply();
if (!FTPReply.isPositiveCompletion(reply)) { if (!FTPReply.isPositiveCompletion(reply)) {
if (reply != FTPReply.TRANSFER_ABORTED) { if (reply != FTPReply.TRANSFER_ABORTED) {
fail(new AriaIOException(TAG, fail(new AriaIOException(TAG,

@ -89,6 +89,11 @@ public class HttpFileInfoThread implements Runnable {
true); true);
} finally { } finally {
if (conn != null) { if (conn != null) {
try {
conn.getInputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
conn.disconnect(); conn.disconnect();
} }
} }

@ -76,6 +76,7 @@ public class DGroupLoaderUtil extends AbsGroupUtil {
getState().setSubSize(getWrapper().getSubTaskWrapper().size()); getState().setSubSize(getWrapper().getSubTaskWrapper().size());
if (getState().getCompleteNum() == getState().getSubSize()) { if (getState().getCompleteNum() == getState().getSubSize()) {
mListener.onComplete(); mListener.onComplete();
return false;
} else { } else {
// 处理组合任务大小未知的情况 // 处理组合任务大小未知的情况
if (getWrapper().isUnknownSize() && getWrapper().getEntity().getFileSize() < 1) { if (getWrapper().isUnknownSize() && getWrapper().getEntity().getFileSize() < 1) {

@ -143,6 +143,7 @@ final class HttpDThreadTaskAdapter extends BaseHttpThreadTaskAdapter {
is.close(); is.close();
} }
if (conn != null) { if (conn != null) {
conn.getInputStream().close();
conn.disconnect(); conn.disconnect();
} }
} catch (IOException e) { } catch (IOException e) {

@ -51,15 +51,12 @@ public class RecordHelper {
File temp = new File(mTaskRecord.filePath); File temp = new File(mTaskRecord.filePath);
boolean fileExists = false; boolean fileExists = false;
if (!temp.exists()) { if (!temp.exists()) {
BufferedRandomAccessFile tempFile; createPlaceHolderFile(temp);
try {
tempFile = new BufferedRandomAccessFile(temp, "rw");
tempFile.setLength(mWrapper.getEntity().getFileSize());
} catch (IOException e) {
e.printStackTrace();
}
} else { } else {
if (temp.length() != mWrapper.getEntity().getFileSize()) {
FileUtil.deleteFile(temp);
createPlaceHolderFile(temp);
}
fileExists = true; fileExists = true;
} }
// 处理文件被删除的情况 // 处理文件被删除的情况
@ -81,6 +78,19 @@ public class RecordHelper {
mWrapper.setNewTask(false); mWrapper.setNewTask(false);
} }
/**
* 创建非分块的占位文件
*/
private void createPlaceHolderFile(File temp) {
BufferedRandomAccessFile tempFile;
try {
tempFile = new BufferedRandomAccessFile(temp, "rw");
tempFile.setLength(mWrapper.getEntity().getFileSize());
} catch (IOException e) {
e.printStackTrace();
}
}
/** /**
* 处理分块任务的记录分块文件blockFileLen长度必须需要小于等于线程区间threadRectLen的长度 * 处理分块任务的记录分块文件blockFileLen长度必须需要小于等于线程区间threadRectLen的长度
*/ */

@ -27,6 +27,7 @@ import com.arialyy.aria.core.listener.IEventListener;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper; import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
@ -83,14 +84,24 @@ public abstract class AbsGroupUtil implements IUtil, Runnable {
private void initState() { private void initState() {
mState = new GroupRunState(getWrapper().getKey(), mListener, mSubQueue); mState = new GroupRunState(getWrapper().getKey(), mListener, mSubQueue);
for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) { for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) {
if (wrapper.getEntity().getState() == IEntity.STATE_COMPLETE) { File subFile = new File(wrapper.getEntity().getFilePath());
if (wrapper.getEntity().getState() == IEntity.STATE_COMPLETE
&& subFile.exists()
&& subFile.length() == wrapper.getEntity().getFileSize()) {
mState.updateCompleteNum(); mState.updateCompleteNum();
mCurrentLocation += wrapper.getEntity().getFileSize(); mCurrentLocation += wrapper.getEntity().getFileSize();
} else { } else {
if (!subFile.exists()) {
wrapper.getEntity().setCurrentProgress(0);
}
wrapper.getEntity().setState(IEntity.STATE_POST_PRE);
mCache.put(wrapper.getKey(), wrapper); mCache.put(wrapper.getKey(), wrapper);
mCurrentLocation += wrapper.getEntity().getCurrentProgress(); mCurrentLocation += wrapper.getEntity().getCurrentProgress();
} }
} }
if (getWrapper().getSubTaskWrapper().size() != mState.getCompleteNum()) {
getWrapper().setState(IEntity.STATE_POST_PRE);
}
mState.updateProgress(mCurrentLocation); mState.updateProgress(mCurrentLocation);
mScheduler = new Handler(Looper.getMainLooper(), SimpleSchedulers.newInstance(mState)); mScheduler = new Handler(Looper.getMainLooper(), SimpleSchedulers.newInstance(mState));
} }

@ -122,8 +122,9 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
mTask.putExpand(AbsTask.ERROR_INFO_KEY, e); mTask.putExpand(AbsTask.ERROR_INFO_KEY, e);
sendInState2Target(ISchedulers.FAIL); sendInState2Target(ISchedulers.FAIL);
if (e != null) { if (e != null) {
e.printStackTrace(); String error = ALog.getExceptionString(e);
ErrorHelp.saveError(e.getTag(), "", ALog.getExceptionString(e)); ALog.w(TAG, error);
ErrorHelp.saveError(e.getTag(), "", error);
} }
} }

@ -16,8 +16,8 @@
package com.arialyy.aria.core.manager; package com.arialyy.aria.core.manager;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.core.task.IThreadTask; import com.arialyy.aria.core.task.IThreadTask;
import com.arialyy.aria.core.wrapper.AbsTaskWrapper;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import java.util.HashSet; import java.util.HashSet;
@ -25,8 +25,9 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -34,11 +35,12 @@ import java.util.concurrent.locks.ReentrantLock;
* 线程任务管理器 * 线程任务管理器
*/ */
public class ThreadTaskManager { public class ThreadTaskManager {
private final String TAG = CommonUtil.getClassName(this);
private static volatile ThreadTaskManager INSTANCE = null; private static volatile ThreadTaskManager INSTANCE = null;
private final String TAG = "ThreadTaskManager"; private static final int CORE_POOL_NUM = 20;
private ExecutorService mExePool;
private Map<String, Set<FutureContainer>> mThreadTasks = new ConcurrentHashMap<>();
private static final ReentrantLock LOCK = new ReentrantLock(); private static final ReentrantLock LOCK = new ReentrantLock();
private ThreadPoolExecutor mExePool;
private Map<String, Set<FutureContainer>> mThreadTasks = new ConcurrentHashMap<>();
public static synchronized ThreadTaskManager getInstance() { public static synchronized ThreadTaskManager getInstance() {
if (INSTANCE == null) { if (INSTANCE == null) {
@ -48,7 +50,10 @@ public class ThreadTaskManager {
} }
private ThreadTaskManager() { private ThreadTaskManager() {
mExePool = Executors.newCachedThreadPool(); mExePool = new ThreadPoolExecutor(CORE_POOL_NUM, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
mExePool.allowsCoreThreadTimeOut();
} }
/** /**

@ -79,7 +79,7 @@ public abstract class AbsTask<TASK_WRAPPER extends AbsTaskWrapper>
ALog.e(TAG, "key 为空"); ALog.e(TAG, "key 为空");
return; return;
} else if (obj == null) { } else if (obj == null) {
ALog.w(TAG, "扩展数据为空"); ALog.i(TAG, "扩展数据为空");
return; return;
} }
mExpand.put(key, obj); mExpand.put(key, obj);

@ -79,6 +79,11 @@ public interface ITaskWrapper {
*/ */
int U_TCP_PEER = 11; int U_TCP_PEER = 11;
/**
* SFTP 下载
*/
int D_SFTP = 12;
/** /**
* 获取任务类型 * 获取任务类型
* *

@ -17,10 +17,6 @@ package com.arialyy.aria.orm;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.lang.reflect.Field;
import java.net.URLEncoder;
/** /**
* Created by laoyuyu on 2018/3/22. * Created by laoyuyu on 2018/3/22.
@ -28,52 +24,8 @@ import java.net.URLEncoder;
abstract class AbsDelegate { abstract class AbsDelegate {
static final String TAG = "AbsDelegate"; static final String TAG = "AbsDelegate";
/**
* 检查list参数是否合法list只能是{@code List<String>}
*
* @return {@code true} 合法
*/
boolean checkList(Field list) {
Class t = CommonUtil.getListParamType(list);
if (t != null && t == String.class) {
return true;
} else {
ALog.d(TAG, "map参数错误,支持List<String>的参数字段");
return false;
}
}
/**
* 检查map参数是否合法map只能是{@code Map<String, String>}
*
* @return {@code true} 合法
*/
boolean checkMap(Field map) {
Class[] ts = CommonUtil.getMapParamType(map);
if (ts != null
&& ts[0] != null
&& ts[1] != null
&& ts[0] == String.class
&& ts[1] == String.class) {
return true;
} else {
ALog.d(TAG, "map参数错误,支持Map<String,String>的参数字段");
return false;
}
}
void closeCursor(Cursor cursor) { void closeCursor(Cursor cursor) {
synchronized (AbsDelegate.class) { SqlUtil.closeCursor(cursor);
if (cursor != null && !cursor.isClosed()) {
try {
cursor.close();
} catch (android.database.SQLException e) {
e.printStackTrace();
}
}
}
} }
/** /**
@ -82,9 +34,6 @@ abstract class AbsDelegate {
* @return 返回数据库 * @return 返回数据库
*/ */
SQLiteDatabase checkDb(SQLiteDatabase db) { SQLiteDatabase checkDb(SQLiteDatabase db) {
if (db == null || !db.isOpen()) { return SqlUtil.checkDb(db);
db = SqlHelper.getInstance().getDb();
}
return db;
} }
} }

@ -1,242 +0,0 @@
/*
* 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.orm;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import com.arialyy.aria.orm.annotation.Default;
import com.arialyy.aria.orm.annotation.Foreign;
import com.arialyy.aria.orm.annotation.Primary;
import com.arialyy.aria.util.CommonUtil;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Created by laoyuyu on 2018/3/22.
* 通用委托创建表检查字段
*/
class DelegateCommon extends AbsDelegate {
private DelegateCommon() {
}
/**
* 删除指定的表
*/
void dropTable(SQLiteDatabase db, String tableName) {
db = checkDb(db);
String deleteSQL = String.format("DROP TABLE IF EXISTS %s", tableName);
//db.beginTransaction();
db.execSQL(deleteSQL);
//db.setTransactionSuccessful();
//db.endTransaction();
}
/**
* 清空表数据
*/
<T extends DbEntity> void clean(SQLiteDatabase db, Class<T> clazz) {
db = checkDb(db);
String tableName = CommonUtil.getClassName(clazz);
if (tableExists(db, clazz)) {
String sql = "DELETE FROM " + tableName;
db.execSQL(sql);
}
}
/**
* 查找表是否存在
*
* @param clazz 数据库实体
* @return true该数据库实体对应的表存在false不存在
*/
boolean tableExists(SQLiteDatabase db, Class clazz) {
return tableExists(db, CommonUtil.getClassName(clazz));
}
/**
* 查找表是否存在
*
* @param tableName 表名
* @return true该数据库实体对应的表存在false不存在
*/
boolean tableExists(SQLiteDatabase db, String tableName) {
db = checkDb(db);
Cursor cursor = null;
try {
String sql =
String.format("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='%s'",
tableName);
cursor = db.rawQuery(sql, null);
if (cursor != null && cursor.moveToNext()) {
int count = cursor.getInt(0);
if (count > 0) {
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
closeCursor(cursor);
}
return false;
}
/**
* 检查某个字段的值是否存在
*
* @param expression 字段和值"url=xxx"
* @return {@code true}该字段的对应的value已存在
*/
boolean checkDataExist(SQLiteDatabase db, Class clazz, String... expression) {
db = checkDb(db);
if (!CommonUtil.checkSqlExpression(expression)) {
return false;
}
String sql = String.format("SELECT rowid, * FROM %s WHERE %s ", CommonUtil.getClassName(clazz),
expression[0]);
sql = sql.replace("?", "%s");
Object[] params = new String[expression.length - 1];
for (int i = 0, len = params.length; i < len; i++) {
params[i] = String.format("'%s'", SqlUtil.encodeStr(expression[i + 1]));
}
sql = String.format(sql, params);
Cursor cursor = db.rawQuery(sql, null);
final boolean isExist = cursor.getCount() > 0;
closeCursor(cursor);
return isExist;
}
/**
* 创建表
*
* @param clazz 数据库实体
*/
void createTable(SQLiteDatabase db, Class clazz) {
db = checkDb(db);
List<Field> fields = CommonUtil.getAllFields(clazz);
if (fields != null && fields.size() > 0) {
//外键Map,在Sqlite3中foreign修饰的字段必须放在最后
final List<Field> foreignArray = new ArrayList<>();
StringBuilder sb = new StringBuilder();
sb.append("CREATE TABLE IF NOT EXISTS ")
.append(CommonUtil.getClassName(clazz))
.append(" (");
for (Field field : fields) {
field.setAccessible(true);
if (SqlUtil.isIgnore(field)) {
continue;
}
Class<?> type = field.getType();
sb.append(field.getName());
if (type == String.class || type.isEnum()) {
sb.append(" VARCHAR");
} else if (type == int.class || type == Integer.class) {
sb.append(" INTEGER");
} else if (type == float.class || type == Float.class) {
sb.append(" FLOAT");
} else if (type == double.class || type == Double.class) {
sb.append(" DOUBLE");
} else if (type == long.class || type == Long.class) {
sb.append(" BIGINT");
} else if (type == boolean.class || type == Boolean.class) {
sb.append(" BOOLEAN");
} else if (type == java.util.Date.class || type == java.sql.Date.class) {
sb.append(" DATA");
} else if (type == byte.class || type == Byte.class) {
sb.append(" BLOB");
} else if (type == Map.class || type == List.class) {
sb.append(" TEXT");
} else {
continue;
}
if (SqlUtil.isPrimary(field)) {
Primary pk = field.getAnnotation(Primary.class);
sb.append(" PRIMARY KEY");
if (pk.autoincrement() && (type == int.class || type == Integer.class)) {
sb.append(" AUTOINCREMENT");
}
}
if (SqlUtil.isForeign(field)) {
foreignArray.add(field);
}
if (SqlUtil.isNoNull(field)) {
sb.append(" NOT NULL");
}
if (SqlUtil.isDefault(field)) {
Default d = field.getAnnotation(Default.class);
if (!TextUtils.isEmpty(d.value())) {
sb.append(" ERROR ").append("'").append(d.value()).append("'");
}
}
if (SqlUtil.isUnique(field)) {
sb.append(" UNIQUE");
}
sb.append(",");
}
for (Field field : foreignArray) {
Foreign foreign = field.getAnnotation(Foreign.class);
sb.append("FOREIGN KEY (")
.append(field.getName())
.append(") REFERENCES ")
.append(CommonUtil.getClassName(foreign.parent()))
.append("(")
.append(foreign.column())
.append(")");
ActionPolicy update = foreign.onUpdate();
ActionPolicy delete = foreign.onDelete();
if (update != ActionPolicy.NO_ACTION) {
sb.append(" ON UPDATE ").append(update.function);
}
if (delete != ActionPolicy.NO_ACTION) {
sb.append(" ON DELETE ").append(update.function);
}
sb.append(",");
}
String str = sb.toString();
str = str.substring(0, str.length() - 1) + ");";
db.execSQL(str);
}
}
/**
* 通过class 获取该class的表字段
*
* @return 表字段列表
*/
List<String> getColumns(Class<? extends DbEntity> clazz) {
List<String> columns = new ArrayList<>();
List<Field> fields = CommonUtil.getAllFields(clazz);
for (Field field : fields) {
field.setAccessible(true);
if (SqlUtil.isIgnore(field)) {
continue;
}
columns.add(field.getName());
}
return columns;
}
}

@ -136,6 +136,9 @@ class DelegateFind extends AbsDelegate {
Many m = many.getAnnotation(Many.class); Many m = many.getAnnotation(Many.class);
Class parentClazz = Class.forName(one.getType().getName()); Class parentClazz = Class.forName(one.getType().getName());
Class childClazz = Class.forName(CommonUtil.getListParamType(many).getName()); Class childClazz = Class.forName(CommonUtil.getListParamType(many).getName());
// 检查表
SqlUtil.checkTable(db, parentClazz);
SqlUtil.checkTable(db, childClazz);
final String pTableName = parentClazz.getSimpleName(); final String pTableName = parentClazz.getSimpleName();
final String cTableName = childClazz.getSimpleName(); final String cTableName = childClazz.getSimpleName();
List<Field> pColumn = SqlUtil.getAllNotIgnoreField(parentClazz); List<Field> pColumn = SqlUtil.getAllNotIgnoreField(parentClazz);
@ -418,6 +421,7 @@ class DelegateFind extends AbsDelegate {
*/ */
private <T extends DbEntity> List<T> exeNormalDataSql(SQLiteDatabase db, Class<T> clazz, private <T extends DbEntity> List<T> exeNormalDataSql(SQLiteDatabase db, Class<T> clazz,
String sql, String[] selectionArgs) { String sql, String[] selectionArgs) {
SqlUtil.checkTable(db, clazz);
String[] temp = new String[selectionArgs.length]; String[] temp = new String[selectionArgs.length];
int i = 0; int i = 0;
for (String arg : selectionArgs) { for (String arg : selectionArgs) {

@ -38,6 +38,7 @@ class DelegateUpdate extends AbsDelegate {
*/ */
synchronized <T extends DbEntity> void delData(SQLiteDatabase db, Class<T> clazz, synchronized <T extends DbEntity> void delData(SQLiteDatabase db, Class<T> clazz,
String... expression) { String... expression) {
SqlUtil.checkTable(db, clazz);
db = checkDb(db); db = checkDb(db);
if (!CommonUtil.checkSqlExpression(expression)) { if (!CommonUtil.checkSqlExpression(expression)) {
return; return;
@ -57,6 +58,7 @@ class DelegateUpdate extends AbsDelegate {
* 修改某行数据 * 修改某行数据
*/ */
synchronized void updateData(SQLiteDatabase db, DbEntity dbEntity) { synchronized void updateData(SQLiteDatabase db, DbEntity dbEntity) {
SqlUtil.checkTable(db, dbEntity.getClass());
db = checkDb(db); db = checkDb(db);
ContentValues values = createValues(dbEntity); ContentValues values = createValues(dbEntity);
if (values != null) { if (values != null) {
@ -109,6 +111,7 @@ class DelegateUpdate extends AbsDelegate {
if (oldClazz == null || oldClazz != entity.getClass() || table == null) { if (oldClazz == null || oldClazz != entity.getClass() || table == null) {
oldClazz = entity.getClass(); oldClazz = entity.getClass();
table = CommonUtil.getClassName(oldClazz); table = CommonUtil.getClassName(oldClazz);
SqlUtil.checkTable(db, oldClazz);
} }
ContentValues value = createValues(entity); ContentValues value = createValues(entity);
@ -130,6 +133,7 @@ class DelegateUpdate extends AbsDelegate {
* 插入数据 * 插入数据
*/ */
synchronized void insertData(SQLiteDatabase db, DbEntity dbEntity) { synchronized void insertData(SQLiteDatabase db, DbEntity dbEntity) {
SqlUtil.checkTable(db, dbEntity.getClass());
db = checkDb(db); db = checkDb(db);
ContentValues values = createValues(dbEntity); ContentValues values = createValues(dbEntity);
if (values != null) { if (values != null) {
@ -156,9 +160,9 @@ class DelegateUpdate extends AbsDelegate {
} }
String value = null; String value = null;
Type type = field.getType(); Type type = field.getType();
if (type == Map.class && checkMap(field)) { if (type == Map.class && SqlUtil.checkMap(field)) {
value = SqlUtil.map2Str((Map<String, String>) field.get(dbEntity)); value = SqlUtil.map2Str((Map<String, String>) field.get(dbEntity));
} else if (type == List.class && checkList(field)) { } else if (type == List.class && SqlUtil.checkList(field)) {
value = SqlUtil.list2Str(dbEntity, field); value = SqlUtil.list2Str(dbEntity, field);
} else { } else {
Object obj = field.get(dbEntity); Object obj = field.get(dbEntity);

@ -85,15 +85,14 @@ public class DelegateWrapper {
* @return {@code true}该字段的对应的value已存在 * @return {@code true}该字段的对应的value已存在
*/ */
boolean checkDataExist(Class clazz, String... expression) { boolean checkDataExist(Class clazz, String... expression) {
return mDManager.getDelegate(DelegateCommon.class) return SqlUtil.checkDataExist(mDb, clazz, expression);
.checkDataExist(mDb, clazz, expression);
} }
/** /**
* 清空表数据 * 清空表数据
*/ */
<T extends DbEntity> void clean(Class<T> clazz) { <T extends DbEntity> void clean(Class<T> clazz) {
mDManager.getDelegate(DelegateCommon.class).clean(mDb, clazz); SqlUtil.clean(mDb, clazz);
} }
/** /**
@ -193,7 +192,7 @@ public class DelegateWrapper {
* 查找某张表是否存在 * 查找某张表是否存在
*/ */
public boolean tableExists(Class clazz) { public boolean tableExists(Class clazz) {
return mDManager.getDelegate(DelegateCommon.class).tableExists(mDb, clazz); return SqlUtil.tableExists(mDb, clazz);
} }
/** /**

@ -41,13 +41,10 @@ final class SqlHelper extends SQLiteOpenHelper {
private static volatile SqlHelper INSTANCE = null; private static volatile SqlHelper INSTANCE = null;
private Context mContext; private Context mContext;
private DelegateCommon mDelegate;
synchronized static SqlHelper init(Context context) { synchronized static SqlHelper init(Context context) {
if (INSTANCE == null) { if (INSTANCE == null) {
synchronized (SqlHelper.class) { synchronized (SqlHelper.class) {
DelegateCommon delegate = DelegateManager.getInstance().getDelegate(DelegateCommon.class); INSTANCE = new SqlHelper(context.getApplicationContext());
INSTANCE = new SqlHelper(context.getApplicationContext(), delegate);
} }
} }
return INSTANCE; return INSTANCE;
@ -57,11 +54,10 @@ final class SqlHelper extends SQLiteOpenHelper {
return INSTANCE; return INSTANCE;
} }
private SqlHelper(Context context, DelegateCommon delegate) { private SqlHelper(Context context) {
super(DBConfig.SAVE_IN_SDCARD ? new DatabaseContext(context) : context, DBConfig.DB_NAME, null, super(DBConfig.SAVE_IN_SDCARD ? new DatabaseContext(context) : context, DBConfig.DB_NAME, null,
DBConfig.VERSION); DBConfig.VERSION);
mContext = context; mContext = context;
mDelegate = delegate;
} }
@Override public void onOpen(SQLiteDatabase db) { @Override public void onOpen(SQLiteDatabase db) {
@ -82,12 +78,11 @@ final class SqlHelper extends SQLiteOpenHelper {
} }
@Override public void onCreate(SQLiteDatabase db) { @Override public void onCreate(SQLiteDatabase db) {
DelegateCommon delegate = DelegateManager.getInstance().getDelegate(DelegateCommon.class);
Set<String> tables = DBConfig.mapping.keySet(); Set<String> tables = DBConfig.mapping.keySet();
for (String tableName : tables) { for (String tableName : tables) {
Class clazz = DBConfig.mapping.get(tableName); Class clazz = DBConfig.mapping.get(tableName);
if (!delegate.tableExists(db, clazz)) { if (!SqlUtil.tableExists(db, clazz)) {
delegate.createTable(db, clazz); SqlUtil.createTable(db, clazz);
} }
} }
} }
@ -167,13 +162,13 @@ final class SqlHelper extends SQLiteOpenHelper {
Set<String> tables = DBConfig.mapping.keySet(); Set<String> tables = DBConfig.mapping.keySet();
for (String tableName : tables) { for (String tableName : tables) {
Class<? extends DbEntity> clazz = DBConfig.mapping.get(tableName); Class<? extends DbEntity> clazz = DBConfig.mapping.get(tableName);
if (mDelegate.tableExists(db, clazz)) { if (SqlUtil.tableExists(db, clazz)) {
//修改表名为中介表名 //修改表名为中介表名
String alertSql = String.format("ALTER TABLE %s RENAME TO %s_temp", tableName, tableName); String alertSql = String.format("ALTER TABLE %s RENAME TO %s_temp", tableName, tableName);
db.execSQL(alertSql); db.execSQL(alertSql);
//创建新表 //创建新表
mDelegate.createTable(db, clazz); SqlUtil.createTable(db, clazz);
String sql = String.format("SELECT COUNT(*) FROM %s_temp", tableName); String sql = String.format("SELECT COUNT(*) FROM %s_temp", tableName);
Cursor cursor = db.rawQuery(sql, null); Cursor cursor = db.rawQuery(sql, null);
@ -187,7 +182,7 @@ final class SqlHelper extends SQLiteOpenHelper {
db.rawQuery(String.format("PRAGMA table_info(%s_temp)", tableName), null); db.rawQuery(String.format("PRAGMA table_info(%s_temp)", tableName), null);
// 获取新表的所有字段名称 // 获取新表的所有字段名称
List<String> newTabColumns = mDelegate.getColumns(clazz); List<String> newTabColumns = SqlUtil.getColumns(clazz);
// 获取旧表的所有字段名称 // 获取旧表的所有字段名称
List<String> oldTabColumns = new ArrayList<>(); List<String> oldTabColumns = new ArrayList<>();
@ -237,9 +232,9 @@ final class SqlHelper extends SQLiteOpenHelper {
db.execSQL(insertSql); db.execSQL(insertSql);
} }
//删除中介表 //删除中介表
mDelegate.dropTable(db, tableName + "_temp"); SqlUtil.dropTable(db, tableName + "_temp");
} else { } else {
mDelegate.createTable(db, clazz); SqlUtil.createTable(db, clazz);
} }
} }
db.setTransactionSuccessful(); db.setTransactionSuccessful();
@ -281,7 +276,7 @@ final class SqlHelper extends SQLiteOpenHelper {
if (url.startsWith("ftp") || url.startsWith("sftp")) { if (url.startsWith("ftp") || url.startsWith("sftp")) {
type = ITaskWrapper.D_FTP; type = ITaskWrapper.D_FTP;
} else { } else {
if (mDelegate.tableExists(db, M3U8Entity.class)) { if (SqlUtil.tableExists(db, M3U8Entity.class)) {
Cursor m3u8c = db.rawQuery("SELECT isLive FROM M3U8Entity WHERE filePath=\"" Cursor m3u8c = db.rawQuery("SELECT isLive FROM M3U8Entity WHERE filePath=\""
+ SqlUtil.encodeStr(filePath) + SqlUtil.encodeStr(filePath)
+ "\"", null); + "\"", null);
@ -386,8 +381,8 @@ final class SqlHelper extends SQLiteOpenHelper {
String[] taskTables = String[] taskTables =
new String[] { "UploadTaskEntity", "DownloadTaskEntity", "DownloadGroupTaskEntity" }; new String[] { "UploadTaskEntity", "DownloadTaskEntity", "DownloadGroupTaskEntity" };
for (String taskTable : taskTables) { for (String taskTable : taskTables) {
if (mDelegate.tableExists(db, taskTable)) { if (SqlUtil.tableExists(db, taskTable)) {
mDelegate.dropTable(db, taskTable); SqlUtil.dropTable(db, taskTable);
} }
} }
@ -420,8 +415,8 @@ final class SqlHelper extends SQLiteOpenHelper {
String[] taskTables = String[] taskTables =
new String[] { "UploadTaskEntity", "DownloadTaskEntity", "DownloadGroupTaskEntity" }; new String[] { "UploadTaskEntity", "DownloadTaskEntity", "DownloadGroupTaskEntity" };
for (String taskTable : taskTables) { for (String taskTable : taskTables) {
if (mDelegate.tableExists(db, taskTable)) { if (SqlUtil.tableExists(db, taskTable)) {
mDelegate.dropTable(db, taskTable); SqlUtil.dropTable(db, taskTable);
} }
} }
@ -430,7 +425,7 @@ final class SqlHelper extends SQLiteOpenHelper {
String[] keys = new String[] { "downloadPath", "groupName" }; String[] keys = new String[] { "downloadPath", "groupName" };
int i = 0; int i = 0;
for (String tableName : tables) { for (String tableName : tables) {
if (!mDelegate.tableExists(db, tableName)) { if (!SqlUtil.tableExists(db, tableName)) {
continue; continue;
} }
String pColumn = keys[i]; String pColumn = keys[i];

@ -15,6 +15,8 @@
*/ */
package com.arialyy.aria.orm; package com.arialyy.aria.orm;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils; import android.text.TextUtils;
import com.arialyy.aria.core.AriaConfig; import com.arialyy.aria.core.AriaConfig;
import com.arialyy.aria.orm.annotation.Default; import com.arialyy.aria.orm.annotation.Default;
@ -26,6 +28,7 @@ import com.arialyy.aria.orm.annotation.One;
import com.arialyy.aria.orm.annotation.Primary; import com.arialyy.aria.orm.annotation.Primary;
import com.arialyy.aria.orm.annotation.Unique; import com.arialyy.aria.orm.annotation.Unique;
import com.arialyy.aria.orm.annotation.Wrapper; import com.arialyy.aria.orm.annotation.Wrapper;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
@ -41,6 +44,280 @@ import java.util.Set;
* sql工具 * sql工具
*/ */
final class SqlUtil { final class SqlUtil {
private static final String TAG = CommonUtil.getClassName("SqlUtil");
/**
* 检查表是否存在不存在则创建表
*/
static void checkTable(SQLiteDatabase db, Class<? extends DbEntity> clazz) {
if (!tableExists(db, clazz)) {
createTable(db, clazz);
}
}
static void closeCursor(Cursor cursor) {
synchronized (AbsDelegate.class) {
if (cursor != null && !cursor.isClosed()) {
try {
cursor.close();
} catch (android.database.SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 查找表是否存在
*
* @param clazz 数据库实体
* @return true该数据库实体对应的表存在false不存在
*/
static boolean tableExists(SQLiteDatabase db, Class<? extends DbEntity> clazz) {
return tableExists(db, CommonUtil.getClassName(clazz));
}
/**
* 查找表是否存在
*
* @param tableName 表名
* @return true该数据库实体对应的表存在false不存在
*/
static boolean tableExists(SQLiteDatabase db, String tableName) {
db = checkDb(db);
Cursor cursor = null;
try {
String sql =
String.format("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='%s'",
tableName);
cursor = db.rawQuery(sql, null);
if (cursor != null && cursor.moveToNext()) {
int count = cursor.getInt(0);
if (count > 0) {
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
closeCursor(cursor);
}
return false;
}
/**
* 检查list参数是否合法list只能是{@code List<String>}
*
* @return {@code true} 合法
*/
static boolean checkList(Field list) {
Class t = CommonUtil.getListParamType(list);
if (t == String.class) {
return true;
} else {
ALog.d(TAG, "map参数错误,支持List<String>的参数字段");
return false;
}
}
/**
* 检查map参数是否合法map只能是{@code Map<String, String>}
*
* @return {@code true} 合法
*/
static boolean checkMap(Field map) {
Class[] ts = CommonUtil.getMapParamType(map);
if (ts != null
&& ts[0] != null
&& ts[1] != null
&& ts[0] == String.class
&& ts[1] == String.class) {
return true;
} else {
ALog.d(TAG, "map参数错误,支持Map<String,String>的参数字段");
return false;
}
}
/**
* 删除指定的表
*/
static void dropTable(SQLiteDatabase db, String tableName) {
db = checkDb(db);
String deleteSQL = String.format("DROP TABLE IF EXISTS %s", tableName);
//db.beginTransaction();
db.execSQL(deleteSQL);
//db.setTransactionSuccessful();
//db.endTransaction();
}
/**
* 清空表数据
*/
static void clean(SQLiteDatabase db, Class<? extends DbEntity> clazz) {
db = checkDb(db);
String tableName = CommonUtil.getClassName(clazz);
if (tableExists(db, clazz)) {
String sql = "DELETE FROM " + tableName;
db.execSQL(sql);
}
}
/**
* 检查某个字段的值是否存在
*
* @param expression 字段和值"url=xxx"
* @return {@code true}该字段的对应的value已存在
*/
static boolean checkDataExist(SQLiteDatabase db, Class<? extends DbEntity> clazz,
String... expression) {
db = checkDb(db);
if (!CommonUtil.checkSqlExpression(expression)) {
return false;
}
String sql = String.format("SELECT rowid, * FROM %s WHERE %s ", CommonUtil.getClassName(clazz),
expression[0]);
sql = sql.replace("?", "%s");
Object[] params = new String[expression.length - 1];
for (int i = 0, len = params.length; i < len; i++) {
params[i] = String.format("'%s'", SqlUtil.encodeStr(expression[i + 1]));
}
sql = String.format(sql, params);
Cursor cursor = db.rawQuery(sql, null);
final boolean isExist = cursor.getCount() > 0;
closeCursor(cursor);
return isExist;
}
/**
* 通过class 获取该class的表字段
*
* @return 表字段列表
*/
static List<String> getColumns(Class<? extends DbEntity> clazz) {
List<String> columns = new ArrayList<>();
List<Field> fields = CommonUtil.getAllFields(clazz);
for (Field field : fields) {
field.setAccessible(true);
if (SqlUtil.isIgnore(field)) {
continue;
}
columns.add(field.getName());
}
return columns;
}
/**
* 检查数据库是否关闭已经关闭的话打开数据库
*
* @return 返回数据库
*/
static SQLiteDatabase checkDb(SQLiteDatabase db) {
if (db == null || !db.isOpen()) {
db = SqlHelper.getInstance().getDb();
}
return db;
}
/**
* 创建表
*
* @param clazz 数据库实体
*/
static void createTable(SQLiteDatabase db, Class<? extends DbEntity> clazz) {
db = checkDb(db);
List<Field> fields = CommonUtil.getAllFields(clazz);
if (fields != null && fields.size() > 0) {
//外键Map,在Sqlite3中foreign修饰的字段必须放在最后
final List<Field> foreignArray = new ArrayList<>();
StringBuilder sb = new StringBuilder();
sb.append("CREATE TABLE IF NOT EXISTS ")
.append(CommonUtil.getClassName(clazz))
.append(" (");
for (Field field : fields) {
field.setAccessible(true);
if (SqlUtil.isIgnore(field)) {
continue;
}
Class<?> type = field.getType();
sb.append(field.getName());
if (type == String.class || type.isEnum()) {
sb.append(" VARCHAR");
} else if (type == int.class || type == Integer.class) {
sb.append(" INTEGER");
} else if (type == float.class || type == Float.class) {
sb.append(" FLOAT");
} else if (type == double.class || type == Double.class) {
sb.append(" DOUBLE");
} else if (type == long.class || type == Long.class) {
sb.append(" BIGINT");
} else if (type == boolean.class || type == Boolean.class) {
sb.append(" BOOLEAN");
} else if (type == java.util.Date.class || type == java.sql.Date.class) {
sb.append(" DATA");
} else if (type == byte.class || type == Byte.class) {
sb.append(" BLOB");
} else if (type == Map.class || type == List.class) {
sb.append(" TEXT");
} else {
continue;
}
if (SqlUtil.isPrimary(field)) {
Primary pk = field.getAnnotation(Primary.class);
sb.append(" PRIMARY KEY");
if (pk.autoincrement() && (type == int.class || type == Integer.class)) {
sb.append(" AUTOINCREMENT");
}
}
if (SqlUtil.isForeign(field)) {
foreignArray.add(field);
}
if (SqlUtil.isNoNull(field)) {
sb.append(" NOT NULL");
}
if (SqlUtil.isDefault(field)) {
Default d = field.getAnnotation(Default.class);
if (!TextUtils.isEmpty(d.value())) {
sb.append(" ERROR ").append("'").append(d.value()).append("'");
}
}
if (SqlUtil.isUnique(field)) {
sb.append(" UNIQUE");
}
sb.append(",");
}
for (Field field : foreignArray) {
Foreign foreign = field.getAnnotation(Foreign.class);
sb.append("FOREIGN KEY (")
.append(field.getName())
.append(") REFERENCES ")
.append(CommonUtil.getClassName(foreign.parent()))
.append("(")
.append(foreign.column())
.append(")");
ActionPolicy update = foreign.onUpdate();
ActionPolicy delete = foreign.onDelete();
if (update != ActionPolicy.NO_ACTION) {
sb.append(" ON UPDATE ").append(update.function);
}
if (delete != ActionPolicy.NO_ACTION) {
sb.append(" ON DELETE ").append(update.function);
}
sb.append(",");
}
String str = sb.toString();
str = str.substring(0, str.length() - 1) + ");";
db.execSQL(str);
}
}
/** /**
* URL编码字符串 * URL编码字符串
@ -56,7 +333,7 @@ final class SqlUtil {
/** /**
* 获取主键字段名 * 获取主键字段名
*/ */
static String getPrimaryName(Class clazz) { static String getPrimaryName(Class<? extends DbEntity> clazz) {
List<Field> fields = CommonUtil.getAllFields(clazz); List<Field> fields = CommonUtil.getAllFields(clazz);
String column; String column;
if (fields != null && !fields.isEmpty()) { if (fields != null && !fields.isEmpty()) {
@ -201,8 +478,8 @@ final class SqlUtil {
* *
* @return {@code true} * @return {@code true}
*/ */
static boolean isWrapper(Class clazz) { static boolean isWrapper(Class<? extends AbsDbWrapper> clazz) {
Wrapper w = (Wrapper) clazz.getAnnotation(Wrapper.class); Wrapper w = clazz.getAnnotation(Wrapper.class);
return w != null; return w != null;
} }

@ -28,6 +28,7 @@ import java.lang.ref.SoftReference;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.List; import java.util.List;
/** /**

@ -44,16 +44,16 @@ Aria有以下特点:
## 引入库 ## 引入库
[![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat)](https://github.com/AriaLyy/Aria/blob/master/LICENSE) [![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat)](https://github.com/AriaLyy/Aria/blob/master/LICENSE)
[![Core](https://img.shields.io/badge/Core-3.8-blue)](https://github.com/AriaLyy/Aria) [![Core](https://img.shields.io/badge/Core-3.8.1-blue)](https://github.com/AriaLyy/Aria)
[![Compiler](https://img.shields.io/badge/Compiler-3.8-blue)](https://github.com/AriaLyy/Aria) [![Compiler](https://img.shields.io/badge/Compiler-3.8.1-blue)](https://github.com/AriaLyy/Aria)
[![FtpComponent](https://img.shields.io/badge/FtpComponent-3.8-orange)](https://github.com/AriaLyy/Aria) [![FtpComponent](https://img.shields.io/badge/FtpComponent-3.8.1-orange)](https://github.com/AriaLyy/Aria)
[![M3U8Component](https://img.shields.io/badge/M3U8Component-3.8-orange)](https://github.com/AriaLyy/Aria) [![M3U8Component](https://img.shields.io/badge/M3U8Component-3.8.1-orange)](https://github.com/AriaLyy/Aria)
```java ```java
implementation 'com.arialyy.aria:core:3.8' implementation 'com.arialyy.aria:core:3.8.1'
annotationProcessor 'com.arialyy.aria:compiler:3.8' annotationProcessor 'com.arialyy.aria:compiler:3.8.1'
implementation 'com.arialyy.aria:ftpComponent:3.8' # 如果需要使用ftp,请增加该组件 implementation 'com.arialyy.aria:ftpComponent:3.8.1' # 如果需要使用ftp,请增加该组件
implementation 'com.arialyy.aria:m3u8Component:3.8' # 如果需要使用m3u8下载功能,请增加该组件 implementation 'com.arialyy.aria:m3u8Component:3.8.1' # 如果需要使用m3u8下载功能,请增加该组件
``` ```
如果出现android support依赖错误,请将 `compile 'com.arialyy.aria:core:<last-version>'`替换为 如果出现android support依赖错误,请将 `compile 'com.arialyy.aria:core:<last-version>'`替换为
``` ```
@ -137,19 +137,12 @@ protected void onCreate(Bundle savedInstanceState) {
### 版本日志 ### 版本日志
+ v_3.8 (2019/12/17) + v_3.8.1 (2019/12/22)
- 移除androidx和support的依赖,现在无论是哪个版本的appcompat包都可以使用本框架 - 修复一个表创建失败的问题 https://github.com/AriaLyy/Aria/issues/570
- 修复一个在xml中使用fragment导致的内存泄漏问题 - 修复一个非分块模式下导致下载失败的问题 https://github.com/AriaLyy/Aria/issues/571
- m3u8协议的key信息增加了`keyFormat`,`keyFormatVersion`字段 - 修复一个服务器端无法创建socket连接,却没有返回码导致客户端卡住的问题 https://github.com/AriaLyy/Aria/issues/569
- m3u8增加了`ignoreFailureTs`方法,忽略下载失败的ts切片 - 修复文件删除后,组合任务没有重新下载的问题 https://github.com/AriaLyy/Aria/issues/574
- 修复在dialogFragment的`onCreateDialog()`注册导致的注解不生效问题 - 优化缓存队列和执行队列
- 修复组合任务初始化失败时,无法删除的问题
- 修复`reStart()`后,无法停止的问题
- ftp增加主动模式,开启主动模式:https://aria.laoyuyu.me/aria_doc/api/ftp_params.html
- 修复ftp服务器无法响应`abor`命令导致的无法停止上传的问题 https://github.com/AriaLyy/Aria/issues/564
- 修复ftp上传时,服务器有长度为0的文件导致上传失败的问题
- 修复下载任务和上传任务的文件路径是同一个时,导致的记录混乱问题
- 优化提示
[更多版本记录](https://github.com/AriaLyy/Aria/blob/master/DEV_LOG.md) [更多版本记录](https://github.com/AriaLyy/Aria/blob/master/DEV_LOG.md)

@ -32,7 +32,7 @@
3、只对新的多线程下载任务有效 3、只对新的多线程下载任务有效
4、只对多线程的任务有效 4、只对多线程的任务有效
--> -->
<useBlock value="true"/> <useBlock value="false"/>
<!--设置下载线程数,下载线程数不能小于1 <!--设置下载线程数,下载线程数不能小于1
注意: 注意:

@ -1,161 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<aria>
<!--注意,修改该配置文件中的属性会覆盖代码中所设置的属性-->
<!--Aria框架配置-->
<app>
<!--是否使用AriaCrashHandler来捕获异常,异常日志保存在:/mnt/sdcard/Android/data/{package_name}/files/log/-->
<useAriaCrashHandler value="true"/>
<!--设置Aria的日志级别,{@link ALog#LOG_LEVEL_VERBOSE}-->
<logLevel value="2"/>
<!-- 是否检查网络 true: 检查网络,false: 不检查网络-->
<netCheck value="true"/>
<!--除非无法使用注解,否则不建议使用广播来接受任务状态,true:使用广播接收任务状态,false:不适用广播接收状态 -->
<!-- http://aria.laoyuyu.me/aria_doc/api/use_broadcast.html -->
<useBroadcast value="true"/>
<!--断网的时候是否重试,true:断网也重试;false:断网不重试,直接走失败的回调-->
<notNetRetry value="false"/>
</app>
<!--普通下载任务-->
<download>
<!--设置任务最大下载速度,0表示不限速,单位为:kb-->
<maxSpeed value="0"/>
<!--
多线程下载是否使用块下载模式,{@code true}使用,{@code false}不使用
注意:
1、使用分块模式,在I/O性能底下的手机上,合并文件需要的时间会更加长;
2、优点是使用多线程的块下载,初始化时,文件初始化时将不会预占用对应长度的空间;
3、只对新的多线程下载任务有效
4、只对多线程的任务有效
-->
<useBlock value="false"/>
<!--设置下载线程数,下载线程数不能小于1
注意:
1、线程下载数改变后,新的下载任务才会生效;
2、如果任务大小小于1m,该设置不会生效;
3、从3.4.1开始,如果线程数为1,文件初始化时将不再预占用对应长度的空间,下载多少byte,则占多大的空间;
对于3.4.1之前版本的未完成的老任务,依然采用原来的文件空间占用方式;
-->
<threadNum value="3"/>
<!--设置下载队列最大任务数, 默认为2-->
<maxTaskNum value="3"/>
<!--设置下载失败,重试次数,默认为10-->
<reTryNum value="5"/>
<!--设置重试间隔,单位为毫秒,默认2000毫秒-->
<reTryInterval value="5000"/>
<!--设置url连接超时时间,单位为毫秒,默认5000毫秒-->
<connectTimeOut value="5000"/>
<!--设置IO流读取时间,单位为毫秒,默认20000毫秒,该时间不能少于10000毫秒-->
<iOTimeOut value="10000"/>
<!--设置写文件buff大小,该数值大小不能小于2048,数值变小,下载速度会变慢-->
<buffSize value="8192"/>
<!--设置https ca 证书信息;path 为assets目录下的CA证书完整路径,name 为CA证书名-->
<ca name="" path=""/>
<!--是否需要转换速度单位,转换完成后为:1b/s、1kb/s、1mb/s、1gb/s、1tb/s,如果不需要将返回byte长度-->
<convertSpeed value="true"/>
<!--执行队列类型,见com.arialyy.aria.core.QueueMod,默认类型为wait-->
<queueMod value="wait"/>
<!--进度更新更新间隔,默认1000毫秒-->
<updateInterval value="1000"/>
</download>
<!--普通上传任务-->
<upload>
<!--设置任务最大上传速度,0表示不限速,单位为:kb-->
<maxSpeed value="0"/>
<!--设置IO流读取时间,单位为毫秒,默认20000毫秒,该时间不能少于10000毫秒-->
<iOTimeOut value="10000"/>
<!--设置写文件buff大小,该数值大小不能小于2048,数值变小,速度会变慢-->
<buffSize value="8192"/>
<!--是否需要转换速度单位,转换完成后为:1b/s、1kb/s、1mb/s、1gb/s、1tb/s,如果不需要将返回byte长度-->
<convertSpeed value="true"/>
<!--设置上传队列最大任务数, 默认为2-->
<maxTaskNum value="2"/>
<!--设置上传失败,重试次数,默认为10-->
<reTryNum value="3"/>
<!--设置重试间隔,单位为毫秒-->
<reTryInterval value="2000"/>
<!--设置url连接超时时间,单位为毫秒,默认5000毫秒-->
<connectTimeOut value="5000"/>
<!--执行队列类型,见com.arialyy.aria.core.QueueMod,默认类型为wait-->
<queueMod value="wait"/>
<!--进度更新更新间隔,默认1000毫秒-->
<updateInterval value="1000"/>
</upload>
<!-- 下载类组合任务 -->
<dGroup>
<!--组合任务下载队列最大任务数, 默认为2-->
<maxTaskNum value="2"/>
<!--设置下载失败,重试次数,默认为10-->
<reTryNum value="1"/>
<!--设置重试间隔,单位为毫秒,默认2000毫秒-->
<reTryInterval value="5000"/>
<!--执行队列类型,见com.arialyy.aria.core.QueueMod,默认类型为wait-->
<queueMod value="wait"/>
<!--进度更新更新间隔,默认1000毫秒-->
<updateInterval value="1000"/>
<!-- =============================以下为子任务的配置====================================-->
<!--能同时下载的子任务最大任务数,默认3-->
<subMaxTaskNum value="5"/>
<!--子任务下载失败时的重试次数,默认为5-->
<subReTryNum value="5"/>
<!--子任务下载失败时的重试间隔,单位为毫秒,默认2000毫秒-->
<subReTryInterval value="5000"/>
<!--子任务url连接超时时间,单位为毫秒,默认5000毫秒-->
<connectTimeOut value="5000"/>
<!--子任务IO流读取时间,单位为毫秒,默认20000毫秒,该时间不能少于10000毫秒-->
<iOTimeOut value="10000"/>
<!--子任务写文件buff大小,该数值大小不能小于2048,数值变小,下载速度会变慢-->
<buffSize value="8192"/>
<!--子任务 https ca 证书信息;path 为assets目录下的CA证书完整路径,name 为CA证书名-->
<ca name="" path=""/>
<!--子任务是否需要转换速度单位,转换完成后为:1b/s、1kb/s、1mb/s、1gb/s、1tb/s,如果不需要将返回byte长度-->
<convertSpeed value="true"/>
<!--子任务的最大下载速度,0表示不限速,单位为:kb; -->
<maxSpeed value="0"/>
</dGroup>
</aria>

@ -117,6 +117,8 @@ public class MainActivity extends BaseActivity<ActivityMainBinding> {
module.startNextActivity(MainActivity.this, data.get(position), module.startNextActivity(MainActivity.this, data.get(position),
M3U8LiveDLoadActivity.class); M3U8LiveDLoadActivity.class);
break; break;
case 8: // sftp
break;
} }
} }
}); });

@ -97,7 +97,7 @@ public class DownloadGroupActivity extends BaseActivity<ActivityDownloadGroupBin
.setDirPath( .setDirPath(
Environment.getExternalStorageDirectory().getPath() + "/Download/group_imgs") Environment.getExternalStorageDirectory().getPath() + "/Download/group_imgs")
//.setSubFileName(getModule(GroupModule.class).getSubName2()) //.setSubFileName(getModule(GroupModule.class).getSubName2())
//.setSubFileName(getModule(GroupModule.class).getSubName()) .setSubFileName(getModule(GroupModule.class).getSubName())
.unknownSize() .unknownSize()
.option(getHttpOption()) .option(getHttpOption())
.ignoreFilePathOccupy() .ignoreFilePathOccupy()

@ -103,7 +103,8 @@ public class CommonModule extends BaseViewModule {
R.drawable.ic_ftp_dir, R.drawable.ic_ftp_dir,
R.drawable.ic_ftp, R.drawable.ic_ftp,
R.drawable.ic_ts, R.drawable.ic_ts,
R.drawable.ic_live R.drawable.ic_live,
R.drawable.ic_sftp
}; };
int i = 0; int i = 0;
for (String title : titles) { for (String title : titles) {

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M694.4,1.6L163.2,1.6C113.6,1.6 73.6,41.6 73.6,91.2v843.2c0,49.6 40,89.6 89.6,89.6h475.2c-20.8,-14.4 -33.6,-36.8 -33.6,-64L604.8,758.4c0,-41.6 32,-84.8 75.2,-84.8h209.6l1.6,-475.2L694.4,1.6zM307.2,545.6c-6.4,9.6 -14.4,16 -25.6,20.8 -11.2,4.8 -24,8 -38.4,8 -24,0 -41.6,-4.8 -54.4,-14.4 -12.8,-9.6 -20.8,-24 -24,-43.2l30.4,-4.8c1.6,12.8 8,20.8 16,27.2 8,6.4 19.2,9.6 33.6,9.6 14.4,0 22.4,-3.2 28.8,-9.6s9.6,-11.2 9.6,-17.6c0,-6.4 -3.2,-12.8 -9.6,-16 -4.8,-3.2 -14.4,-4.8 -30.4,-9.6 -22.4,-6.4 -38.4,-11.2 -46.4,-14.4 -8,-4.8 -16,-9.6 -19.2,-17.6 -4.8,-8 -6.4,-16 -6.4,-24s1.6,-16 4.8,-22.4c3.2,-6.4 8,-12.8 14.4,-17.6 4.8,-3.2 14.4,-9.6 22.4,-12.8 8,-3.2 17.6,-3.2 27.2,-3.2 14.4,0 22.4,1.6 33.6,6.4 6.4,3.2 19.2,12.8 24,20.8 4.8,6.4 8,16 11.2,27.2l-30.4,4.8c-1.6,-9.6 -4.8,-16 -11.2,-22.4 -6.4,-4.8 -16,-8 -27.2,-8 -14.4,0 -22.4,1.6 -27.2,4.8 -6.4,4.8 -11.2,11.2 -11.2,17.6 0,3.2 0,8 1.6,11.2 3.2,3.2 6.4,6.4 11.2,8 3.2,1.6 12.8,3.2 27.2,8 22.4,6.4 36.8,11.2 44.8,14.4 8,3.2 16,9.6 20.8,17.6 4.8,6.4 8,16 8,25.6 0,8 -3.2,17.6 -8,25.6zM465.6,417.6h-91.2v44.8h78.4v32h-78.4v80L336,574.4L336,385.6h129.6v32zM633.6,417.6h-56v156.8h-38.4v-156.8h-56v-32h150.4v32zM790.4,473.6c-4.8,8 -9.6,14.4 -16,19.2 -6.4,4.8 -12.8,8 -19.2,9.6 -9.6,1.6 -22.4,3.2 -40,3.2h-25.6v72h-38.4v-192h60.8c22.4,0 38.4,1.6 44.8,3.2 11.2,3.2 19.2,9.6 27.2,19.2s11.2,22.4 11.2,36.8c0,11.2 -1.6,20.8 -4.8,28.8zM694.4,196.8L694.4,51.2l145.6,145.6h-145.6z"
android:fillColor="@color/icon_color"/>
<path
android:pathData="M736,419.2c-4.8,-1.6 -14.4,-1.6 -27.2,-1.6h-19.2v52.8h20.8c14.4,0 25.6,-1.6 30.4,-3.2 4.8,-1.6 9.6,-4.8 11.2,-9.6 3.2,-4.8 4.8,-9.6 4.8,-14.4 0,-6.4 -1.6,-12.8 -6.4,-17.6 -3.2,-3.2 -9.6,-4.8 -14.4,-6.4zM795.2,867.2c-11.2,0 -22.4,9.6 -22.4,22.4 0,9.6 6.4,17.6 14.4,20.8v30.4h14.4v-30.4c8,-3.2 14.4,-11.2 14.4,-20.8 1.6,-12.8 -8,-22.4 -20.8,-22.4zM795.2,764.8c-16,0 -28.8,12.8 -28.8,28.8v28.8h57.6v-28.8c1.6,-16 -12.8,-28.8 -28.8,-28.8z"
android:fillColor="@color/icon_color"/>
<path
android:pathData="M892.8,705.6L699.2,705.6c-32,0 -57.6,25.6 -57.6,56L641.6,960c0,30.4 25.6,56 57.6,56h195.2c32,0 57.6,-25.6 57.6,-56L952,761.6c-1.6,-30.4 -27.2,-56 -59.2,-56zM883.2,955.2c0,8 -6.4,14.4 -14.4,14.4h-144c-8,0 -14.4,-6.4 -14.4,-14.4L710.4,832c0,-8 6.4,-14.4 14.4,-14.4h22.4v-28.8c0,-27.2 22.4,-51.2 51.2,-51.2 27.2,0 51.2,22.4 51.2,51.2v28.8h22.4c8,0 14.4,6.4 14.4,14.4v123.2z"
android:fillColor="@color/icon_color"/>
</vector>

@ -32,14 +32,15 @@
<string name="error_file_name_null">文件名为空</string> <string name="error_file_name_null">文件名为空</string>
<string-array name="main_items"> <string-array name="main_items">
<item>http 下载</item> <item>HTTP 下载</item>
<item>http 上传</item> <item>HTTP 上传</item>
<item>http 组合任务下载</item> <item>HTTP 组合任务下载</item>
<item>FTP 下载</item> <item>FTP 下载</item>
<item>FTP 文件夹下载</item> <item>FTP 文件夹下载</item>
<item>FTP 文件上传</item> <item>FTP 文件上传</item>
<item>M3U8 点播文件下载</item> <item>M3U8 点播文件下载</item>
<item>M3U8 直播文件下载</item> <item>M3U8 直播文件下载</item>
<item>SFTP 下载</item>
</string-array> </string-array>
<string-array name="main_items_desc"> <string-array name="main_items_desc">
@ -51,6 +52,7 @@
<item>上传单个文件到服务器</item> <item>上传单个文件到服务器</item>
<item>下载M3U8点播文件</item> <item>下载M3U8点播文件</item>
<item>下载M3U8直播文件</item> <item>下载M3U8直播文件</item>
<item>SFTP单文件下载</item>
</string-array> </string-array>
<string-array name="download_items"> <string-array name="download_items">

@ -44,8 +44,8 @@ task clean(type: Delete) {
} }
ext { ext {
versionCode = 380 versionCode = 381
versionName = '3.8' versionName = '3.8.1'
userOrg = 'arialyy' userOrg = 'arialyy'
groupId = 'com.arialyy.aria' groupId = 'com.arialyy.aria'
publishVersion = versionName publishVersion = versionName

Loading…
Cancel
Save