diff --git a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java index 9f7316c5..59a744aa 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java +++ b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java @@ -97,7 +97,7 @@ import org.xml.sax.SAXException; } /** - * 设置Aria 日志级别 + * 设置Aria的日志级别 * * @param level {@link ALog#LOG_LEVEL_VERBOSE} */ @@ -106,20 +106,34 @@ import org.xml.sax.SAXException; } /** - * 设置上传任务的执行队列类型 + * 设置上传任务的执行队列类型,后续版本会删除该api,请使用: + *
+   *   
+   *     Aria.get(this).getUploadConfig().setQueueMod(mod.tag)
+   *   
+   * 
    *
    * @param mod {@link QueueMod}
+   * @deprecated 后续版本会删除该api
    */
+  @Deprecated
   public AriaManager setUploadQueueMod(QueueMod mod) {
     mUConfig.setQueueMod(mod.tag);
     return this;
   }
 
   /**
-   * 设置下载任务的执行队列类型
+   * 设置下载任务的执行队列类型,后续版本会删除该api,请使用:
+   * 
+   *   
+   *     Aria.get(this).getDownloadConfig().setQueueMod(mod.tag)
+   *   
+   * 
    *
    * @param mod {@link QueueMod}
+   * @deprecated 后续版本会删除该api
    */
+  @Deprecated
   public AriaManager setDownloadQueueMod(QueueMod mod) {
     mDConfig.setQueueMod(mod.tag);
     return this;
@@ -270,7 +284,8 @@ import org.xml.sax.SAXException;
   /**
    * 不允许在"onDestroy"、"finish"、"onStop"这三个方法中添加注册器
    */
-  private AbsReceiver checkTarget(String key, AbsReceiver receiver, Object obj, boolean needRmReceiver) {
+  private AbsReceiver checkTarget(String key, AbsReceiver receiver, Object obj,
+      boolean needRmReceiver) {
     StackTraceElement[] stack = Thread.currentThread().getStackTrace();
     int i = 0;
     for (StackTraceElement e : stack) {
diff --git a/Aria/src/main/java/com/arialyy/aria/core/delegate/FtpDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/delegate/FtpDelegate.java
new file mode 100644
index 00000000..578c478f
--- /dev/null
+++ b/Aria/src/main/java/com/arialyy/aria/core/delegate/FtpDelegate.java
@@ -0,0 +1,64 @@
+/*
+ * 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.delegate;
+
+import android.text.TextUtils;
+import com.arialyy.aria.core.inf.AbsEntity;
+import com.arialyy.aria.core.inf.AbsTaskEntity;
+import com.arialyy.aria.core.inf.IFtpTarget;
+import com.arialyy.aria.core.inf.ITarget;
+import com.arialyy.aria.util.ALog;
+
+/**
+ * Created by laoyuyu on 2018/3/9.
+ * ftp 委托
+ */
+public class FtpDelegate>
+    implements IFtpTarget {
+  private static final String TAG = "FtpDelegate";
+  private ENTITY mEntity;
+  private TASK_ENTITY mTaskEntity;
+
+  public FtpDelegate(TASK_ENTITY mTaskEntity) {
+    this.mTaskEntity = mTaskEntity;
+    mEntity = mTaskEntity.getEntity();
+  }
+
+  @Override public TARGET charSet(String charSet) {
+    if (TextUtils.isEmpty(charSet)) return (TARGET) this;
+    mTaskEntity.charSet = charSet;
+    return (TARGET) this;
+  }
+
+  @Override public TARGET login(String userName, String password) {
+    return login(userName, password, null);
+  }
+
+  @Override public TARGET login(String userName, String password, String account) {
+    if (TextUtils.isEmpty(userName)) {
+      ALog.e(TAG, "用户名不能为null");
+      return (TARGET) this;
+    } else if (TextUtils.isEmpty(password)) {
+      ALog.e(TAG, "密码不能为null");
+      return (TARGET) this;
+    }
+    mTaskEntity.urlEntity.needLogin = true;
+    mTaskEntity.urlEntity.user = userName;
+    mTaskEntity.urlEntity.password = password;
+    mTaskEntity.urlEntity.account = account;
+    return (TARGET) this;
+  }
+}
diff --git a/Aria/src/main/java/com/arialyy/aria/core/delegate/HttpHeaderDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/delegate/HttpHeaderDelegate.java
new file mode 100644
index 00000000..628b1501
--- /dev/null
+++ b/Aria/src/main/java/com/arialyy/aria/core/delegate/HttpHeaderDelegate.java
@@ -0,0 +1,202 @@
+/*
+ * 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.delegate;
+
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import com.arialyy.aria.core.common.RequestEnum;
+import com.arialyy.aria.core.download.DownloadEntity;
+import com.arialyy.aria.core.inf.AbsEntity;
+import com.arialyy.aria.core.inf.AbsTaskEntity;
+import com.arialyy.aria.core.inf.IHttpHeaderTarget;
+import com.arialyy.aria.core.inf.ITarget;
+import com.arialyy.aria.util.ALog;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Created by laoyuyu on 2018/3/9.
+ * HTTP header参数设置委托类
+ */
+public class HttpHeaderDelegate>
+    implements IHttpHeaderTarget {
+  private static final String TAG = "HttpHeaderDelegate";
+  private ENTITY mEntity;
+  private TASK_ENTITY mTaskEntity;
+
+  public HttpHeaderDelegate(TASK_ENTITY mTaskEntity) {
+    this.mTaskEntity = mTaskEntity;
+    mEntity = mTaskEntity.getEntity();
+  }
+
+  /**
+   * 给url请求添加Header数据
+   * 如果新的header数据和数据保存的不一致,则更新数据库中对应的header数据
+   *
+   * @param key header对应的key
+   * @param value header对应的value
+   */
+  @Override
+  public TARGET addHeader(@NonNull String key, @NonNull String value) {
+    if (TextUtils.isEmpty(key)) {
+      ALog.w(TAG, "设置header失败,header对应的key不能为null");
+      return (TARGET) this;
+    } else if (TextUtils.isEmpty(value)) {
+      ALog.w(TAG, "设置header失败,header对应的value不能为null");
+      return (TARGET) this;
+    }
+    if (mTaskEntity.headers.get(key) == null) {
+      mTaskEntity.headers.put(key, value);
+      mTaskEntity.update();
+    } else if (!mTaskEntity.headers.get(key).equals(value)) {
+      mTaskEntity.headers.put(key, value);
+      mTaskEntity.update();
+    }
+    return (TARGET) this;
+  }
+
+  /**
+   * 给url请求添加一组header数据
+   * 如果新的header数据和数据保存的不一致,则更新数据库中对应的header数据
+   *
+   * @param headers 一组http header数据
+   */
+  @Override
+  public TARGET addHeaders(@NonNull Map headers) {
+    if (headers.size() == 0) {
+      ALog.w(TAG, "设置header失败,map没有header数据");
+      return (TARGET) this;
+    }
+    /*
+      两个map比较逻辑
+      1、比对key是否相同
+      2、如果key相同,比对value是否相同
+      3、只有当上面两个步骤中key 和 value都相同时才能任务两个map数据一致
+     */
+    boolean mapEquals = false;
+    if (mTaskEntity.headers.size() == headers.size()) {
+      int i = 0;
+      Set keys = mTaskEntity.headers.keySet();
+      for (String key : keys) {
+        if (headers.containsKey(key)) {
+          i++;
+        } else {
+          break;
+        }
+      }
+      if (i == mTaskEntity.headers.size()) {
+        int j = 0;
+        Collection values = mTaskEntity.headers.values();
+        for (String value : values) {
+          if (headers.containsValue(value)) {
+            j++;
+          } else {
+            break;
+          }
+        }
+        if (j == mTaskEntity.headers.size()) {
+          mapEquals = true;
+        }
+      }
+    }
+
+    if (!mapEquals) {
+      mTaskEntity.headers.clear();
+      Set keys = headers.keySet();
+      for (String key : keys) {
+        mTaskEntity.headers.put(key, headers.get(key));
+      }
+      mTaskEntity.update();
+    }
+
+    return (TARGET) this;
+  }
+
+  /**
+   * 设置请求类型,POST或GET,默认为在GET
+   * 只试用于HTTP请求
+   *
+   * @param requestEnum {@link RequestEnum}
+   */
+  @Override
+  public TARGET setRequestMode(RequestEnum requestEnum) {
+    mTaskEntity.requestEnum = requestEnum;
+    return (TARGET) this;
+  }
+
+  /**
+   * 如果你的下载链接的header中含有md5码信息,那么你可以通过设置key,来获取从header获取该md5码信息。
+   * key默认值为:Content-MD5
+   * 获取md5信息:{@link DownloadEntity#getMd5Code()}
+   */
+  @Override
+  public TARGET setHeaderMd5Key(String md5Key) {
+    if (TextUtils.isEmpty(md5Key)) return (TARGET) this;
+    mTaskEntity.md5Key = md5Key;
+    if (TextUtils.isEmpty(mTaskEntity.md5Key) || !mTaskEntity.md5Key.equals(md5Key)) {
+      mTaskEntity.update();
+    }
+    return (TARGET) this;
+  }
+
+  /**
+   * 如果你的文件长度是放在header中,那么你需要配置key来让Aria知道正确的文件长度
+   * key默认值为:Content-Length
+   */
+  @Override
+  public TARGET setHeaderContentLengthKey(String contentLength) {
+    if (TextUtils.isEmpty(contentLength)) return (TARGET) this;
+    mTaskEntity.contentLength = contentLength;
+    if (TextUtils.isEmpty(mTaskEntity.contentLength) || !mTaskEntity.contentLength.equals(
+        contentLength)) {
+      mTaskEntity.update();
+    }
+    return (TARGET) this;
+  }
+
+  /**
+   * 如果你的下载链接的header中含有文件描述信息,那么你可以通过设置key,来获取从header获取该文件描述信息。
+   * key默认值为:Content-Disposition
+   * 获取文件描述信息:{@link DownloadEntity#getDisposition()}
+   */
+  @Override
+  public TARGET setHeaderDispositionKey(String dispositionKey) {
+    if (TextUtils.isEmpty(dispositionKey)) return (TARGET) this;
+    mTaskEntity.dispositionKey = dispositionKey;
+    if (TextUtils.isEmpty(mTaskEntity.dispositionKey) || !mTaskEntity.dispositionKey.equals(
+        dispositionKey)) {
+      mTaskEntity.update();
+    }
+    return (TARGET) this;
+  }
+
+  /**
+   * 从文件描述信息{@link #setHeaderDispositionKey(String)}中含有文件名信息,你可以通过设置key来获取header中的文件名
+   * key默认值为:attachment;filename
+   * 获取文件名信息:{@link DownloadEntity#getServerFileName()}
+   */
+  @Override
+  public TARGET setHeaderDispositionFileKey(String dispositionFileKey) {
+    if (TextUtils.isEmpty(dispositionFileKey)) return (TARGET) this;
+    mTaskEntity.dispositionFileKey = dispositionFileKey;
+    if (TextUtils.isEmpty(mTaskEntity.dispositionFileKey) || !mTaskEntity.dispositionFileKey.equals(
+        dispositionFileKey)) {
+      mTaskEntity.update();
+    }
+    return (TARGET) this;
+  }
+}
diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/AbsDownloadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/AbsDownloadTarget.java
new file mode 100644
index 00000000..b9e33158
--- /dev/null
+++ b/Aria/src/main/java/com/arialyy/aria/core/download/AbsDownloadTarget.java
@@ -0,0 +1,110 @@
+/*
+ * 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.download;
+
+import android.text.TextUtils;
+import com.arialyy.aria.core.AriaManager;
+import com.arialyy.aria.core.command.normal.NormalCmdFactory;
+import com.arialyy.aria.core.inf.AbsEntity;
+import com.arialyy.aria.core.inf.AbsTarget;
+import com.arialyy.aria.core.inf.AbsTaskEntity;
+import com.arialyy.aria.util.ALog;
+import com.arialyy.aria.util.CommonUtil;
+
+/**
+ * Created by lyy on 2017/2/28.
+ */
+abstract class AbsDownloadTarget
+    extends AbsTarget {
+
+  static final int HTTP = 1;
+  static final int FTP = 2;
+  //HTTP任务组
+  static final int GROUP_HTTP = 3;
+  //FTP文件夹
+  static final int GROUP_FTP_DIR = 4;
+
+  /**
+   * 设置的文件保存路径的临时变量
+   */
+  String mTempFilePath;
+
+  /**
+   * 将任务设置为最高优先级任务,最高优先级任务有以下特点:
+   * 1、在下载队列中,有且只有一个最高优先级任务
+   * 2、最高优先级任务会一直存在,直到用户手动暂停或任务完成
+   * 3、任务调度器不会暂停最高优先级任务
+   * 4、用户手动暂停或任务完成后,第二次重新执行该任务,该命令将失效
+   * 5、如果下载队列中已经满了,则会停止队尾的任务,当高优先级任务完成后,该队尾任务将自动执行
+   * 6、把任务设置为最高优先级任务后,将自动执行任务,不需要重新调用start()启动任务
+   */
+  protected void setHighestPriority() {
+    if (checkEntity()) {
+      AriaManager.getInstance(AriaManager.APP)
+          .setCmd(CommonUtil.createNormalCmd(mTargetName, mTaskEntity,
+              NormalCmdFactory.TASK_HIGHEST_PRIORITY, checkTaskType()))
+          .exe();
+    }
+  }
+
+  /**
+   * 添加任务
+   */
+  public void add() {
+    if (checkEntity()) {
+      AriaManager.getInstance(AriaManager.APP)
+          .setCmd(CommonUtil.createNormalCmd(mTargetName, mTaskEntity, NormalCmdFactory.TASK_CREATE,
+              checkTaskType()))
+          .exe();
+    }
+  }
+
+  /**
+   * 重定向后,新url的key,默认为location
+   */
+  public void setRedirectUrlKey(String redirectUrlKey) {
+    if (TextUtils.isEmpty(redirectUrlKey)) {
+      ALog.e("AbsDownloadTarget", "重定向后,新url的key不能为null");
+      return;
+    }
+    mTaskEntity.redirectUrlKey = redirectUrlKey;
+  }
+
+  /**
+   * 获取任务文件大小
+   *
+   * @return 文件大小
+   */
+  public long getFileSize() {
+    return getSize();
+  }
+
+  /**
+   * 获取单位转换后的文件大小
+   *
+   * @return 文件大小{@code xxx mb}
+   */
+  public String getConvertFileSize() {
+    return getConvertSize();
+  }
+
+  /**
+   * 设置target类型
+   *
+   * @return {@link #HTTP}、{@link #FTP}、{@link #GROUP_HTTP}、{@link #GROUP_FTP_DIR}
+   */
+  protected abstract int getTargetType();
+}
diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/BaseGroupTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/BaseGroupTarget.java
index e6ed4274..4ace4af0 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/download/BaseGroupTarget.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/download/BaseGroupTarget.java
@@ -16,32 +16,29 @@
 package com.arialyy.aria.core.download;
 
 import android.text.TextUtils;
-import com.arialyy.aria.core.inf.AbsDownloadTarget;
-import com.arialyy.aria.core.inf.AbsGroupTaskEntity;
-import com.arialyy.aria.core.inf.AbsTarget;
 import com.arialyy.aria.core.manager.SubTaskManager;
+import com.arialyy.aria.core.queue.DownloadGroupTaskQueue;
+import com.arialyy.aria.core.queue.DownloadTaskQueue;
 import com.arialyy.aria.orm.DbEntity;
-import com.arialyy.aria.util.CommonUtil;
+import com.arialyy.aria.util.ALog;
 import java.io.File;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Created by Aria.Lao on 2017/7/26.
  */
-abstract class BaseGroupTarget
-    extends AbsDownloadTarget {
+abstract class BaseGroupTarget
+    extends AbsDownloadTarget {
+  private static final String TAG = "BaseGroupTarget";
 
-  List mUrls = new ArrayList<>();
-  String mGroupName;
   /**
-   * 子任务文件名
+   * 组任务名
    */
-  private List mSubTaskFileName = new ArrayList<>();
+  String mGroupName;
   /**
-   * 是否已经设置了文件路径
+   * 文件夹临时路径
    */
-  private boolean isSetDirPathed = false;
+  private String mDirPathTemp;
 
   private SubTaskManager mSubTaskManager;
 
@@ -67,6 +64,21 @@ abstract class BaseGroupTarget
    *
-   * @param groupDirPath 任务组保存文件夹路径
+   * @param dirPath 任务组保存文件夹路径
    */
-  public TARGET setDownloadDirPath(String groupDirPath) {
-    if (TextUtils.isEmpty(groupDirPath)) {
-      throw new NullPointerException("任务组文件夹保存路径不能为null");
-    }
-
-    isSetDirPathed = true;
-    if (mEntity.getDirPath().equals(groupDirPath)) return (TARGET) this;
-
-    File file = new File(groupDirPath);
-    if (file.exists() && file.isFile()) {
-      throw new IllegalArgumentException("路径不能为文件");
-    }
-    if (!file.exists()) {
-      file.mkdirs();
-    }
-
-    mEntity.setDirPath(groupDirPath);
-    if (!TextUtils.isEmpty(mEntity.getDirPath())) {
-      reChangeDirPath(groupDirPath);
-    } else {
-      mEntity.setSubTasks(createSubTask());
-    }
-    mEntity.update();
+  public TARGET setDirPath(String dirPath) {
+    mDirPathTemp = dirPath;
     return (TARGET) this;
   }
 
+  @Override public boolean isRunning() {
+    DownloadGroupTask task = DownloadGroupTaskQueue.getInstance().getTask(mEntity.getKey());
+    return task != null && task.isRunning();
+  }
+
   /**
    * 改变任务组文件夹路径,修改文件夹路径会将子任务所有路径更换
    *
@@ -133,81 +129,38 @@ abstract class BaseGroupTarget subTaskFileName) {
-
-    return setSubFileName(subTaskFileName);
-  }
-
-  /**
-   * 设置子任务文件名,该方法必须在{@link #setDownloadDirPath(String)}之后调用,否则不生效
+   * @return {@code true} 合法
    */
-  public TARGET setSubFileName(List subTaskFileName) {
-    if (subTaskFileName == null || subTaskFileName.isEmpty()) return (TARGET) this;
-    mSubTaskFileName.addAll(subTaskFileName);
-    if (mUrls.size() != subTaskFileName.size()) {
-      throw new IllegalArgumentException("下载链接数必须要和保存路径的数量一致");
+  boolean checkDirPath() {
+    final String dirPath = mDirPathTemp;
+    if (TextUtils.isEmpty(dirPath)) {
+      ALog.e(TAG, "文件夹路径不能为null");
+      return false;
+    } else if (!dirPath.startsWith("/")) {
+      ALog.e(TAG, "文件夹路径【" + dirPath + "】错误");
+      return false;
     }
-    if (isSetDirPathed) {
-      List entities = mEntity.getSubTask();
-      int i = 0;
-      for (DownloadEntity entity : entities) {
-        if (i < mSubTaskFileName.size()) {
-          String newName = mSubTaskFileName.get(i);
-          updateSubFileName(entity, newName);
-        }
-        i++;
-      }
+    File file = new File(dirPath);
+    if (file.isFile()) {
+      ALog.e(TAG, "路径【" + dirPath + "】是文件,请设置文件夹路径");
+      return false;
     }
-    return (TARGET) this;
-  }
 
-  /**
-   * 更新子任务文件名
-   */
-  private void updateSubFileName(DownloadEntity entity, String newName) {
-    if (!newName.equals(entity.getFileName())) {
-      String oldPath = mEntity.getDirPath() + "/" + entity.getFileName();
-      String newPath = mEntity.getDirPath() + "/" + newName;
-      File oldFile = new File(oldPath);
-      if (oldFile.exists()) {
-        oldFile.renameTo(new File(newPath));
+    if (!mEntity.getDirPath().equals(dirPath)) {
+      if (!file.exists()) {
+        file.mkdirs();
       }
-      CommonUtil.renameDownloadConfig(oldFile.getName(), newName);
-      DbEntity.exeSql(
-          "UPDATE DownloadTaskEntity SET key='" + newPath + "' WHERE key='" + oldPath + "'");
-      entity.setDownloadPath(newPath);
-      entity.setFileName(newName);
-      entity.update();
+      mEntity.setDirPath(dirPath);
+      reChangeDirPath(dirPath);
+      mEntity.update();
     }
-  }
 
-  /**
-   * 创建子任务
-   */
-  private List createSubTask() {
-    List list = new ArrayList<>();
-    for (int i = 0, len = mUrls.size(); i < len; i++) {
-      DownloadEntity entity = new DownloadEntity();
-      entity.setUrl(mUrls.get(i));
-      String fileName =
-          mSubTaskFileName.isEmpty() ? createFileName(entity.getUrl()) : mSubTaskFileName.get(i);
-      entity.setDownloadPath(mEntity.getDirPath() + "/" + fileName);
-      entity.setGroupName(mGroupName);
-      entity.setGroupChild(true);
-      entity.setFileName(fileName);
-      entity.insert();
-      list.add(entity);
-    }
-    return list;
+    return true;
   }
 }
diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/BaseNormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/BaseNormalTarget.java
new file mode 100644
index 00000000..b488685e
--- /dev/null
+++ b/Aria/src/main/java/com/arialyy/aria/core/download/BaseNormalTarget.java
@@ -0,0 +1,200 @@
+/*
+ * 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.download;
+
+import android.text.TextUtils;
+import com.arialyy.aria.core.manager.TEManager;
+import com.arialyy.aria.core.queue.DownloadTaskQueue;
+import com.arialyy.aria.orm.DbEntity;
+import com.arialyy.aria.util.ALog;
+import com.arialyy.aria.util.CommonUtil;
+import java.io.File;
+
+/**
+ * Created by Aria.Lao on 2017/7/26.
+ */
+abstract class BaseNormalTarget
+    extends AbsDownloadTarget {
+
+  private static final String TAG = "BaseNormalTarget";
+  /**
+   * 资源地址
+   */
+  protected String url;
+
+  /**
+   * 通过实体初始化target
+   */
+  void initTarget(DownloadEntity entity, String targetName, boolean refreshInfo) {
+    this.url = entity.getUrl();
+    mTargetName = targetName;
+    mTaskEntity = TEManager.getInstance().getTEntity(DownloadTaskEntity.class, url);
+    if (mTaskEntity == null) {
+      mTaskEntity = TEManager.getInstance().createTEntity(DownloadTaskEntity.class, entity);
+    }
+    mEntity = mTaskEntity.entity;
+    mTaskEntity.refreshInfo = refreshInfo;
+  }
+
+  /**
+   * 通过地址初始化target
+   */
+  void initTarget(String url, String targetName, boolean refreshInfo) {
+    this.url = url;
+    mTargetName = targetName;
+    mTaskEntity = TEManager.getInstance().getTEntity(DownloadTaskEntity.class, url);
+    if (mTaskEntity == null) {
+      mTaskEntity = TEManager.getInstance().createTEntity(DownloadTaskEntity.class, url);
+    }
+    mEntity = mTaskEntity.entity;
+    mTaskEntity.refreshInfo = refreshInfo;
+  }
+
+  /**
+   * 将任务设置为最高优先级任务,最高优先级任务有以下特点:
+   * 1、在下载队列中,有且只有一个最高优先级任务
+   * 2、最高优先级任务会一直存在,直到用户手动暂停或任务完成
+   * 3、任务调度器不会暂停最高优先级任务
+   * 4、用户手动暂停或任务完成后,第二次重新执行该任务,该命令将失效
+   * 5、如果下载队列中已经满了,则会停止队尾的任务,当高优先级任务完成后,该队尾任务将自动执行
+   * 6、把任务设置为最高优先级任务后,将自动执行任务,不需要重新调用start()启动任务
+   */
+  @Override public void setHighestPriority() {
+    super.setHighestPriority();
+  }
+
+  /**
+   * 下载任务是否存在
+   *
+   * @return {@code true}任务存在
+   */
+  @Override public boolean taskExists() {
+    return DownloadTaskQueue.getInstance().getTask(mEntity.getUrl()) != null;
+  }
+
+  /**
+   * 获取下载实体
+   */
+  public DownloadEntity getDownloadEntity() {
+    return mEntity;
+  }
+
+  /**
+   * 是否在下载,该api后续版本会删除
+   *
+   * @deprecated {@link #isRunning()}
+   */
+  @Deprecated
+  public boolean isDownloading() {
+    return isRunning();
+  }
+
+  /**
+   * 是否在下载
+   *
+   * @return {@code true}任务正在下载
+   */
+  @Override public boolean isRunning() {
+    DownloadTask task = DownloadTaskQueue.getInstance().getTask(mEntity.getKey());
+    return task != null && task.isRunning();
+  }
+
+  /**
+   * 检查下载实体,判断实体是否合法
+   * 合法标准为:
+   * 1、下载路径不为null,并且下载路径是正常的http或ftp路径
+   * 2、保存路径不为null,并且保存路径是android文件系统路径
+   * 3、保存路径不能重复
+   *
+   * @return {@code true}合法
+   */
+  @Override
+  protected boolean checkEntity() {
+    return getTargetType() < GROUP_HTTP && checkUrl() && checkFilePath();
+  }
+
+  /**
+   * 检查并设置普通任务的文件保存路径
+   *
+   * @return {@code true}保存路径合法
+   */
+  private boolean checkFilePath() {
+    String filePath = mTempFilePath;
+    if (TextUtils.isEmpty(filePath)) {
+      ALog.e(TAG, "下载失败,文件保存路径为null");
+      return false;
+    } else if (!filePath.startsWith("/")) {
+      ALog.e(TAG, "下载失败,文件保存路径【" + filePath + "】错误");
+      return false;
+    }
+    File file = new File(filePath);
+    if (file.isDirectory()) {
+      if (getTargetType() == HTTP) {
+        ALog.e(TAG, "保存路径【" + filePath + "】不能为文件夹,路径需要是完整的文件路径,如:/mnt/sdcard/game.zip");
+        return false;
+      } else if (getTargetType() == FTP) {
+        filePath += mEntity.getFileName();
+      }
+    }
+    //设置文件保存路径,如果新文件路径和就文件路径不同,则修改路径
+    if (!filePath.equals(mEntity.getDownloadPath())) {
+      if (!mTaskEntity.refreshInfo && DbEntity.checkDataExist(DownloadEntity.class,
+          "downloadPath=?", filePath)) {
+        ALog.e(TAG, "保存路径【" + filePath + "】已经被其它任务占用,请设置其它保存路径");
+        return false;
+      }
+      File oldFile = new File(mEntity.getDownloadPath());
+      File newFile = new File(filePath);
+      if (TextUtils.isEmpty(mEntity.getDownloadPath()) || oldFile.renameTo(newFile)) {
+        mEntity.setDownloadPath(filePath);
+        mEntity.setFileName(newFile.getName());
+        mTaskEntity.key = filePath;
+        mTaskEntity.update();
+        CommonUtil.renameDownloadConfig(oldFile.getName(), newFile.getName());
+      }
+    }
+    return true;
+  }
+
+  /**
+   * 检查普通任务的下载地址
+   *
+   * @return {@code true}地址合法
+   */
+  private boolean checkUrl() {
+    final String url = mEntity.getUrl();
+    if (TextUtils.isEmpty(url)) {
+      ALog.e(TAG, "下载失败,url为null");
+      return false;
+    } else if (!url.startsWith("http") && !url.startsWith("ftp")) {
+      ALog.e(TAG, "下载失败,url【" + url + "】错误");
+      return false;
+    }
+    int index = url.indexOf("://");
+    if (index == -1) {
+      ALog.e(TAG, "下载失败,url【" + url + "】不合法");
+      return false;
+    }
+    String temp = url.substring(index + 3, url.length());
+    if (temp.contains("//")) {
+      temp = url.substring(0, index + 3) + temp.replaceAll("//", "/");
+      ALog.w(TAG, "url中含有//,//将转换为/,转换后的url为:" + temp);
+      mEntity.setUrl(temp);
+      mEntity.update();
+    }
+    return true;
+  }
+}
diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java
index cb714c38..c6697a5e 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java
@@ -34,7 +34,7 @@ public class DownloadGroupEntity extends AbsGroupEntity {
   /**
    * 任务组下载文件的文件夹地址
    *
-   * @see DownloadGroupTarget#setDownloadDirPath(String)
+   * @see DownloadGroupTarget#setDirPath(String)
    */
   private String dirPath = "";
 
diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java
index 9bd63dd7..903f8b7e 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTarget.java
@@ -15,21 +15,34 @@
  */
 package com.arialyy.aria.core.download;
 
-import com.arialyy.aria.core.inf.IEntity;
+import android.text.TextUtils;
 import com.arialyy.aria.core.manager.TEManager;
-import com.arialyy.aria.core.queue.DownloadGroupTaskQueue;
+import com.arialyy.aria.orm.DbEntity;
 import com.arialyy.aria.util.ALog;
-import com.arialyy.aria.util.CheckUtil;
 import com.arialyy.aria.util.CommonUtil;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
 
 /**
  * Created by AriaL on 2017/6/29.
  * 下载任务组
  */
-public class DownloadGroupTarget
-    extends BaseGroupTarget {
+public class DownloadGroupTarget extends BaseGroupTarget {
   private final String TAG = "DownloadGroupTarget";
+  /**
+   * 子任务下载地址,
+   */
+  private List mUrls = new ArrayList<>();
+
+  /**
+   * 子任务文件名
+   */
+  private List mSubNameTemp = new ArrayList<>();
 
   DownloadGroupTarget(DownloadGroupEntity groupEntity, String targetName) {
     this.mTargetName = targetName;
@@ -80,16 +93,190 @@ public class DownloadGroupTarget
    * 如果你是使用{@link DownloadReceiver#load(DownloadGroupEntity)}进行下载操作,那么你需要设置任务组的下载地址
    */
   public DownloadGroupTarget setGroupUrl(List urls) {
-    CheckUtil.checkDownloadUrls(urls);
     mUrls.clear();
     mUrls.addAll(urls);
-    mEntity.setGroupName(CommonUtil.getMd5Code(urls));
-    mEntity.update();
     return this;
   }
 
-  @Override public boolean isRunning() {
-    DownloadGroupTask task = DownloadGroupTaskQueue.getInstance().getTask(mEntity.getKey());
-    return task != null && task.isRunning();
+  /**
+   * 设置子任务文件名,该方法必须在{@link #setDirPath(String)}之后调用,否则不生效
+   *
+   * @deprecated {@link #setSubFileName(List)} 请使用该api
+   */
+  @Deprecated public DownloadGroupTarget setSubTaskFileName(List subTaskFileName) {
+    return setSubFileName(subTaskFileName);
+  }
+
+  /**
+   * 设置子任务文件名,该方法必须在{@link #setDirPath(String)}之后调用,否则不生效
+   */
+  public DownloadGroupTarget setSubFileName(List subTaskFileName) {
+    if (subTaskFileName == null || subTaskFileName.isEmpty()) {
+      return this;
+    }
+    mSubNameTemp.clear();
+    mSubNameTemp.addAll(subTaskFileName);
+    return this;
+  }
+
+  /**
+   * 创建子任务
+   */
+  private List createSubTask() {
+    List list = new ArrayList<>();
+    for (int i = 0, len = mUrls.size(); i < len; i++) {
+      DownloadEntity entity = new DownloadEntity();
+      entity.setUrl(mUrls.get(i));
+      String fileName =
+          mSubNameTemp.isEmpty() ? CommonUtil.createFileName(entity.getUrl()) : mSubNameTemp.get(i);
+      entity.setDownloadPath(mEntity.getDirPath() + "/" + fileName);
+      entity.setGroupName(mGroupName);
+      entity.setGroupChild(true);
+      entity.setFileName(fileName);
+      entity.insert();
+      list.add(entity);
+    }
+    return list;
+  }
+
+  @Override protected int getTargetType() {
+    return GROUP_HTTP;
+  }
+
+  @Override protected boolean checkEntity() {
+    if (getTargetType() == GROUP_HTTP) {
+      if (!checkDirPath()) {
+        return false;
+      }
+
+      if (!checkSubName()) {
+        return false;
+      }
+
+      if (!checkUrls()) {
+        return false;
+      }
+
+      //文件夹路径通过后,并且该实体没有子任务,则创建子任务
+      if (mEntity.getSubTask() == null || mEntity.getSubTask().isEmpty()) {
+        mEntity.setSubTasks(createSubTask());
+        mTaskEntity.update();
+      } else {
+        updateSingleSubFileName();
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * 更新所有改动的子任务文件名
+   */
+  private void updateSingleSubFileName() {
+    List entities = mEntity.getSubTask();
+    int i = 0;
+    for (DownloadEntity entity : entities) {
+      if (i < mSubNameTemp.size()) {
+        String newName = mSubNameTemp.get(i);
+        updateSingleSubFileName(entity, newName);
+      }
+      i++;
+    }
+  }
+
+  /**
+   * 检查urls是否合法,并删除不合法的子任务
+   *
+   * @return {@code true} 合法
+   */
+  private boolean checkUrls() {
+    if (mUrls.isEmpty()) {
+      ALog.e(TAG, "下载失败,子任务下载列表为null");
+      return false;
+    }
+    Set delItem = new HashSet<>();
+
+    Map reSetUrl = new WeakHashMap<>();
+    int i = 0;
+    for (String url : mUrls) {
+      if (TextUtils.isEmpty(url)) {
+        ALog.e(TAG, "子任务url为null,即将删除该子任务。");
+        delItem.add(i);
+        continue;
+      } else if (!url.startsWith("http")) {
+        //} else if (!url.startsWith("http") && !url.startsWith("ftp")) {
+        ALog.e(TAG, "子任务url【" + url + "】错误,即将删除该子任务。");
+        delItem.add(i);
+        continue;
+      }
+      int index = url.indexOf("://");
+      if (index == -1) {
+        ALog.e(TAG, "子任务url【" + url + "】不合法,即将删除该子任务。");
+        delItem.add(i);
+        continue;
+      }
+
+      String temp = url.substring(index + 3, url.length());
+      if (temp.contains("//")) {
+        temp = url.substring(0, index + 3) + temp.replaceAll("//", "/");
+        ALog.w(TAG, "url中含有//,//将转换为/,转换后的url为:" + temp);
+        reSetUrl.put(i, temp);
+      }
+      i++;
+    }
+
+    Set keys = reSetUrl.keySet();
+    for (Integer index : keys) {
+      mUrls.set(index, reSetUrl.get(index));
+    }
+
+    for (int index : delItem) {
+      mUrls.remove(index);
+      if (mSubNameTemp != null && !mSubNameTemp.isEmpty()) {
+        mSubNameTemp.remove(index);
+      }
+    }
+
+    mEntity.setGroupName(CommonUtil.getMd5Code(mUrls));
+    mEntity.update();
+
+    return true;
+  }
+
+  /**
+   * 更新单个子任务文件名
+   */
+  private void updateSingleSubFileName(DownloadEntity entity, String newName) {
+    if (!newName.equals(entity.getFileName())) {
+      String oldPath = mEntity.getDirPath() + "/" + entity.getFileName();
+      String newPath = mEntity.getDirPath() + "/" + newName;
+      File oldFile = new File(oldPath);
+      if (oldFile.exists()) {
+        oldFile.renameTo(new File(newPath));
+      }
+      CommonUtil.renameDownloadConfig(oldFile.getName(), newName);
+      DbEntity.exeSql(
+          "UPDATE DownloadTaskEntity SET key='" + newPath + "' WHERE key='" + oldPath + "'");
+      entity.setDownloadPath(newPath);
+      entity.setFileName(newName);
+      entity.update();
+    }
+  }
+
+  /**
+   * 如果用户设置了子任务文件名,检查子任务文件名
+   *
+   * @return {@code true} 合法
+   */
+  private boolean checkSubName() {
+    if (mSubNameTemp == null || mSubNameTemp.isEmpty()) {
+      return true;
+    }
+    if (mUrls.size() != mSubNameTemp.size()) {
+      ALog.e(TAG, "子任务文件名必须和子任务数量一致");
+      return false;
+    }
+
+    return true;
   }
 }
diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java
index 21ca515b..e4efccb6 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java
@@ -27,7 +27,6 @@ import com.arialyy.aria.core.inf.AbsReceiver;
 import com.arialyy.aria.core.inf.AbsTarget;
 import com.arialyy.aria.core.scheduler.DownloadGroupSchedulers;
 import com.arialyy.aria.core.scheduler.DownloadSchedulers;
-import com.arialyy.aria.core.scheduler.ISchedulerListener;
 import com.arialyy.aria.orm.DbEntity;
 import com.arialyy.aria.util.CheckUtil;
 import com.arialyy.aria.util.CommonUtil;
@@ -41,7 +40,6 @@ import java.util.Set;
  */
 public class DownloadReceiver extends AbsReceiver {
   private final String TAG = "DownloadReceiver";
-  public ISchedulerListener listener;
 
   /**
    * 设置最大下载速度,单位:kb
@@ -114,8 +112,18 @@ public class DownloadReceiver extends AbsReceiver {
 
   /**
    * 加载下载地址,如果任务组的中的下载地址改变了,则任务从新的一个任务组
+   *
+   * @param urls 人刷谁
    */
+  @Deprecated
   public DownloadGroupTarget load(List urls) {
+    return loadGroup(urls);
+  }
+
+  /**
+   * 加载下载地址,如果任务组的中的下载地址改变了,则任务从新的一个任务组
+   */
+  public DownloadGroupTarget loadGroup(List urls) {
     CheckUtil.checkDownloadUrls(urls);
     return new DownloadGroupTarget(urls, targetName);
   }
@@ -170,12 +178,24 @@ public class DownloadReceiver extends AbsReceiver {
   }
 
   /**
-   * 使用任务组实体执行任务组的实体执行任务组的下载操作
+   * 使用任务组实体执行任务组的实体执行任务组的下载操作,后续版本会删除该api
    *
    * @param groupEntity 如果加载的任务实体没有子项的下载地址,
    * 那么你需要使用{@link DownloadGroupTarget#setGroupUrl(List)}设置子项的下载地址
+   * @deprecated {@link #loadGroup(DownloadGroupEntity)}
    */
+  @Deprecated
   public DownloadGroupTarget load(DownloadGroupEntity groupEntity) {
+    return loadGroup(groupEntity);
+  }
+
+  /**
+   * 使用任务组实体执行任务组的实体执行任务组的下载操作
+   *
+   * @param groupEntity 如果加载的任务实体没有子项的下载地址,
+   * 那么你需要使用{@link DownloadGroupTarget#setGroupUrl(List)}设置子项的下载地址
+   */
+  public DownloadGroupTarget loadGroup(DownloadGroupEntity groupEntity) {
     return new DownloadGroupTarget(groupEntity, targetName);
   }
 
@@ -232,7 +252,6 @@ public class DownloadReceiver extends AbsReceiver {
 
   @Override public void destroy() {
     targetName = null;
-    listener = null;
   }
 
   /**
diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java
index 5a8b20ed..d29b6d79 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java
@@ -16,35 +16,27 @@
 package com.arialyy.aria.core.download;
 
 import android.support.annotation.NonNull;
-import android.text.TextUtils;
-import com.arialyy.aria.core.inf.AbsDownloadTarget;
-import com.arialyy.aria.core.manager.TEManager;
-import com.arialyy.aria.core.queue.DownloadTaskQueue;
-import com.arialyy.aria.orm.DbEntity;
-import com.arialyy.aria.util.CommonUtil;
-import java.io.File;
+import com.arialyy.aria.core.common.RequestEnum;
+import com.arialyy.aria.core.delegate.HttpHeaderDelegate;
+import com.arialyy.aria.core.inf.IHttpHeaderTarget;
+import java.util.Map;
 
 /**
  * Created by lyy on 2016/12/5.
  * https://github.com/AriaLyy/Aria
  */
-public class DownloadTarget
-    extends AbsDownloadTarget {
-  protected String url;
+public class DownloadTarget extends BaseNormalTarget
+    implements IHttpHeaderTarget {
+  private static final String TAG = "DownloadTarget";
+  private HttpHeaderDelegate mDelegate;
 
   DownloadTarget(DownloadEntity entity, String targetName) {
     this(entity, targetName, false);
   }
 
   DownloadTarget(DownloadEntity entity, String targetName, boolean refreshInfo) {
-    this.url = entity.getUrl();
-    mTargetName = targetName;
-    mTaskEntity = TEManager.getInstance().getTEntity(DownloadTaskEntity.class, url);
-    if (mTaskEntity == null) {
-      mTaskEntity = TEManager.getInstance().createTEntity(DownloadTaskEntity.class, entity);
-    }
-    mEntity = mTaskEntity.entity;
-    mTaskEntity.refreshInfo = refreshInfo;
+    initTarget(entity, targetName, refreshInfo);
+    initTHis();
   }
 
   DownloadTarget(String url, String targetName) {
@@ -52,14 +44,12 @@ public class DownloadTarget
   }
 
   DownloadTarget(String url, String targetName, boolean refreshInfo) {
-    this.url = url;
-    mTargetName = targetName;
-    mTaskEntity = TEManager.getInstance().getTEntity(DownloadTaskEntity.class, url);
-    if (mTaskEntity == null) {
-      mTaskEntity = TEManager.getInstance().createTEntity(DownloadTaskEntity.class, url);
-    }
-    mEntity = mTaskEntity.entity;
-    mTaskEntity.refreshInfo = refreshInfo;
+    initTarget(url, targetName, refreshInfo);
+    initTHis();
+  }
+
+  private void initTHis() {
+    mDelegate = new HttpHeaderDelegate<>(mTaskEntity);
   }
 
   /**
@@ -75,25 +65,15 @@ public class DownloadTarget
   }
 
   /**
-   * 将任务设置为最高优先级任务,最高优先级任务有以下特点:
-   * 1、在下载队列中,有且只有一个最高优先级任务
-   * 2、最高优先级任务会一直存在,直到用户手动暂停或任务完成
-   * 3、任务调度器不会暂停最高优先级任务
-   * 4、用户手动暂停或任务完成后,第二次重新执行该任务,该命令将失效
-   * 5、如果下载队列中已经满了,则会停止队尾的任务,当高优先级任务完成后,该队尾任务将自动执行
-   * 6、把任务设置为最高优先级任务后,将自动执行任务,不需要重新调用start()启动任务
-   */
-  @Override public void setHighestPriority() {
-    super.setHighestPriority();
-  }
-
-  /**
-   * 下载任务是否存在
+   * 设置文件存储路径
+   * 该api后续版本会删除
    *
-   * @return {@code true}任务存在
+   * @param downloadPath 文件保存路径
+   * @deprecated {@link #setFilePath(String)} 请使用这个api
    */
-  @Override public boolean taskExists() {
-    return DownloadTaskQueue.getInstance().getTask(mEntity.getUrl()) != null;
+  @Deprecated
+  public DownloadTarget setDownloadPath(@NonNull String downloadPath) {
+    return setFilePath(downloadPath);
   }
 
   /**
@@ -101,38 +81,13 @@ public class DownloadTarget
    * 如:原文件路径 /mnt/sdcard/test.zip
    * 如果需要将test.zip改为game.zip,只需要重新设置文件路径为:/mnt/sdcard/game.zip
    *
-   * @param downloadPath 路径必须为文件路径,不能为文件夹路径
+   * @param filePath 路径必须为文件路径,不能为文件夹路径
    */
-  public DownloadTarget setDownloadPath(@NonNull String downloadPath) {
-    if (TextUtils.isEmpty(downloadPath)) {
-      throw new IllegalArgumentException("文件保持路径不能为null");
-    }
-    File file = new File(downloadPath);
-    if (file.isDirectory()) {
-      throw new IllegalArgumentException("保存路径不能为文件夹,路径需要是完整的文件路径,如:/mnt/sdcard/game.zip");
-    }
-    if (!downloadPath.equals(mEntity.getDownloadPath())) {
-      if (!mTaskEntity.refreshInfo && DbEntity.checkDataExist(DownloadEntity.class,
-          "downloadPath=?", downloadPath)) {
-        throw new IllegalArgumentException("保存路径【" + downloadPath + "】已经被其它任务占用,请设置其它保存路径");
-      }
-      File oldFile = new File(mEntity.getDownloadPath());
-      File newFile = new File(downloadPath);
-      if (TextUtils.isEmpty(mEntity.getDownloadPath()) || oldFile.renameTo(newFile)) {
-        mEntity.setDownloadPath(downloadPath);
-        mEntity.setFileName(newFile.getName());
-        mTaskEntity.key = downloadPath;
-        mTaskEntity.update();
-        CommonUtil.renameDownloadConfig(oldFile.getName(), newFile.getName());
-      }
-    }
+  public DownloadTarget setFilePath(@NonNull String filePath) {
+    mTempFilePath = filePath;
     return this;
   }
 
-  public DownloadEntity getDownloadEntity() {
-    return mEntity;
-  }
-
   /**
    * 从header中获取文件描述信息
    */
@@ -140,17 +95,35 @@ public class DownloadTarget
     return mEntity.getDisposition();
   }
 
-  /**
-   * 是否在下载
-   *
-   * @deprecated {@link #isRunning()}
-   */
-  public boolean isDownloading() {
-    return isRunning();
+  @Override protected int getTargetType() {
+    return HTTP;
+  }
+
+  @Override public DownloadTarget addHeader(@NonNull String key, @NonNull String value) {
+    return mDelegate.addHeader(key, value);
+  }
+
+  @Override public DownloadTarget addHeaders(Map headers) {
+    return mDelegate.addHeaders(headers);
+  }
+
+  @Override public DownloadTarget setRequestMode(RequestEnum requestEnum) {
+    return mDelegate.setRequestMode(requestEnum);
+  }
+
+  @Override public DownloadTarget setHeaderMd5Key(String md5Key) {
+    return mDelegate.setHeaderMd5Key(md5Key);
+  }
+
+  @Override public DownloadTarget setHeaderContentLengthKey(String contentLength) {
+    return mDelegate.setHeaderContentLengthKey(contentLength);
+  }
+
+  @Override public DownloadTarget setHeaderDispositionKey(String dispositionKey) {
+    return mDelegate.setHeaderDispositionKey(dispositionKey);
   }
 
-  @Override public boolean isRunning() {
-    DownloadTask task = DownloadTaskQueue.getInstance().getTask(mEntity.getKey());
-    return task != null && task.isRunning();
+  @Override public DownloadTarget setHeaderDispositionFileKey(String dispositionFileKey) {
+    return mDelegate.setHeaderDispositionFileKey(dispositionFileKey);
   }
 }
diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDownloadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDownloadTarget.java
index f7c51df7..ffeacb14 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDownloadTarget.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDownloadTarget.java
@@ -16,18 +16,20 @@
 package com.arialyy.aria.core.download;
 
 import android.text.TextUtils;
+import com.arialyy.aria.core.delegate.FtpDelegate;
 import com.arialyy.aria.core.inf.AbsTaskEntity;
+import com.arialyy.aria.core.inf.IFtpTarget;
 import com.arialyy.aria.core.manager.TEManager;
-import com.arialyy.aria.core.queue.DownloadGroupTaskQueue;
 import com.arialyy.aria.util.ALog;
 
 /**
  * Created by Aria.Lao on 2017/7/26.
  * ftp文件夹下载
  */
-public class FtpDirDownloadTarget
-    extends BaseGroupTarget {
+public class FtpDirDownloadTarget extends BaseGroupTarget implements
+    IFtpTarget {
   private final String TAG = "FtpDirDownloadTarget";
+  private FtpDelegate mDelegate;
 
   FtpDirDownloadTarget(String url, String targetName) {
     mTargetName = targetName;
@@ -42,51 +44,57 @@ public class FtpDirDownloadTarget
     }
     mTaskEntity.requestType = AbsTaskEntity.D_FTP_DIR;
     mEntity = mTaskEntity.entity;
+
+    mDelegate = new FtpDelegate<>(mTaskEntity);
   }
 
-  /**
-   * 设置字符编码
-   */
-  public FtpDirDownloadTarget charSet(String charSet) {
-    if (TextUtils.isEmpty(charSet)) return this;
-    mTaskEntity.charSet = charSet;
-    return this;
+  @Override protected int getTargetType() {
+    return GROUP_FTP_DIR;
   }
 
-  /**
-   * ftp 用户登录信息
-   *
-   * @param userName ftp用户名
-   * @param password ftp用户密码
-   */
-  public FtpDirDownloadTarget login(String userName, String password) {
-    return login(userName, password, null);
+  @Override protected boolean checkEntity() {
+    return getTargetType() == GROUP_FTP_DIR && checkDirPath() && checkUrl();
   }
 
   /**
-   * ftp 用户登录信息
+   * 检查普通任务的下载地址
    *
-   * @param userName ftp用户名
-   * @param password ftp用户密码
-   * @param account ftp账号
+   * @return {@code true}地址合法
    */
-  public FtpDirDownloadTarget login(String userName, String password, String account) {
-    if (TextUtils.isEmpty(userName)) {
-      ALog.e(TAG, "用户名不能为null");
-      return this;
-    } else if (TextUtils.isEmpty(password)) {
-      ALog.e(TAG, "密码不能为null");
-      return this;
+  private boolean checkUrl() {
+    final String url = mGroupName;
+    if (TextUtils.isEmpty(url)) {
+      ALog.e(TAG, "下载失败,url为null");
+      return false;
+    } else if (!url.startsWith("ftp")) {
+      ALog.e(TAG, "下载失败,url【" + url + "】错误");
+      return false;
     }
-    mTaskEntity.urlEntity.needLogin = true;
-    mTaskEntity.urlEntity.user = userName;
-    mTaskEntity.urlEntity.password = password;
-    mTaskEntity.urlEntity.account = account;
-    return this;
+    int index = url.indexOf("://");
+    if (index == -1) {
+      ALog.e(TAG, "下载失败,url【" + url + "】不合法");
+      return false;
+    }
+    String temp = url.substring(index + 3, url.length());
+    if (temp.contains("//")) {
+      temp = url.substring(0, index + 3) + temp.replaceAll("//", "/");
+      ALog.w(TAG, "url中含有//,//将转换为/,转换后的url为:" + temp);
+      mGroupName = temp;
+      mEntity.setGroupName(temp);
+      mEntity.update();
+    }
+    return true;
+  }
+
+  @Override public FtpDirDownloadTarget charSet(String charSet) {
+    return mDelegate.charSet(charSet);
+  }
+
+  @Override public FtpDirDownloadTarget login(String userName, String password) {
+    return mDelegate.login(userName, password);
   }
 
-  @Override public boolean isRunning() {
-    DownloadGroupTask task = DownloadGroupTaskQueue.getInstance().getTask(mEntity.getKey());
-    return task != null && task.isRunning();
+  @Override public FtpDirDownloadTarget login(String userName, String password, String account) {
+    return mDelegate.login(userName, password, account);
   }
 }
diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/FtpDownloadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/FtpDownloadTarget.java
index ccc18455..d9b7b54b 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/download/FtpDownloadTarget.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/download/FtpDownloadTarget.java
@@ -16,21 +16,22 @@
 package com.arialyy.aria.core.download;
 
 import android.support.annotation.NonNull;
-import android.text.TextUtils;
+import com.arialyy.aria.core.delegate.FtpDelegate;
 import com.arialyy.aria.core.inf.AbsTaskEntity;
-import com.arialyy.aria.util.ALog;
+import com.arialyy.aria.core.inf.IFtpTarget;
 import com.arialyy.aria.util.CommonUtil;
-import java.io.File;
 
 /**
  * Created by lyy on 2016/12/5.
  * https://github.com/AriaLyy/Aria
  */
-public class FtpDownloadTarget extends DownloadTarget {
+public class FtpDownloadTarget extends BaseNormalTarget
+    implements IFtpTarget {
   private final String TAG = "FtpDownloadTarget";
+  private FtpDelegate mDelegate;
 
   FtpDownloadTarget(DownloadEntity entity, String targetName, boolean refreshInfo) {
-    super(entity, targetName, refreshInfo);
+    initTarget(entity, targetName, refreshInfo);
     init(refreshInfo);
   }
 
@@ -39,7 +40,7 @@ public class FtpDownloadTarget extends DownloadTarget {
   }
 
   FtpDownloadTarget(String url, String targetName, boolean refreshInfo) {
-    super(url, targetName);
+    initTarget(url, targetName, refreshInfo);
     init(refreshInfo);
   }
 
@@ -49,6 +50,19 @@ public class FtpDownloadTarget extends DownloadTarget {
     mTaskEntity.urlEntity = CommonUtil.getFtpUrlInfo(url);
     mTaskEntity.refreshInfo = refreshInfo;
     mTaskEntity.requestType = AbsTaskEntity.D_FTP;
+
+    mDelegate = new FtpDelegate<>(mTaskEntity);
+  }
+
+  /**
+   * 设置文件保存文件夹路径
+   *
+   * @param filePath 文件保存路径
+   * @deprecated {@link #setFilePath(String)} 请使用这个api
+   */
+  @Deprecated
+  public FtpDownloadTarget setDownloadPath(@NonNull String filePath) {
+    return setFilePath(filePath);
   }
 
   /**
@@ -57,69 +71,26 @@ public class FtpDownloadTarget extends DownloadTarget {
    * 1、如果保存路径是该文件的保存路径,如:/mnt/sdcard/file.zip,则使用路径中的文件名file.zip
    * 2、如果保存路径是文件夹路径,如:/mnt/sdcard/,则使用FTP服务器该文件的文件名
    *
-   * @param downloadPath 路径必须为文件路径,不能为文件夹路径
+   * @param filePath 路径必须为文件路径,不能为文件夹路径
    */
-  @Override public FtpDownloadTarget setDownloadPath(@NonNull String downloadPath) {
-    if (TextUtils.isEmpty(downloadPath)) {
-      throw new IllegalArgumentException("文件保持路径不能为null");
-    }
-    File file = new File(downloadPath);
-    if (file.isDirectory()) {
-      downloadPath += mEntity.getFileName();
-    }
-    if (!downloadPath.equals(mEntity.getDownloadPath())) {
-      File oldFile = new File(mEntity.getDownloadPath());
-      File newFile = new File(downloadPath);
-      if (TextUtils.isEmpty(mEntity.getDownloadPath()) || oldFile.renameTo(newFile)) {
-        mEntity.setDownloadPath(downloadPath);
-        mEntity.setFileName(newFile.getName());
-        mTaskEntity.key = downloadPath;
-        mEntity.update();
-        mTaskEntity.update();
-        CommonUtil.renameDownloadConfig(oldFile.getName(), newFile.getName());
-      }
-    }
+  public FtpDownloadTarget setFilePath(@NonNull String filePath) {
+    mTempFilePath = filePath;
     return this;
   }
 
-  /**
-   * 设置字符编码
-   */
-  public FtpDownloadTarget charSet(String charSet) {
-    if (TextUtils.isEmpty(charSet)) return this;
-    mTaskEntity.charSet = charSet;
-    return this;
+  @Override protected int getTargetType() {
+    return FTP;
   }
 
-  /**
-   * ftp 用户登录信息
-   *
-   * @param userName ftp用户名
-   * @param password ftp用户密码
-   */
-  public FtpDownloadTarget login(String userName, String password) {
-    return login(userName, password, null);
+  @Override public FtpDownloadTarget charSet(String charSet) {
+    return mDelegate.charSet(charSet);
   }
 
-  /**
-   * ftp 用户登录信息
-   *
-   * @param userName ftp用户名
-   * @param password ftp用户密码
-   * @param account ftp账号
-   */
-  public FtpDownloadTarget login(String userName, String password, String account) {
-    if (TextUtils.isEmpty(userName)) {
-      ALog.e(TAG, "用户名不能为null");
-      return this;
-    } else if (TextUtils.isEmpty(password)) {
-      ALog.e(TAG, "密码不能为null");
-      return this;
-    }
-    mTaskEntity.urlEntity.needLogin = true;
-    mTaskEntity.urlEntity.user = userName;
-    mTaskEntity.urlEntity.password = password;
-    mTaskEntity.urlEntity.account = account;
-    return this;
+  @Override public FtpDownloadTarget login(String userName, String password) {
+    return mDelegate.login(userName, password);
+  }
+
+  @Override public FtpDownloadTarget login(String userName, String password, String account) {
+    return mDelegate.login(userName, password, account);
   }
 }
diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsDownloadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsDownloadTarget.java
deleted file mode 100644
index f5909a1f..00000000
--- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsDownloadTarget.java
+++ /dev/null
@@ -1,151 +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.core.inf;
-
-import android.text.TextUtils;
-import com.arialyy.aria.core.AriaManager;
-import com.arialyy.aria.core.command.normal.NormalCmdFactory;
-import com.arialyy.aria.core.download.DownloadEntity;
-import com.arialyy.aria.util.ALog;
-import com.arialyy.aria.util.CommonUtil;
-
-/**
- * Created by lyy on 2017/2/28.
- */
-public abstract class AbsDownloadTarget
-    extends AbsTarget {
-
-  /**
-   * 如果你的下载链接的header中含有md5码信息,那么你可以通过设置key,来获取从header获取该md5码信息。
-   * key默认值为:Content-MD5
-   * 获取md5信息:{@link DownloadEntity#getMd5Code()}
-   */
-  public TARGET setHeaderMd5Key(String md5Key) {
-    if (TextUtils.isEmpty(md5Key)) return (TARGET) this;
-    mTaskEntity.md5Key = md5Key;
-    if (TextUtils.isEmpty(mTaskEntity.md5Key) || !mTaskEntity.md5Key.equals(md5Key)) {
-      mTaskEntity.update();
-    }
-    return (TARGET) this;
-  }
-
-  /**
-   * 如果你的文件长度是放在header中,那么你需要配置key来让Aria知道正确的文件长度
-   * key默认值为:Content-Length
-   */
-  public TARGET setHeaderContentLengthKey(String contentLength) {
-    if (TextUtils.isEmpty(contentLength)) return (TARGET) this;
-    mTaskEntity.contentLength = contentLength;
-    if (TextUtils.isEmpty(mTaskEntity.contentLength) || !mTaskEntity.contentLength.equals(
-        contentLength)) {
-      mTaskEntity.update();
-    }
-    return (TARGET) this;
-  }
-
-  /**
-   * 如果你的下载链接的header中含有文件描述信息,那么你可以通过设置key,来获取从header获取该文件描述信息。
-   * key默认值为:Content-Disposition
-   * 获取文件描述信息:{@link DownloadEntity#getDisposition()}
-   */
-  public TARGET setHeaderDispositionKey(String dispositionKey) {
-    if (TextUtils.isEmpty(dispositionKey)) return (TARGET) this;
-    mTaskEntity.dispositionKey = dispositionKey;
-    if (TextUtils.isEmpty(mTaskEntity.dispositionKey) || !mTaskEntity.dispositionKey.equals(
-        dispositionKey)) {
-      mTaskEntity.save();
-    }
-    return (TARGET) this;
-  }
-
-  /**
-   * 从文件描述信息{@link #setHeaderDispositionKey(String)}中含有文件名信息,你可以通过设置key来获取header中的文件名
-   * key默认值为:attachment;filename
-   * 获取文件名信息:{@link DownloadEntity#getServerFileName()}
-   */
-  public TARGET setHeaderDispositionFileKey(String dispositionFileKey) {
-    if (TextUtils.isEmpty(dispositionFileKey)) return (TARGET) this;
-    mTaskEntity.dispositionFileKey = dispositionFileKey;
-    if (TextUtils.isEmpty(mTaskEntity.dispositionFileKey) || !mTaskEntity.dispositionFileKey.equals(
-        dispositionFileKey)) {
-      mTaskEntity.save();
-    }
-    return (TARGET) this;
-  }
-
-  /**
-   * 将任务设置为最高优先级任务,最高优先级任务有以下特点:
-   * 1、在下载队列中,有且只有一个最高优先级任务
-   * 2、最高优先级任务会一直存在,直到用户手动暂停或任务完成
-   * 3、任务调度器不会暂停最高优先级任务
-   * 4、用户手动暂停或任务完成后,第二次重新执行该任务,该命令将失效
-   * 5、如果下载队列中已经满了,则会停止队尾的任务,当高优先级任务完成后,该队尾任务将自动执行
-   * 6、把任务设置为最高优先级任务后,将自动执行任务,不需要重新调用start()启动任务
-   */
-  protected void setHighestPriority() {
-    AriaManager.getInstance(AriaManager.APP)
-        .setCmd(CommonUtil.createNormalCmd(mTargetName, mTaskEntity,
-            NormalCmdFactory.TASK_HIGHEST_PRIORITY, checkTaskType()))
-        .exe();
-  }
-
-  /**
-   * 重定向后,新url的key,默认为location
-   */
-  public void setRedirectUrlKey(String redirectUrlKey) {
-    if (TextUtils.isEmpty(redirectUrlKey)) {
-      ALog.e("AbsDownloadTarget", "重定向后,新url的key不能为null");
-      return;
-    }
-    mTaskEntity.redirectUrlKey = redirectUrlKey;
-  }
-
-  /**
-   * 获取任务文件大小
-   *
-   * @return 文件大小
-   */
-  public long getFileSize() {
-    return getSize();
-  }
-
-  /**
-   * 获取单位转换后的文件大小
-   *
-   * @return 文件大小{@code xxx mb}
-   */
-  public String getConvertFileSize() {
-    return getConvertSize();
-  }
-
-  /**
-   * 添加任务
-   */
-  public void add() {
-    AriaManager.getInstance(AriaManager.APP)
-        .setCmd(CommonUtil.createNormalCmd(mTargetName, mTaskEntity, NormalCmdFactory.TASK_CREATE,
-            checkTaskType()))
-        .exe();
-  }
-
-  /**
-   * 重新下载
-   */
-  public void reStart() {
-    cancel();
-    start();
-  }
-}
diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java
index 6e2d69c4..742f4af6 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java
@@ -15,13 +15,11 @@
  */
 package com.arialyy.aria.core.inf;
 
-import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import com.arialyy.aria.core.AriaManager;
 import com.arialyy.aria.core.command.ICmd;
 import com.arialyy.aria.core.command.normal.CancelCmd;
 import com.arialyy.aria.core.command.normal.NormalCmdFactory;
-import com.arialyy.aria.core.common.RequestEnum;
 import com.arialyy.aria.core.download.DownloadGroupTaskEntity;
 import com.arialyy.aria.core.download.DownloadTaskEntity;
 import com.arialyy.aria.core.manager.TEManager;
@@ -30,8 +28,6 @@ import com.arialyy.aria.util.ALog;
 import com.arialyy.aria.util.CommonUtil;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 /**
  * Created by AriaL on 2017/7/3.
@@ -65,27 +61,12 @@ public abstract class AbsTarget headers) {
-    return addHeaders(headers, false);
-  }
-
-  /**
-   * 给url请求添加头部
-   *
-   * @param refreshHeader 更新数据库中保存的头部信息
-   */
-  public TARGET addHeaders(Map headers, boolean refreshHeader) {
-    if (headers != null && headers.size() > 0) {
-      Set keys = headers.keySet();
-      for (String key : keys) {
-        mTaskEntity.headers.put(key, headers.get(key));
-      }
-    }
-    if (refreshHeader) {
-      mTaskEntity.update();
-    }
-    return (TARGET) this;
-  }
-
-  /**
-   * 设置请求类型,POST或GET,默认为在GET
-   * 只试用于HTTP请求
+   * 检查实体是否合法
    *
-   * @param requestEnum {@link RequestEnum}
+   * @return {@code true} 合法
    */
-  public TARGET setRequestMode(RequestEnum requestEnum) {
-    mTaskEntity.requestEnum = requestEnum;
-    return (TARGET) this;
-  }
-
-  /**
-   * 开始任务
-   */
-  @Override public void start() {
-    AriaManager.getInstance(AriaManager.APP)
-        .setCmd(CommonUtil.createNormalCmd(mTargetName, mTaskEntity, NormalCmdFactory.TASK_START,
-            checkTaskType()))
-        .exe();
-  }
+  protected abstract boolean checkEntity();
 
   protected int checkTaskType() {
     int taskType = 0;
@@ -238,53 +156,76 @@ public abstract class AbsTarget cmds = new ArrayList<>();
-    int taskType = checkTaskType();
-    cmds.add(
-        CommonUtil.createNormalCmd(mTargetName, mTaskEntity, NormalCmdFactory.TASK_STOP, taskType));
-    cmds.add(CommonUtil.createNormalCmd(mTargetName, mTaskEntity, NormalCmdFactory.TASK_START,
-        taskType));
-    AriaManager.getInstance(AriaManager.APP).setCmds(cmds).exe();
+    if (checkEntity()) {
+      List cmds = new ArrayList<>();
+      int taskType = checkTaskType();
+      cmds.add(
+          CommonUtil.createNormalCmd(mTargetName, mTaskEntity, NormalCmdFactory.TASK_STOP,
+              taskType));
+      cmds.add(CommonUtil.createNormalCmd(mTargetName, mTaskEntity, NormalCmdFactory.TASK_START,
+          taskType));
+      AriaManager.getInstance(AriaManager.APP).setCmds(cmds).exe();
+    }
   }
 
   /**
@@ -294,35 +235,23 @@ public abstract class AbsTarget 0) {
-      tempUrl = url.substring(0, end);
-      int tempEnd = tempUrl.lastIndexOf("/");
-      if (tempEnd > 0) {
-        fileName = tempUrl.substring(tempEnd + 1, tempUrl.length());
-      }
-    } else {
-      int tempEnd = url.lastIndexOf("/");
-      if (tempEnd > 0) {
-        fileName = url.substring(tempEnd + 1, url.length());
-      }
-    }
-    if (TextUtils.isEmpty(fileName)) {
-      fileName = CommonUtil.keyToHashKey(url);
+  public void reStart() {
+    if (checkEntity()) {
+      cancel();
+      start();
     }
-    return fileName;
   }
+
+
 }
diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsUploadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsUploadTarget.java
deleted file mode 100644
index be6541f1..00000000
--- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsUploadTarget.java
+++ /dev/null
@@ -1,65 +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.core.inf;
-
-import android.support.annotation.NonNull;
-import com.arialyy.aria.core.queue.UploadTaskQueue;
-import com.arialyy.aria.core.upload.UploadEntity;
-import com.arialyy.aria.core.upload.UploadTask;
-import com.arialyy.aria.core.upload.UploadTaskEntity;
-import com.arialyy.aria.util.CheckUtil;
-
-/**
- * Created by AriaL on 2017/6/29.
- * 任务组超类
- */
-public abstract class AbsUploadTarget
-    extends AbsTarget {
-
-  /**
-   * 设置上传路径
-   *
-   * @param uploadUrl 上传路径
-   */
-  public TARGET setUploadUrl(@NonNull String uploadUrl) {
-    uploadUrl = CheckUtil.checkUrl(uploadUrl);
-    if (mEntity.getUrl().equals(uploadUrl)) return (TARGET) this;
-    mEntity.setUrl(uploadUrl);
-    mEntity.update();
-    return (TARGET) this;
-  }
-
-  /**
-   * 下载任务是否存在
-   */
-  @Override public boolean taskExists() {
-    return UploadTaskQueue.getInstance().getTask(mEntity.getFilePath()) != null;
-  }
-
-  /**
-   * 是否在下载
-   *
-   * @deprecated {@link #isRunning()}
-   */
-  public boolean isUploading() {
-    return isRunning();
-  }
-
-  @Override public boolean isRunning() {
-    UploadTask task = UploadTaskQueue.getInstance().getTask(mEntity.getKey());
-    return task != null && task.isRunning();
-  }
-}
diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/IFtpTarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/IFtpTarget.java
new file mode 100644
index 00000000..5f5a68fd
--- /dev/null
+++ b/Aria/src/main/java/com/arialyy/aria/core/inf/IFtpTarget.java
@@ -0,0 +1,45 @@
+/*
+ * 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.inf;
+
+/**
+ * Created by laoyuyu on 2018/3/9.
+ */
+public interface IFtpTarget {
+  /**
+   * 设置字符编码
+   */
+  TARGET charSet(String charSet);
+
+  /**
+   * ftp 用户登录信。
+   * 设置登录信息需要在设置上传链接之后{@link #setUploadUrl(String)}
+   *
+   * @param userName ftp用户名
+   * @param password ftp用户密码
+   */
+  TARGET login(String userName, String password);
+
+  /**
+   * ftp 用户登录信息
+   * 设置登录信息需要在设置上传链接之后{@link #setUploadUrl(String)}
+   *
+   * @param userName ftp用户名
+   * @param password ftp用户密码
+   * @param account ftp账号
+   */
+  TARGET login(String userName, String password, String account);
+}
diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/IHttpHeaderTarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/IHttpHeaderTarget.java
new file mode 100644
index 00000000..8cd41def
--- /dev/null
+++ b/Aria/src/main/java/com/arialyy/aria/core/inf/IHttpHeaderTarget.java
@@ -0,0 +1,79 @@
+/*
+ * 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.inf;
+
+import android.support.annotation.NonNull;
+import com.arialyy.aria.core.common.RequestEnum;
+import com.arialyy.aria.core.download.DownloadEntity;
+import java.util.Map;
+
+/**
+ * Created by laoyuyu on 2018/3/9.
+ * HTTP Header功能接口
+ */
+public interface IHttpHeaderTarget {
+
+  /**
+   * 给url请求添加Header数据
+   * 如果新的header数据和数据保存的不一致,则更新数据库中对应的header数据
+   *
+   * @param key header对应的key
+   * @param value header对应的value
+   */
+  TARGET addHeader(@NonNull String key, @NonNull String value);
+
+  /**
+   * 给url请求添加一组header数据
+   * 如果新的header数据和数据保存的不一致,则更新数据库中对应的header数据
+   *
+   * @param headers 一组http header数据
+   */
+  TARGET addHeaders(Map headers);
+
+  /**
+   * 设置HTTP请求类型
+   *
+   * @param requestEnum {@link RequestEnum}
+   */
+  TARGET setRequestMode(RequestEnum requestEnum);
+
+  /**
+   * 如果你的下载链接的header中含有md5码信息,那么你可以通过设置key,来获取从header获取该md5码信息。
+   * key默认值为:Content-MD5
+   * 获取md5信息:{@link DownloadEntity#getMd5Code()}
+   */
+  TARGET setHeaderMd5Key(String md5Key);
+
+  /**
+   * 如果你的文件长度是放在header中,那么你需要配置key来让Aria知道正确的文件长度
+   * key默认值为:Content-Length
+   */
+  TARGET setHeaderContentLengthKey(String contentLength);
+
+  /**
+   * 如果你的下载链接的header中含有文件描述信息,那么你可以通过设置key,来获取从header获取该文件描述信息。
+   * key默认值为:Content-Disposition
+   * 获取文件描述信息:{@link DownloadEntity#getDisposition()}
+   */
+  TARGET setHeaderDispositionKey(String dispositionKey);
+
+  /**
+   * 从文件描述信息{@link #setHeaderDispositionKey(String)}中含有文件名信息,你可以通过设置key来获取header中的文件名
+   * key默认值为:attachment;filename
+   * 获取文件名信息:{@link DownloadEntity#getServerFileName()}
+   */
+  TARGET setHeaderDispositionFileKey(String dispositionFileKey);
+}
diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/ITarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/ITarget.java
index 527e8078..21149e0f 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/inf/ITarget.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/inf/ITarget.java
@@ -15,53 +15,51 @@
  */
 package com.arialyy.aria.core.inf;
 
-import android.support.annotation.NonNull;
-import com.arialyy.aria.core.common.RequestEnum;
-import java.util.Map;
-
 /**
  * Created by AriaL on 2017/6/29.
  */
 public interface ITarget {
+
   /**
-   * 任务文件大小
+   * 获取任务状态
+   *
+   * @return {@link IEntity}
    */
-  long getSize();
+  int getTaskState();
 
   /**
-   * 转换后的大小
+   * 任务是否在执行
+   *
+   * @return {@code true} 任务正在执行
    */
-  String getConvertSize();
+  boolean isRunning();
 
   /**
-   * 获取任务进度百分比
+   * 任务是否存在
+   *
+   * @return {@code true} 任务存在
    */
-  int getPercent();
+  boolean taskExists();
 
   /**
-   * 获取任务进度,如果任务存在,则返回当前进度
+   * 任务文件大小
    */
-  long getCurrentProgress();
+  long getSize();
 
   /**
-   * 给url请求添加头部
-   *
-   * @param key 头部key
-   * @param header 头部value
+   * 转换后的大小
    */
-  TARGET addHeader(@NonNull String key, @NonNull String header) ;
+  String getConvertSize();
 
   /**
-   * 给url请求添加头部
+   * 获取任务进度百分比
    */
-  TARGET addHeaders(Map headers);
+  int getPercent();
 
   /**
-   * 设置请求类型
-   *
-   * @param requestEnum {@link RequestEnum}
+   * 获取任务进度,如果任务存在,则返回当前进度
    */
-  TARGET setRequestMode(RequestEnum requestEnum);
+  long getCurrentProgress();
 
   /**
    * 开始下载
@@ -82,5 +80,4 @@ public interface ITarget {
    * 取消下载
    */
   void cancel();
-
 }
diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/AbsTaskQueue.java b/Aria/src/main/java/com/arialyy/aria/core/queue/AbsTaskQueue.java
index b8c02a25..2c90fc26 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/queue/AbsTaskQueue.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/queue/AbsTaskQueue.java
@@ -150,10 +150,10 @@ abstract class AbsTaskQueue
+    extends AbsTarget {
+
+}
diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/BaseNormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/upload/BaseNormalTarget.java
new file mode 100644
index 00000000..9a2a31b4
--- /dev/null
+++ b/Aria/src/main/java/com/arialyy/aria/core/upload/BaseNormalTarget.java
@@ -0,0 +1,139 @@
+/*
+ * 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.upload;
+
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import com.arialyy.aria.core.manager.TEManager;
+import com.arialyy.aria.core.queue.UploadTaskQueue;
+import com.arialyy.aria.util.ALog;
+import java.io.File;
+
+/**
+ * Created by AriaL on 2018/3/9.
+ */
+abstract class BaseNormalTarget
+    extends AbsUploadTarget {
+  private static final String TAG = "BaseNormalTarget";
+
+  protected void initTarget(String filePath) {
+    mTaskEntity = TEManager.getInstance().getTEntity(UploadTaskEntity.class, filePath);
+    if (mTaskEntity == null) {
+      mTaskEntity = TEManager.getInstance().createTEntity(UploadTaskEntity.class, filePath);
+    }
+    mEntity = mTaskEntity.entity;
+    File file = new File(filePath);
+    mEntity.setFileName(file.getName());
+    mEntity.setFileSize(file.length());
+  }
+
+  /**
+   * 设置上传路径
+   *
+   * @param uploadUrl 上传路径
+   */
+  public abstract TARGET setUploadUrl(@NonNull String uploadUrl);
+
+  /**
+   * 上传任务是否存在
+   *
+   * @return {@code true}存在
+   */
+  @Override public boolean taskExists() {
+    return UploadTaskQueue.getInstance().getTask(mEntity.getFilePath()) != null;
+  }
+
+  /**
+   * 是否在上传
+   *
+   * @deprecated {@link #isRunning()}
+   */
+  public boolean isUploading() {
+    return isRunning();
+  }
+
+  @Override public boolean isRunning() {
+    UploadTask task = UploadTaskQueue.getInstance().getTask(mEntity.getKey());
+    return task != null && task.isRunning();
+  }
+
+  @Override protected boolean checkEntity() {
+    return checkUrl() && checkFilePath();
+  }
+
+  /**
+   * 检查上传文件路径是否合法
+   *
+   * @return {@code true} 合法
+   */
+  private boolean checkFilePath() {
+    String filePath = mEntity.getFilePath();
+    if (TextUtils.isEmpty(filePath)) {
+      ALog.e(TAG, "上传失败,文件路径为null");
+      return false;
+    } else if (!filePath.startsWith("/")) {
+      ALog.e(TAG, "上传失败,文件路径【" + filePath + "】不合法");
+      return false;
+    }
+
+    File file = new File(mEntity.getFilePath());
+    if (!file.exists()) {
+      ALog.e(TAG, "上传失败,文件【" + filePath + "】不存在");
+      return false;
+    }
+    if (file.isDirectory()) {
+      ALog.e(TAG, "上传失败,文件【" + filePath + "】不能死文件夹");
+      return false;
+    }
+
+    if (filePath.contains("//")) {
+      filePath = filePath.replaceAll("//", "/");
+      ALog.w(TAG, "url中含有//,//将转换为/,转换后的url为:" + filePath);
+      mEntity.setFilePath(filePath);
+      mEntity.update();
+    }
+    return true;
+  }
+
+  /**
+   * 检查普通任务的下载地址
+   *
+   * @return {@code true}地址合法
+   */
+  private boolean checkUrl() {
+    final String url = mEntity.getUrl();
+    if (TextUtils.isEmpty(url)) {
+      ALog.e(TAG, "上传失败,url为null");
+      return false;
+    } else if (!url.startsWith("http") && !url.startsWith("ftp")) {
+      ALog.e(TAG, "上传失败,url【" + url + "】错误");
+      return false;
+    }
+    int index = url.indexOf("://");
+    if (index == -1) {
+      ALog.e(TAG, "上传失败,url【" + url + "】不合法");
+      return false;
+    }
+    String temp = url.substring(index + 3, url.length());
+    if (temp.contains("//")) {
+      temp = url.substring(0, index + 3) + temp.replaceAll("//", "/");
+      ALog.w(TAG, "url中含有//,//将转换为/,转换后的url为:" + temp);
+      mEntity.setUrl(temp);
+      mEntity.update();
+    }
+    return true;
+  }
+}
diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/FtpUploadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/upload/FtpUploadTarget.java
index 7bcccac5..8892c8b8 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/upload/FtpUploadTarget.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/upload/FtpUploadTarget.java
@@ -16,13 +16,12 @@
 package com.arialyy.aria.core.upload;
 
 import android.support.annotation.NonNull;
-import android.text.TextUtils;
 import com.arialyy.aria.core.AriaManager;
 import com.arialyy.aria.core.command.normal.NormalCmdFactory;
+import com.arialyy.aria.core.delegate.FtpDelegate;
 import com.arialyy.aria.core.inf.AbsTaskEntity;
-import com.arialyy.aria.core.inf.AbsUploadTarget;
+import com.arialyy.aria.core.inf.IFtpTarget;
 import com.arialyy.aria.core.manager.TEManager;
-import com.arialyy.aria.util.ALog;
 import com.arialyy.aria.util.CheckUtil;
 import com.arialyy.aria.util.CommonUtil;
 import java.io.File;
@@ -31,9 +30,10 @@ import java.io.File;
  * Created by Aria.Lao on 2017/7/27.
  * ftp单任务上传
  */
-public class FtpUploadTarget
-    extends AbsUploadTarget {
+public class FtpUploadTarget extends BaseNormalTarget
+    implements IFtpTarget {
   private final String TAG = "FtpUploadTarget";
+  private FtpDelegate mDelegate;
 
   FtpUploadTarget(String filePath, String targetName) {
     this.mTargetName = targetName;
@@ -41,15 +41,9 @@ public class FtpUploadTarget
   }
 
   private void initTask(String filePath) {
-    mTaskEntity = TEManager.getInstance().getTEntity(UploadTaskEntity.class, filePath);
-    if (mTaskEntity == null) {
-      mTaskEntity = TEManager.getInstance().createTEntity(UploadTaskEntity.class, filePath);
-    }
-    mEntity = mTaskEntity.entity;
-    File file = new File(filePath);
-    mEntity.setFileName(file.getName());
-    mEntity.setFileSize(file.length());
+    initTarget(filePath);
     mTaskEntity.requestType = AbsTaskEntity.U_FTP;
+    mDelegate = new FtpDelegate<>(mTaskEntity);
   }
 
   /**
@@ -57,6 +51,7 @@ public class FtpUploadTarget
    *
    * @param uploadUrl 上传路径
    */
+  @Override
   public FtpUploadTarget setUploadUrl(@NonNull String uploadUrl) {
     uploadUrl = CheckUtil.checkUrl(uploadUrl);
     if (!uploadUrl.endsWith("/")) {
@@ -70,55 +65,26 @@ public class FtpUploadTarget
   }
 
   /**
-   * 设置字符编码
+   * 添加任务
    */
-  public FtpUploadTarget charSet(String charSet) {
-    if (TextUtils.isEmpty(charSet)) return this;
-    mTaskEntity.charSet = charSet;
-    return this;
+  public void add() {
+    if (checkEntity()) {
+      AriaManager.getInstance(AriaManager.APP)
+          .setCmd(CommonUtil.createNormalCmd(mTargetName, mTaskEntity, NormalCmdFactory.TASK_CREATE,
+              checkTaskType()))
+          .exe();
+    }
   }
 
-  /**
-   * ftp 用户登录信。
-   * 设置登录信息需要在设置上传链接之后{@link #setUploadUrl(String)}
-   *
-   * @param userName ftp用户名
-   * @param password ftp用户密码
-   */
-  public FtpUploadTarget login(String userName, String password) {
-    return login(userName, password, null);
+  @Override public FtpUploadTarget charSet(String charSet) {
+    return mDelegate.charSet(charSet);
   }
 
-  /**
-   * ftp 用户登录信息
-   * 设置登录信息需要在设置上传链接之后{@link #setUploadUrl(String)}
-   *
-   * @param userName ftp用户名
-   * @param password ftp用户密码
-   * @param account ftp账号
-   */
-  public FtpUploadTarget login(String userName, String password, String account) {
-    if (TextUtils.isEmpty(userName)) {
-      ALog.e(TAG, "用户名不能为null");
-      return this;
-    } else if (TextUtils.isEmpty(password)) {
-      ALog.e(TAG, "密码不能为null");
-      return this;
-    }
-    mTaskEntity.urlEntity.needLogin = true;
-    mTaskEntity.urlEntity.user = userName;
-    mTaskEntity.urlEntity.password = password;
-    mTaskEntity.urlEntity.account = account;
-    return this;
+  @Override public FtpUploadTarget login(String userName, String password) {
+    return mDelegate.login(userName, password);
   }
 
-  /**
-   * 添加任务
-   */
-  public void add() {
-    AriaManager.getInstance(AriaManager.APP)
-        .setCmd(CommonUtil.createNormalCmd(mTargetName, mTaskEntity, NormalCmdFactory.TASK_CREATE,
-            checkTaskType()))
-        .exe();
+  @Override public FtpUploadTarget login(String userName, String password, String account) {
+    return mDelegate.login(userName, password, account);
   }
 }
diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java
index a86c2219..3d9b6040 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java
@@ -36,7 +36,6 @@ import java.util.Set;
  */
 public class UploadReceiver extends AbsReceiver {
   private static final String TAG = "UploadReceiver";
-  public ISchedulerListener listener;
 
   /**
    * 加载HTTP单文件上传任务
@@ -104,7 +103,6 @@ public class UploadReceiver extends AbsReceiver {
 
   @Override public void destroy() {
     targetName = null;
-    listener = null;
   }
 
   /**
diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTarget.java
index b3b49956..eeacbfd9 100644
--- a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTarget.java
+++ b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTarget.java
@@ -16,16 +16,21 @@
 package com.arialyy.aria.core.upload;
 
 import android.support.annotation.NonNull;
-import com.arialyy.aria.core.inf.AbsUploadTarget;
-import com.arialyy.aria.core.manager.TEManager;
+import com.arialyy.aria.core.common.RequestEnum;
+import com.arialyy.aria.core.delegate.HttpHeaderDelegate;
 import com.arialyy.aria.core.inf.AbsTaskEntity;
-import java.io.File;
+import com.arialyy.aria.core.inf.IHttpHeaderTarget;
+import com.arialyy.aria.util.CheckUtil;
+import java.util.Map;
 
 /**
  * Created by lyy on 2017/2/28.
- * http 当文件上传
+ * http 单文件上传
  */
-public class UploadTarget extends AbsUploadTarget {
+public class UploadTarget extends BaseNormalTarget
+    implements IHttpHeaderTarget {
+  private static final String TAG = "UploadTarget";
+  private HttpHeaderDelegate mDelegate;
 
   UploadTarget(String filePath, String targetName) {
     this.mTargetName = targetName;
@@ -33,17 +38,22 @@ public class UploadTarget extends AbsUploadTarget(mTaskEntity);
+  }
+
+  @Override public UploadTarget setUploadUrl(@NonNull String uploadUrl) {
+    uploadUrl = CheckUtil.checkUrl(uploadUrl);
+    if (mEntity.getUrl().equals(uploadUrl)) {
+      return this;
+    }
+    mEntity.setUrl(uploadUrl);
+    mEntity.update();
+    return this;
   }
 
   /**
@@ -73,4 +83,32 @@ public class UploadTarget extends AbsUploadTarget headers) {
+    return mDelegate.addHeaders(headers);
+  }
+
+  @Override public UploadTarget setRequestMode(RequestEnum requestEnum) {
+    return mDelegate.setRequestMode(requestEnum);
+  }
+
+  @Override public UploadTarget setHeaderMd5Key(String md5Key) {
+    return mDelegate.setHeaderMd5Key(md5Key);
+  }
+
+  @Override public UploadTarget setHeaderContentLengthKey(String contentLength) {
+    return mDelegate.setHeaderContentLengthKey(contentLength);
+  }
+
+  @Override public UploadTarget setHeaderDispositionKey(String dispositionKey) {
+    return mDelegate.setHeaderDispositionKey(dispositionKey);
+  }
+
+  @Override public UploadTarget setHeaderDispositionFileKey(String dispositionFileKey) {
+    return mDelegate.setHeaderDispositionFileKey(dispositionFileKey);
+  }
 }
diff --git a/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java b/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java
index 103a5153..1f67935c 100644
--- a/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java
+++ b/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java
@@ -109,6 +109,34 @@ public class CheckUtil {
     return url;
   }
 
+  ///**
+  // * 检查普通任务的下载地址
+  // *
+  // * @return {@code true}地址合法
+  // */
+  //public static boolean checkUrl(DownloadEntity entity, String url) {
+  //  if (TextUtils.isEmpty(url)) {
+  //    ALog.e(TAG, "下载失败,url为null");
+  //    return false;
+  //  } else if (!url.startsWith("http") && !url.startsWith("ftp")) {
+  //    ALog.e(TAG, "下载失败,url【" + url + "】错误");
+  //    return false;
+  //  }
+  //  int index = url.indexOf("://");
+  //  if (index == -1) {
+  //    ALog.e(TAG, "下载失败,url【" + url + "】不合法");
+  //    return false;
+  //  }
+  //  String temp = url.substring(index + 3, url.length());
+  //  if (temp.contains("//")) {
+  //    temp = url.substring(0, index + 3) + temp.replaceAll("//", "/");
+  //    ALog.w(TAG, "url中含有//,//将转换为/,转换后的url为:" + temp);
+  //    entity.setUrl(temp);
+  //    entity.update();
+  //  }
+  //  return true;
+  //}
+
   /**
    * 检测下载链接组是否为null
    */
diff --git a/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java b/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java
index ee215f6f..f52ef139 100644
--- a/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java
+++ b/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java
@@ -73,6 +73,32 @@ import java.util.regex.Pattern;
 public class CommonUtil {
   private static final String TAG = "CommonUtil";
 
+  /**
+   * 创建文件名,如果url链接有后缀名,则使用url中的后缀名
+   *
+   * @return url 的 hashKey
+   */
+  public static String createFileName(String url) {
+    int end = url.indexOf("?");
+    String tempUrl, fileName = "";
+    if (end > 0) {
+      tempUrl = url.substring(0, end);
+      int tempEnd = tempUrl.lastIndexOf("/");
+      if (tempEnd > 0) {
+        fileName = tempUrl.substring(tempEnd + 1, tempUrl.length());
+      }
+    } else {
+      int tempEnd = url.lastIndexOf("/");
+      if (tempEnd > 0) {
+        fileName = url.substring(tempEnd + 1, url.length());
+      }
+    }
+    if (TextUtils.isEmpty(fileName)) {
+      fileName = CommonUtil.keyToHashKey(url);
+    }
+    return fileName;
+  }
+
   /**
    * 分割获取url,协议,ip/域名,端口,内容
    *
diff --git a/app/src/main/java/com/arialyy/simple/download/group/DownloadGroupActivity.java b/app/src/main/java/com/arialyy/simple/download/group/DownloadGroupActivity.java
index 75397bd1..4762fe1f 100644
--- a/app/src/main/java/com/arialyy/simple/download/group/DownloadGroupActivity.java
+++ b/app/src/main/java/com/arialyy/simple/download/group/DownloadGroupActivity.java
@@ -80,7 +80,7 @@ public class DownloadGroupActivity extends BaseActivity