diff --git a/.idea/misc.xml b/.idea/misc.xml index 5d199810..fbb68289 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -37,7 +37,7 @@ - + diff --git a/Aria/build.gradle b/Aria/build.gradle index b32a7e32..f9c8604e 100644 --- a/Aria/build.gradle +++ b/Aria/build.gradle @@ -7,8 +7,8 @@ android { defaultConfig { minSdkVersion 9 targetSdkVersion 23 - versionCode 80 - versionName "2.3.2" + versionCode 81 + versionName "2.3.3" } buildTypes { release { @@ -23,4 +23,4 @@ dependencies { testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.1' } -//apply from: 'jcenter.gradle' +apply from: 'jcenter.gradle' diff --git a/Aria/jcenter.gradle b/Aria/jcenter.gradle index d65b239b..efbb3ea1 100644 --- a/Aria/jcenter.gradle +++ b/Aria/jcenter.gradle @@ -3,7 +3,7 @@ version = PROJ_VERSION project.archivesBaseName = PROJ_ARTIFACT_ID apply plugin: 'com.jfrog.bintray' apply plugin: 'com.github.dcendents.android-maven' -//输入:gradlew bintrayupload 执行 +//输入:gradlew bintray 执行 //############################## jar、sources、doc 打包 start ####################################### task sourcesJar(type: Jar) { @@ -75,9 +75,10 @@ artifacts { //################################# jcenter 上传配置 start ######################################### bintray { - user = hasProperty("bintrayUser") ? getProperty("bintrayUser") : getProperty("BINTRAY_USER") - key = hasProperty("bintrayKey") ? getProperty("bintrayKey") : getProperty("BINTRAY_KEY") - +// user = hasProperty("bintrayUser") ? getProperty("bintrayUser") : getProperty("BINTRAY_USER") +// key = hasProperty("bintrayKey") ? getProperty("bintrayKey") : getProperty("BINTRAY_KEY") + user = BINTRAY_USER + key = BINTRAY_KEY configurations = ['archives'] pkg { diff --git a/Aria/src/main/java/com/arialyy/aria/core/AMReceiver.java b/Aria/src/main/java/com/arialyy/aria/core/AMReceiver.java index f1714319..23cccb3d 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/AMReceiver.java +++ b/Aria/src/main/java/com/arialyy/aria/core/AMReceiver.java @@ -15,7 +15,12 @@ */ package com.arialyy.aria.core; +import android.os.Build; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import com.arialyy.aria.core.scheduler.DownloadSchedulers; import com.arialyy.aria.core.scheduler.OnSchedulerListener; +import com.arialyy.aria.util.CheckUtil; /** * Created by lyy on 2016/12/5. @@ -25,19 +30,36 @@ public class AMReceiver { Object obj; OnSchedulerListener listener; DownloadEntity entity; - DownloadManager manager = DownloadManager.getInstance(); - public AMTarget load(DownloadEntity entity) { + /** + * {@link #load(String)},请使用该方法 + */ + @Deprecated public AMTarget load(DownloadEntity entity) { this.entity = entity; return new AMTarget(this); } + /** + * 读取下载链接 + */ + public AMTarget load(@NonNull String downloadUrl) { + CheckUtil.checkDownloadUrl(downloadUrl); + if (entity == null) { + entity = DownloadEntity.findData(DownloadEntity.class, "downloadUrl=?", downloadUrl); + } + if (entity == null) { + entity = new DownloadEntity(); + } + entity.setDownloadUrl(downloadUrl); + return new AMTarget(this); + } + /** * 添加调度器回调 */ public AMReceiver addSchedulerListener(OnSchedulerListener listener) { this.listener = listener; - manager.getTaskQueue().getDownloadSchedulers().addSchedulerListener(obj, listener); + DownloadSchedulers.getInstance().addSchedulerListener(obj, listener); return this; } @@ -46,8 +68,13 @@ public class AMReceiver { */ public AMReceiver removeSchedulerListener() { if (listener != null) { - manager.getTaskQueue().getDownloadSchedulers().removeSchedulerListener(obj, listener); + DownloadSchedulers.getInstance().removeSchedulerListener(obj, listener); } return this; } + + void destroy() { + obj = null; + listener = null; + } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/AMTarget.java b/Aria/src/main/java/com/arialyy/aria/core/AMTarget.java index b5fdd61c..ce6aecd9 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/AMTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/AMTarget.java @@ -15,10 +15,11 @@ */ package com.arialyy.aria.core; +import android.support.annotation.NonNull; +import android.text.TextUtils; import com.arialyy.aria.core.command.CmdFactory; -import com.arialyy.aria.core.scheduler.OnSchedulerListener; import com.arialyy.aria.core.command.IDownloadCmd; -import com.arialyy.aria.core.task.Task; +import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CommonUtil; import java.util.ArrayList; import java.util.List; @@ -28,18 +29,68 @@ import java.util.List; * https://github.com/AriaLyy/Aria */ public class AMTarget { - private AMReceiver receiver; + private AMReceiver mReceiver; - public AMTarget(AMReceiver receiver) { - this.receiver = receiver; + AMTarget(AMReceiver receiver) { + this.mReceiver = receiver; + } + + /** + * 设置文件存储路径 + */ + public AMTarget setDownloadPath(@NonNull String downloadPath) { + if (TextUtils.isEmpty(downloadPath)) { + throw new IllegalArgumentException("文件保持路径不能为null"); + } + mReceiver.entity.setDownloadPath(downloadPath); + return this; + } + + /** + * 设置文件名 + */ + public AMTarget setDownloadName(@NonNull String downloadName) { + if (TextUtils.isEmpty(downloadName)) { + throw new IllegalArgumentException("文件名不能为null"); + } + mReceiver.entity.setFileName(downloadName); + return this; + } + + /** + * 获取下载文件大小 + */ + public long getFileSize() { + DownloadEntity entity = getDownloadEntity(mReceiver.entity.getDownloadUrl()); + if (entity == null) { + throw new NullPointerException("下载管理器中没有改任务"); + } + return entity.getFileSize(); + } + + /** + * 获取当前下载进度,如果下載实体存在,则返回当前进度 + */ + public long getCurrentProgress() { + DownloadEntity entity = getDownloadEntity(mReceiver.entity.getDownloadUrl()); + if (entity == null) { + throw new NullPointerException("下载管理器中没有改任务"); + } + return entity.getCurrentProgress(); + } + + private DownloadEntity getDownloadEntity(String downloadUrl) { + CheckUtil.checkDownloadUrl(downloadUrl); + return DownloadEntity.findData(DownloadEntity.class, "downloadUrl=?", downloadUrl); } /** * 添加任务 */ public void add() { - receiver.manager.setCmd( - CommonUtil.createCmd(receiver.obj, receiver.entity, CmdFactory.TASK_CREATE)).exe(); + DownloadManager.getInstance() + .setCmd(CommonUtil.createCmd(mReceiver.obj, mReceiver.entity, CmdFactory.TASK_CREATE)) + .exe(); } /** @@ -47,67 +98,51 @@ public class AMTarget { */ public void start() { List cmds = new ArrayList<>(); - cmds.add(CommonUtil.createCmd(receiver.obj, receiver.entity, CmdFactory.TASK_CREATE)); - cmds.add(CommonUtil.createCmd(receiver.obj, receiver.entity, CmdFactory.TASK_START)); - receiver.manager.setCmds(cmds).exe(); + cmds.add(CommonUtil.createCmd(mReceiver.obj, mReceiver.entity, CmdFactory.TASK_CREATE)); + cmds.add(CommonUtil.createCmd(mReceiver.obj, mReceiver.entity, CmdFactory.TASK_START)); + DownloadManager.getInstance().setCmds(cmds).exe(); + cmds.clear(); } /** * 停止下载 */ public void stop() { - receiver.manager.setCmd( - CommonUtil.createCmd(receiver.obj, receiver.entity, CmdFactory.TASK_STOP)).exe(); + DownloadManager.getInstance() + .setCmd(CommonUtil.createCmd(mReceiver.obj, mReceiver.entity, CmdFactory.TASK_STOP)) + .exe(); } /** * 恢复下载 */ public void resume() { - receiver.manager.setCmd( - CommonUtil.createCmd(receiver.obj, receiver.entity, CmdFactory.TASK_START)).exe(); + DownloadManager.getInstance() + .setCmd(CommonUtil.createCmd(mReceiver.obj, mReceiver.entity, CmdFactory.TASK_START)) + .exe(); } /** * 取消下载 */ public void cancel() { - receiver.manager.setCmd( - CommonUtil.createCmd(receiver.obj, receiver.entity, CmdFactory.TASK_CANCEL)).exe(); + DownloadManager.getInstance() + .setCmd(CommonUtil.createCmd(mReceiver.obj, mReceiver.entity, CmdFactory.TASK_CANCEL)) + .exe(); } - public static class SimpleSchedulerListener implements OnSchedulerListener { - - @Override public void onTaskPre(Task task) { - - } - - @Override public void onTaskResume(Task task) { - - } - - @Override public void onTaskStart(Task task) { - - } - - @Override public void onTaskStop(Task task) { - - } - - @Override public void onTaskCancel(Task task) { - - } - - @Override public void onTaskFail(Task task) { - - } - - @Override public void onTaskComplete(Task task) { - - } - - @Override public void onTaskRunning(Task task) { + /** + * 是否在下载 + */ + public boolean isDownloading() { + return DownloadManager.getInstance().getTaskQueue().getTask(mReceiver.entity).isDownloading(); + } - } + /** + * 重新下载 + */ + public void reStart() { + cancel(); + start(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/Aria.java b/Aria/src/main/java/com/arialyy/aria/core/Aria.java index bfb884f9..705352d3 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/Aria.java +++ b/Aria/src/main/java/com/arialyy/aria/core/Aria.java @@ -19,23 +19,90 @@ package com.arialyy.aria.core; import android.annotation.TargetApi; import android.app.Activity; import android.app.Application; +import android.app.Dialog; +import android.app.DialogFragment; import android.app.Fragment; import android.app.Service; import android.content.Context; import android.os.Build; +import com.arialyy.aria.core.scheduler.OnSchedulerListener; +import com.arialyy.aria.core.task.Task; /** * Created by lyy on 2016/12/1. * https://github.com/AriaLyy/Aria * Aria启动,管理全局任务 + *
+ *   
+ *      DownloadEntity mEntity = new DownloadEntity();
+ *      mEntity.setFileName(fileName);          //设置文件名
+ *      mEntity.setDownloadUrl(downloadUrl);    //设置下载链接
+ *      mEntity.setDownloadPath(downloadPath);  //设置存放路径
+ *
+ *      //启动下载
+ *     Aria.whit(this).load(mEntity).start();
+ *   
+ * 
*/ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) public class Aria { + /** + * 预处理完成 + */ + public static final String ACTION_PRE = "ACTION_PRE"; + /** + * 下载开始前事件 + */ + public static final String ACTION_POST_PRE = "ACTION_POST_PRE"; + /** + * 开始下载事件 + */ + public static final String ACTION_START = "ACTION_START"; + /** + * 恢复下载事件 + */ + public static final String ACTION_RESUME = "ACTION_RESUME"; + /** + * 正在下载事件 + */ + public static final String ACTION_RUNNING = "ACTION_RUNNING"; + /** + * 停止下载事件 + */ + public static final String ACTION_STOP = "ACTION_STOP"; + /** + * 取消下载事件 + */ + public static final String ACTION_CANCEL = "ACTION_CANCEL"; + /** + * 下载完成事件 + */ + public static final String ACTION_COMPLETE = "ACTION_COMPLETE"; + /** + * 下载失败事件 + */ + public static final String ACTION_FAIL = "ACTION_FAIL"; + /** + * 下载实体 + */ + public static final String ENTITY = "DOWNLOAD_ENTITY"; + /** + * 位置 + */ + public static final String CURRENT_LOCATION = "CURRENT_LOCATION"; + /** + * 速度 + */ + public static final String CURRENT_SPEED = "CURRENT_SPEED"; private Aria() { } + /** + * 接受Activity、Service、Application + */ public static AMReceiver whit(Context context) { - if (context == null) throw new IllegalArgumentException("context 不能为 null"); + //if (context == null) throw new IllegalArgumentException("context 不能为 null"); + checkNull(context); if (context instanceof Activity || context instanceof Service || context instanceof Application) { @@ -45,12 +112,31 @@ import android.os.Build; } } + /** + * 处理Fragment、或者DialogFragment + */ public static AMReceiver whit(Fragment fragment) { + checkNull(fragment); return AriaManager.getInstance( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? fragment.getContext() : fragment.getActivity()).get(fragment); } + /** + * 处理Dialog + */ + public static AMReceiver whit(Dialog dialog) { + checkNull(dialog); + return AriaManager.getInstance(dialog.getContext()).get(dialog); + } + + private static void checkNull(Object obj) { + if (obj == null) throw new IllegalArgumentException("不能传入空对象"); + } + + /** + * 处理通用事件 + */ public static AriaManager get(Context context) { if (context == null) throw new IllegalArgumentException("context 不能为 null"); if (context instanceof Activity @@ -61,4 +147,39 @@ import android.os.Build; throw new IllegalArgumentException("这是不支持的context"); } } + + public static class SimpleSchedulerListener implements OnSchedulerListener { + + @Override public void onTaskPre(Task task) { + + } + + @Override public void onTaskResume(Task task) { + + } + + @Override public void onTaskStart(Task task) { + + } + + @Override public void onTaskStop(Task task) { + + } + + @Override public void onTaskCancel(Task task) { + + } + + @Override public void onTaskFail(Task task) { + + } + + @Override public void onTaskComplete(Task task) { + + } + + @Override public void onTaskRunning(Task task) { + + } + } } 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 8ea0ded5..a48e93c8 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java +++ b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java @@ -18,17 +18,23 @@ package com.arialyy.aria.core; import android.annotation.TargetApi; import android.app.Activity; import android.app.Application; +import android.app.Dialog; import android.app.Service; import android.content.Context; +import android.content.DialogInterface; import android.os.Build; import android.os.Bundle; +import android.os.Message; import android.support.v4.app.Fragment; import android.text.TextUtils; import android.util.Log; import com.arialyy.aria.core.command.CmdFactory; +import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.core.command.IDownloadCmd; import com.arialyy.aria.util.Configuration; +import java.io.File; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -69,7 +75,7 @@ import java.util.Set; /** * 获取下载列表 */ - public List getDownloadList(){ + public List getDownloadList() { return DownloadEntity.findAllData(DownloadEntity.class); } @@ -77,11 +83,15 @@ import java.util.Set; * 通过下载链接获取下载实体 */ public DownloadEntity getDownloadEntity(String downloadUrl) { - if (TextUtils.isEmpty(downloadUrl)) { - throw new IllegalArgumentException("下载链接不能为null"); - } - return DownloadEntity.findData(DownloadEntity.class, new String[] { "downloadUrl" }, - new String[] { downloadUrl }); + CheckUtil.checkDownloadUrl(downloadUrl); + return DownloadEntity.findData(DownloadEntity.class, "downloadUrl=?", downloadUrl); + } + + /** + * 下载任务是否存在 + */ + public boolean taskExists(String downloadUrl) { + return DownloadEntity.findData(DownloadEntity.class, "downloadUrl=?", downloadUrl) != null; } /** @@ -101,8 +111,7 @@ import java.util.Set; /** * 设置下载超时时间 */ - @Deprecated - private AriaManager setTimeOut(int timeOut) { + @Deprecated private AriaManager setTimeOut(int timeOut) { Configuration.getInstance().setTimeOut(timeOut); return this; } @@ -165,20 +174,61 @@ import java.util.Set; private AMReceiver putTarget(Object obj) { String clsName = obj.getClass().getName(); - AMReceiver target = mTargets.get(clsName); - if (target == null) { - target = new AMReceiver(); - target.obj = obj; + AMReceiver target = null; + String key = ""; + if (!(obj instanceof Activity)) { if (obj instanceof android.support.v4.app.Fragment) { - clsName += "_" + ((Fragment) obj).getActivity().getClass().getName(); + key = clsName + "_" + ((Fragment) obj).getActivity().getClass().getName(); } else if (obj instanceof android.app.Fragment) { - clsName += "_" + ((android.app.Fragment) obj).getActivity().getClass().getName(); + key = clsName + "_" + ((android.app.Fragment) obj).getActivity().getClass().getName(); + } else if (obj instanceof Dialog) { + Activity activity = ((Dialog) obj).getOwnerActivity(); + if (activity != null) { + key = clsName + "_" + activity.getClass().getName(); + } + handleDialogDialogLift((Dialog) obj); + } + } else { + key = clsName; + } + if (TextUtils.isEmpty(key)) { + throw new IllegalArgumentException("未知类型"); + } else { + target = mTargets.get(key); + if (target == null) { + target = new AMReceiver(); + target.obj = obj; + mTargets.put(key, target); } - mTargets.put(clsName, target); } return target; } + /** + * 处理对话框取消或dismiss + */ + private void handleDialogDialogLift(Dialog dialog) { + try { + Field dismissField = CommonUtil.getField(dialog.getClass(), "mDismissMessage"); + Message dismissMsg = (Message) dismissField.get(dialog); + //如果Dialog已经设置Dismiss事件,则查找cancel事件 + if (dismissMsg != null) { + Field cancelField = CommonUtil.getField(dialog.getClass(), "mCancelMessage"); + Message cancelMsg = (Message) cancelField.get(dialog); + if (cancelMsg != null) { + Log.e(TAG, "你已经对Dialog设置了Dismiss和cancel事件。为了防止内存泄露," + + "请在dismiss方法中调用Aria.whit(this).removeSchedulerListener();来注销事件"); + } else { + dialog.setCancelMessage(createCancelMessage()); + } + } else { + dialog.setCancelMessage(createDismissMessage()); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + private AMReceiver getTarget(Object obj) { AMReceiver target = mTargets.get(obj.getClass().getName()); if (target == null) { @@ -198,6 +248,56 @@ import java.util.Set; } } + /** + * 创建Dialog取消消息 + */ + private Message createCancelMessage() { + final Message cancelMsg = new Message(); + cancelMsg.what = 0x44; + cancelMsg.obj = new Dialog.OnCancelListener() { + + @Override public void onCancel(DialogInterface dialog) { + destroySchedulerListener(dialog); + } + }; + return cancelMsg; + } + + /** + * 创建Dialog dismiss取消消息 + */ + private Message createDismissMessage() { + final Message cancelMsg = new Message(); + cancelMsg.what = 0x43; + cancelMsg.obj = new Dialog.OnDismissListener() { + + @Override public void onDismiss(DialogInterface dialog) { + destroySchedulerListener(dialog); + } + }; + return cancelMsg; + } + + /** + * onDestroy + */ + private void destroySchedulerListener(Object obj) { + Set keys = mTargets.keySet(); + String clsName = obj.getClass().getName(); + for (String key : keys) { + if (key.equals(clsName) || key.contains(clsName)) { + AMReceiver receiver = mTargets.get(key); + if (receiver.obj != null) { + if (receiver.obj instanceof Application || receiver.obj instanceof Service) break; + receiver.removeSchedulerListener(); + receiver.destroy(); + mTargets.remove(key); + } + break; + } + } + } + /** * Activity生命周期 */ @@ -228,19 +328,7 @@ import java.util.Set; } @Override public void onActivityDestroyed(Activity activity) { - Set keys = mTargets.keySet(); - for (String key : keys) { - String clsName = activity.getClass().getName(); - if (key.equals(clsName) || key.contains(clsName)) { - AMReceiver target = mTargets.get(key); - if (target.obj != null) { - if (target.obj instanceof Application || target.obj instanceof Service) break; - target.removeSchedulerListener(); - mTargets.remove(key); - } - break; - } - } + destroySchedulerListener(activity); } } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/DownloadManager.java b/Aria/src/main/java/com/arialyy/aria/core/DownloadManager.java index 19afbf70..12640284 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/DownloadManager.java +++ b/Aria/src/main/java/com/arialyy/aria/core/DownloadManager.java @@ -31,54 +31,6 @@ import java.util.List; * 下载管理器,通过命令的方式控制下载 */ public class DownloadManager { - /** - * 预处理完成 - */ - public static final String ACTION_PRE = "ACTION_PRE"; - /** - * 下载开始前事件 - */ - public static final String ACTION_POST_PRE = "ACTION_POST_PRE"; - /** - * 开始下载事件 - */ - public static final String ACTION_START = "ACTION_START"; - /** - * 恢复下载事件 - */ - public static final String ACTION_RESUME = "ACTION_RESUME"; - /** - * 正在下载事件 - */ - public static final String ACTION_RUNNING = "ACTION_RUNNING"; - /** - * 停止下载事件 - */ - public static final String ACTION_STOP = "ACTION_STOP"; - /** - * 取消下载事件 - */ - public static final String ACTION_CANCEL = "ACTION_CANCEL"; - /** - * 下载完成事件 - */ - public static final String ACTION_COMPLETE = "ACTION_COMPLETE"; - /** - * 下载失败事件 - */ - public static final String ACTION_FAIL = "ACTION_FAIL"; - /** - * 下载实体 - */ - public static final String ENTITY = "DOWNLOAD_ENTITY"; - /** - * 位置 - */ - public static final String CURRENT_LOCATION = "CURRENT_LOCATION"; - /** - * 速度 - */ - public static final String CURRENT_SPEED = "CURRENT_SPEED"; private static final String TAG = "DownloadManager"; private static final Object LOCK = new Object(); private static volatile DownloadManager INSTANCE = null; diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java b/Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java index 65c6f35b..e479b9cc 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java @@ -25,6 +25,7 @@ import com.arialyy.aria.core.scheduler.DownloadSchedulers; import com.arialyy.aria.core.scheduler.IDownloadSchedulers; import com.arialyy.aria.core.task.Task; import com.arialyy.aria.core.task.TaskFactory; +import com.arialyy.aria.util.Configuration; /** * Created by lyy on 2016/8/17. @@ -34,8 +35,8 @@ public class DownloadTaskQueue implements ITaskQueue { private static final String TAG = "DownloadTaskQueue"; private CachePool mCachePool = CachePool.getInstance(); private ExecutePool mExecutePool = ExecutePool.getInstance(); - private Context mContext; - private IDownloadSchedulers mSchedulers; + private Context mContext; + //private IDownloadSchedulers mSchedulers; private DownloadTaskQueue() { } @@ -106,6 +107,10 @@ public class DownloadTaskQueue implements ITaskQueue { } @Override public void reTryStart(Task task) { + if (task == null) { + Log.w(TAG, "重试下载失败,task 为null"); + return; + } if (!task.isDownloading()) { task.start(); } else { @@ -113,25 +118,54 @@ public class DownloadTaskQueue implements ITaskQueue { } } - @Override public IDownloadSchedulers getDownloadSchedulers() { - return mSchedulers; - } + //@Override public IDownloadSchedulers getDownloadSchedulers() { + // return mSchedulers; + //} @Override public int size() { return mExecutePool.size(); } @Override public void setDownloadNum(int downloadNum) { + //原始长度 + int size = Configuration.getInstance().getDownloadNum(); + int diff = downloadNum - size; + if (size == downloadNum) { + Log.d(TAG, "设置的下载任务数和配置文件的下载任务数一直,跳过"); + return; + } + //设置的任务数小于配置任务数 + if (diff <= -1 && mExecutePool.size() >= size) { + for (int i = 0, len = Math.abs(diff); i < len; i++) { + Task eTask = mExecutePool.pollTask(); + if (eTask != null) { + stopTask(eTask); + } + } + } mExecutePool.setDownloadNum(downloadNum); + if (diff >= 1) { + for (int i = 0; i < diff; i++) { + Task nextTask = getNextTask(); + if (nextTask != null + && nextTask.getDownloadEntity().getState() == DownloadEntity.STATE_WAIT) { + startTask(nextTask); + } + } + } } @Override public Task createTask(Object target, DownloadEntity entity) { Task task; if (target == null) { - task = TaskFactory.getInstance().createTask(mContext, entity, mSchedulers); + //task = TaskFactory.getInstance().createTask(mContext, entity, mSchedulers); + task = TaskFactory.getInstance() + .createTask(mContext, entity, DownloadSchedulers.getInstance()); } else { task = TaskFactory.getInstance() - .createTask(target.getClass().getName(), mContext, entity, mSchedulers); + //.createTask(target.getClass().getName(), mContext, entity, mSchedulers); + .createTask(target.getClass().getName(), mContext, entity, + DownloadSchedulers.getInstance()); } mCachePool.putTask(task); return task; @@ -163,9 +197,9 @@ public class DownloadTaskQueue implements ITaskQueue { return mCachePool.pollTask(); } - @Override public void setScheduler(IDownloadSchedulers schedulers) { - mSchedulers = schedulers; - } + //@Override public void setScheduler(IDownloadSchedulers schedulers) { + // mSchedulers = schedulers; + //} public static class Builder { Context context; @@ -175,17 +209,17 @@ public class DownloadTaskQueue implements ITaskQueue { this.context = context.getApplicationContext(); } - public Builder setDownloadSchedulers(IDownloadSchedulers schedulers) { - this.schedulers = schedulers; - return this; - } + //public Builder setDownloadSchedulers(IDownloadSchedulers schedulers) { + // this.schedulers = schedulers; + // return this; + //} public DownloadTaskQueue build() { DownloadTaskQueue queue = new DownloadTaskQueue(context); - if (schedulers == null) { - schedulers = DownloadSchedulers.getInstance(queue); - } - queue.setScheduler(schedulers); + //if (schedulers == null) { + // schedulers = DownloadSchedulers.getInstance(); + //} + //queue.setScheduler(schedulers); return queue; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/ITaskQueue.java b/Aria/src/main/java/com/arialyy/aria/core/queue/ITaskQueue.java index d558c668..485eba63 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/ITaskQueue.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/ITaskQueue.java @@ -26,10 +26,10 @@ import com.arialyy.aria.core.task.Task; */ public interface ITaskQueue extends IDownloader { - /** - * 获取调度器 - */ - public IDownloadSchedulers getDownloadSchedulers(); + ///** + // * 获取调度器 + // */ + //public IDownloadSchedulers getDownloadSchedulers(); /** * 任务池队列大小 @@ -74,10 +74,10 @@ public interface ITaskQueue extends IDownloader { */ public Task getNextTask(); - /** - * 设置下载调度器 - * - * @param schedulers 下载调度器{@link IDownloadSchedulers} - */ - public void setScheduler(IDownloadSchedulers schedulers); + ///** + // * 设置下载调度器 + // * + // * @param schedulers 下载调度器{@link IDownloadSchedulers} + // */ + //public void setScheduler(IDownloadSchedulers schedulers); } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/CachePool.java b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/CachePool.java index f2adb5e2..8f710491 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/CachePool.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/CachePool.java @@ -14,7 +14,6 @@ * limitations under the License. */ - package com.arialyy.aria.core.queue.pool; import android.text.TextUtils; @@ -25,6 +24,7 @@ import com.arialyy.aria.core.task.Task; import java.util.HashMap; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; /** * Created by lyy on 2016/8/14. @@ -35,6 +35,7 @@ public class CachePool implements IPool { private static final Object LOCK = new Object(); private static final int MAX_NUM = Integer.MAX_VALUE; //最大下载任务数 private static volatile CachePool INSTANCE = null; + private static final long TIME_OUT = 1000; private Map mCacheArray; private LinkedBlockingQueue mCacheQueue; @@ -75,13 +76,19 @@ public class CachePool implements IPool { @Override public Task pollTask() { synchronized (LOCK) { - Task task = mCacheQueue.poll(); - if (task != null) { - String url = task.getDownloadEntity().getDownloadUrl(); - mCacheArray.remove(CommonUtil.keyToHashKey(url)); + try { + Task task = null; + task = mCacheQueue.poll(TIME_OUT, TimeUnit.MICROSECONDS); + if (task != null) { + String url = task.getDownloadEntity().getDownloadUrl(); + mCacheArray.remove(CommonUtil.keyToHashKey(url)); + } + return task; + } catch (InterruptedException e) { + e.printStackTrace(); } - return task; } + return null; } @Override public Task getTask(String downloadUrl) { diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/ExecutePool.java b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/ExecutePool.java index 6f3f5009..a3a1c911 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/ExecutePool.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/ExecutePool.java @@ -137,12 +137,18 @@ public class ExecutePool implements IPool { @Override public Task pollTask() { synchronized (LOCK) { - Task task = mExecuteQueue.poll(); - if (task != null) { - String url = task.getDownloadEntity().getDownloadUrl(); - mExecuteArray.remove(CommonUtil.keyToHashKey(url)); + try { + Task task = null; + task = mExecuteQueue.poll(TIME_OUT, TimeUnit.MICROSECONDS); + if (task != null) { + String url = task.getDownloadEntity().getDownloadUrl(); + mExecuteArray.remove(CommonUtil.keyToHashKey(url)); + } + return task; + } catch (InterruptedException e) { + e.printStackTrace(); } - return task; + return null; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/DownloadSchedulers.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/DownloadSchedulers.java index 3e952b51..e6d70851 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/DownloadSchedulers.java +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/DownloadSchedulers.java @@ -19,12 +19,12 @@ package com.arialyy.aria.core.scheduler; import android.os.Message; import android.text.TextUtils; import android.util.Log; +import com.arialyy.aria.core.DownloadManager; import com.arialyy.aria.core.queue.ITaskQueue; import com.arialyy.aria.core.DownloadEntity; import com.arialyy.aria.core.task.Task; -import com.arialyy.aria.core.queue.pool.ExecutePool; -import com.arialyy.aria.core.queue.DownloadTaskQueue; import com.arialyy.aria.util.Configuration; +import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -69,30 +69,54 @@ public class DownloadSchedulers implements IDownloadSchedulers { private static final Object LOCK = new Object(); private static volatile DownloadSchedulers INSTANCE = null; - /** - * 超时时间 - */ - long mTimeOut = 10000; - /** * 下载器任务监听 */ Map mSchedulerListeners = new ConcurrentHashMap<>(); + DownloadManager mManager = DownloadManager.getInstance(); ITaskQueue mQueue; - public DownloadSchedulers(ITaskQueue downloadTaskQueue) { - mQueue = downloadTaskQueue; + private DownloadSchedulers() { + mQueue = mManager.getTaskQueue(); } - public static DownloadSchedulers getInstance(DownloadTaskQueue queue) { + public static DownloadSchedulers getInstance() { if (INSTANCE == null) { synchronized (LOCK) { - INSTANCE = new DownloadSchedulers(queue); + //INSTANCE = new DownloadSchedulers(queue); + INSTANCE = new DownloadSchedulers(); } } return INSTANCE; } + @Override public void addSchedulerListener(Object target, OnSchedulerListener schedulerListener) { + if (target == null) { + throw new IllegalArgumentException("target 不能为null"); + } + String name = target.getClass().getName(); + if (mSchedulerListeners.get(name) != null) { + Log.w(TAG, "监听器已存在"); + return; + } + mSchedulerListeners.put(name, schedulerListener); + } + + @Override + public void removeSchedulerListener(Object target, OnSchedulerListener schedulerListener) { + if (target == null) { + throw new IllegalArgumentException("target 不能为null"); + } + //OnSchedulerListener listener = mSchedulerListeners.get(target.getClass().getName()); + //mSchedulerListeners.remove(listener); + //该内存溢出解决方案:http://stackoverflow.com/questions/14585829/how-safe-is-to-delete-already-removed-concurrenthashmap-element + for (Iterator> iter = + mSchedulerListeners.entrySet().iterator(); iter.hasNext(); ) { + Map.Entry entry = iter.next(); + if (entry.getKey().equals(target.getClass().getName())) iter.remove(); + } + } + @Override public boolean handleMessage(Message msg) { Task task = (Task) msg.obj; if (task == null) { @@ -104,6 +128,7 @@ public class DownloadSchedulers implements IDownloadSchedulers { switch (msg.what) { case STOP: case CANCEL: + mQueue.removeTask(entity); mQueue.removeTask(entity); if (mQueue.size() != Configuration.getInstance().getDownloadNum()) { startNextTask(entity); @@ -174,19 +199,23 @@ public class DownloadSchedulers implements IDownloadSchedulers { * * @param entity 失败实体 */ - @Override public void handleFailTask(DownloadEntity entity) { - final Configuration config = Configuration.getInstance(); - if (entity.getFailNum() <= config.getReTryNum()) { - Task task = mQueue.getTask(entity); - mQueue.reTryStart(task); - try { - Thread.currentThread().sleep(config.getReTryInterval()); - } catch (InterruptedException e) { - e.printStackTrace(); + @Override public void handleFailTask(final DownloadEntity entity) { + new Thread(new Runnable() { + @Override public void run() { + final Configuration config = Configuration.getInstance(); + if (entity.getFailNum() <= config.getReTryNum()) { + Task task = mQueue.getTask(entity); + mQueue.reTryStart(task); + try { + Thread.sleep(config.getReTryInterval()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } else { + startNextTask(entity); + } } - } else { - startNextTask(entity); - } + }).start(); } /** @@ -205,24 +234,4 @@ public class DownloadSchedulers implements IDownloadSchedulers { mQueue.startTask(newTask); } } - - @Override public void addSchedulerListener(Object target, OnSchedulerListener schedulerListener) { - if (target == null) { - throw new IllegalArgumentException("target 不能为null"); - } - String name = target.getClass().getName(); - if (mSchedulerListeners.get(name) != null) { - Log.w(TAG, "监听器已存在"); - return; - } - mSchedulerListeners.put(name, schedulerListener); - } - - @Override - public void removeSchedulerListener(Object target, OnSchedulerListener schedulerListener) { - if (target == null) { - throw new IllegalArgumentException("target 不能为null"); - } - mSchedulerListeners.remove(target.getClass().getName()); - } -} \ No newline at end of file +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/task/DownloadListener.java b/Aria/src/main/java/com/arialyy/aria/core/task/DownloadListener.java index a0e9b8b8..bab38eae 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/task/DownloadListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/task/DownloadListener.java @@ -23,6 +23,10 @@ class DownloadListener implements IDownloadListener { } + @Override public void supportBreakpoint(boolean support) { + + } + @Override public void onCancel() { } diff --git a/Aria/src/main/java/com/arialyy/aria/core/task/DownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/task/DownloadUtil.java index 032809b1..c69ee94a 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/task/DownloadUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/task/DownloadUtil.java @@ -14,7 +14,6 @@ * limitations under the License. */ - package com.arialyy.aria.core.task; import android.content.Context; @@ -38,7 +37,7 @@ import java.util.concurrent.Executors; * Created by lyy on 2015/8/25. * 下载工具类 */ -final class DownloadUtil implements IDownloadUtil { +final class DownloadUtil implements IDownloadUtil, Runnable { private static final String TAG = "DownloadUtil"; private static final Object LOCK = new Object(); /** @@ -47,22 +46,26 @@ final class DownloadUtil implements IDownloadUtil { private final int THREAD_NUM; //下载监听 private IDownloadListener mListener; - private int mConnectTimeOut = 5000 * 4; //连接超时时间 - private int mReadTimeOut = 5000 * 20; //流读取的超时时间 + private int mConnectTimeOut = 5000 * 4; //连接超时时间 + private int mReadTimeOut = 5000 * 20; //流读取的超时时间 /** * 已经完成下载任务的线程数量 */ - private int mCompleteThreadNum = 0; - private boolean isDownloading = false; - private boolean isStop = false; - private boolean isCancel = false; - private boolean isNewTask = true; - private int mCancelNum = 0; - private long mCurrentLocation = 0; - private int mStopNum = 0; + private boolean isDownloading = false; + private boolean isStop = false; + private boolean isCancel = false; + private boolean isNewTask = true; + private boolean isSupportBreakpoint = true; + private int mCompleteThreadNum = 0; + private int mCancelNum = 0; + private long mCurrentLocation = 0; + private int mStopNum = 0; + private int mFailNum = 0; private Context mContext; private DownloadEntity mDownloadEntity; private ExecutorService mFixedThreadPool; + private File mDownloadFile; //下载的文件 + private File mConfigFile;//下载信息配置文件 private SparseArray mTask = new SparseArray<>(); DownloadUtil(Context context, DownloadEntity entity, IDownloadListener downloadListener) { @@ -75,7 +78,26 @@ final class DownloadUtil implements IDownloadUtil { mDownloadEntity = entity; mListener = downloadListener; THREAD_NUM = threadNum; - mFixedThreadPool = Executors.newFixedThreadPool(THREAD_NUM); + mFixedThreadPool = Executors.newFixedThreadPool(Integer.MAX_VALUE); + init(); + } + + private void init() { + mDownloadFile = new File(mDownloadEntity.getDownloadPath()); + //读取已完成的线程数 + mConfigFile = new File( + mContext.getFilesDir().getPath() + "/temp/" + mDownloadFile.getName() + ".properties"); + try { + if (!mConfigFile.exists()) { //记录文件被删除,则重新下载 + isNewTask = true; + CommonUtil.createFile(mConfigFile.getPath()); + } else { + isNewTask = !mDownloadFile.exists(); + } + } catch (Exception e) { + e.printStackTrace(); + failDownload("下载失败,记录文件被删除"); + } } public IDownloadListener getListener() { @@ -115,7 +137,7 @@ final class DownloadUtil implements IDownloadUtil { isDownloading = false; mFixedThreadPool.shutdown(); for (int i = 0; i < THREAD_NUM; i++) { - DownLoadTask task = (DownLoadTask) mTask.get(i); + SingleThreadTask task = (SingleThreadTask) mTask.get(i); if (task != null) { task.cancel(); } @@ -130,7 +152,7 @@ final class DownloadUtil implements IDownloadUtil { isDownloading = false; mFixedThreadPool.shutdown(); for (int i = 0; i < THREAD_NUM; i++) { - DownLoadTask task = (DownLoadTask) mTask.get(i); + SingleThreadTask task = (SingleThreadTask) mTask.get(i); if (task != null) { task.stop(); } @@ -173,142 +195,9 @@ final class DownloadUtil implements IDownloadUtil { isCancel = false; mCancelNum = 0; mStopNum = 0; - final String filePath = mDownloadEntity.getDownloadPath(); - final String downloadUrl = mDownloadEntity.getDownloadUrl(); - final File dFile = new File(filePath); - //读取已完成的线程数 - final File configFile = - new File(mContext.getFilesDir().getPath() + "/temp/" + dFile.getName() + ".properties"); - try { - if (!configFile.exists()) { //记录文件被删除,则重新下载 - isNewTask = true; - CommonUtil.createFile(configFile.getPath()); - } else { - isNewTask = !dFile.exists(); - } - } catch (Exception e) { - e.printStackTrace(); - failDownload("下载失败,记录文件被删除"); - return; - } + mFailNum = 0; mListener.onPre(); - new Thread(new Runnable() { - @Override public void run() { - try { - URL url = new URL(downloadUrl); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - setConnectParam(conn); - conn.setConnectTimeout(mConnectTimeOut * 4); - conn.connect(); - int len = conn.getContentLength(); - if (len < 0) { //网络被劫持时会出现这个问题 - failDownload("下载失败,网络被劫持"); - return; - } - int code = conn.getResponseCode(); - if (code == 200) { - int fileLength = conn.getContentLength(); - //必须建一个文件 - CommonUtil.createFile(filePath); - RandomAccessFile file = new RandomAccessFile(filePath, "rwd"); - //设置文件长度 - file.setLength(fileLength); - mListener.onPostPre(fileLength); - //分配每条线程的下载区间 - Properties pro = null; - pro = CommonUtil.loadConfig(configFile); - if (pro.isEmpty()) { - isNewTask = true; - } else { - for (int i = 0; i < THREAD_NUM; i++) { - if (pro.getProperty(dFile.getName() + "_record_" + i) == null) { - Object state = pro.getProperty(dFile.getName() + "_state_" + i); - if (state != null && Integer.parseInt(state + "") == 1) { - continue; - } - isNewTask = true; - break; - } - } - } - int blockSize = fileLength / THREAD_NUM; - int[] recordL = new int[THREAD_NUM]; - int rl = 0; - for (int i = 0; i < THREAD_NUM; i++) { - recordL[i] = -1; - } - for (int i = 0; i < THREAD_NUM; i++) { - long startL = i * blockSize, endL = (i + 1) * blockSize; - Object state = pro.getProperty(dFile.getName() + "_state_" + i); - if (state != null && Integer.parseInt(state + "") == 1) { //该线程已经完成 - mCurrentLocation += endL - startL; - Log.d(TAG, "++++++++++ 线程_" + i + "_已经下载完成 ++++++++++"); - mCompleteThreadNum++; - mStopNum++; - mCancelNum++; - if (mCompleteThreadNum == THREAD_NUM) { - if (configFile.exists()) { - configFile.delete(); - } - mListener.onComplete(); - isDownloading = false; - return; - } - continue; - } - //分配下载位置 - Object record = pro.getProperty(dFile.getName() + "_record_" + i); - if (!isNewTask - && record != null - && Long.parseLong(record + "") > 0) { //如果有记录,则恢复下载 - Long r = Long.parseLong(record + ""); - mCurrentLocation += r - startL; - Log.d(TAG, "++++++++++ 线程_" + i + "_恢复下载 ++++++++++"); - mListener.onChildResume(r); - startL = r; - recordL[rl] = i; - rl++; - } else { - isNewTask = true; - } - if (isNewTask) { - recordL[rl] = i; - rl++; - } - if (i == (THREAD_NUM - 1)) { - //如果整个文件的大小不为线程个数的整数倍,则最后一个线程的结束位置即为文件的总长度 - endL = fileLength; - } - ConfigEntity entity = - new ConfigEntity(mContext, fileLength, downloadUrl, dFile, i, startL, endL); - DownLoadTask task = new DownLoadTask(entity); - mTask.put(i, task); - } - if (mCurrentLocation > 0) { - mListener.onResume(mCurrentLocation); - } else { - mListener.onStart(mCurrentLocation); - } - for (int l : recordL) { - if (l == -1) continue; - Runnable task = mTask.get(l); - if (task != null) { - mFixedThreadPool.execute(task); - } - } - } else { - failDownload("下载失败,返回码:" + code); - } - } catch (IOException e) { - failDownload("下载失败【downloadUrl:" - + downloadUrl - + "】\n【filePath:" - + filePath - + "】" - + CommonUtil.getPrintException(e)); - } - } - }).start(); + new Thread(this).start(); } @Override public void resumeDownload() { @@ -331,6 +220,158 @@ final class DownloadUtil implements IDownloadUtil { "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"); } + @Override public void run() { + try { + URL url = new URL(mDownloadEntity.getDownloadUrl()); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + setConnectParam(conn); + conn.setRequestProperty("Range", "bytes=" + 0 + "-"); + conn.setConnectTimeout(mConnectTimeOut * 4); + conn.connect(); + int len = conn.getContentLength(); + //if (len < 0) { //网络被劫持时会出现这个问题 + // failDownload("下载失败,网络被劫持"); + // return; + //} + int code = conn.getResponseCode(); + //https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81 + //206支持断点 + if (code == HttpURLConnection.HTTP_PARTIAL) { + isSupportBreakpoint = true; + mListener.supportBreakpoint(true); + handleBreakpoint(conn); + } else if (code == HttpURLConnection.HTTP_OK || len < 0) { + //在conn.setRequestProperty("Range", "bytes=" + 0 + "-");下,200为不支持断点状态 + isSupportBreakpoint = false; + mListener.supportBreakpoint(false); + Log.w(TAG, "该下载链接不支持断点下载"); + handleBreakpoint(conn); + } else { + failDownload("下载失败,返回码:" + code); + } + } catch (IOException e) { + failDownload("下载失败【downloadUrl:" + + mDownloadEntity.getDownloadUrl() + + "】\n【filePath:" + + mDownloadFile.getPath() + + "】" + + CommonUtil.getPrintException(e)); + } + } + + /** + * 处理断点 + */ + private void handleBreakpoint(HttpURLConnection conn) throws IOException { + //不支持断点只能单线程下载 + if (!isSupportBreakpoint) { + ConfigEntity entity = new ConfigEntity(); + entity.fileSize = conn.getContentLength(); + entity.downloadUrl = mDownloadEntity.getDownloadUrl(); + entity.tempFile = mDownloadFile; + entity.threadId = 0; + entity.startLocation = 0; + entity.endLocation = entity.fileSize; + SingleThreadTask task = new SingleThreadTask(entity); + mFixedThreadPool.execute(task); + mListener.onStart(0); + return; + } + int fileLength = conn.getContentLength(); + //必须建一个文件 + CommonUtil.createFile(mDownloadFile.getPath()); + RandomAccessFile file = new RandomAccessFile(mDownloadFile.getPath(), "rwd"); + //设置文件长度 + file.setLength(fileLength); + mListener.onPostPre(fileLength); + //分配每条线程的下载区间 + Properties pro = null; + pro = CommonUtil.loadConfig(mConfigFile); + if (pro.isEmpty()) { + isNewTask = true; + } else { + for (int i = 0; i < THREAD_NUM; i++) { + if (pro.getProperty(mDownloadFile.getName() + "_record_" + i) == null) { + Object state = pro.getProperty(mDownloadFile.getName() + "_state_" + i); + if (state != null && Integer.parseInt(state + "") == 1) { + continue; + } + isNewTask = true; + break; + } + } + } + int blockSize = fileLength / THREAD_NUM; + int[] recordL = new int[THREAD_NUM]; + int rl = 0; + for (int i = 0; i < THREAD_NUM; i++) { + recordL[i] = -1; + } + for (int i = 0; i < THREAD_NUM; i++) { + long startL = i * blockSize, endL = (i + 1) * blockSize; + Object state = pro.getProperty(mDownloadFile.getName() + "_state_" + i); + if (state != null && Integer.parseInt(state + "") == 1) { //该线程已经完成 + mCurrentLocation += endL - startL; + Log.d(TAG, "++++++++++ 线程_" + i + "_已经下载完成 ++++++++++"); + mCompleteThreadNum++; + mStopNum++; + mCancelNum++; + if (mCompleteThreadNum == THREAD_NUM) { + if (mConfigFile.exists()) { + mConfigFile.delete(); + } + mListener.onComplete(); + isDownloading = false; + return; + } + continue; + } + //分配下载位置 + Object record = pro.getProperty(mDownloadFile.getName() + "_record_" + i); + //如果有记录,则恢复下载 + if (!isNewTask && record != null && Long.parseLong(record + "") > 0) { + Long r = Long.parseLong(record + ""); + mCurrentLocation += r - startL; + Log.d(TAG, "++++++++++ 线程_" + i + "_恢复下载 ++++++++++"); + mListener.onChildResume(r); + startL = r; + recordL[rl] = i; + rl++; + } else { + isNewTask = true; + } + if (isNewTask) { + recordL[rl] = i; + rl++; + } + if (i == (THREAD_NUM - 1)) { + //如果整个文件的大小不为线程个数的整数倍,则最后一个线程的结束位置即为文件的总长度 + endL = fileLength; + } + ConfigEntity entity = new ConfigEntity(); + entity.fileSize = fileLength; + entity.downloadUrl = mDownloadEntity.getDownloadUrl(); + entity.tempFile = mDownloadFile; + entity.threadId = i; + entity.startLocation = startL; + entity.endLocation = endL; + SingleThreadTask task = new SingleThreadTask(entity); + mTask.put(i, task); + } + if (mCurrentLocation > 0) { + mListener.onResume(mCurrentLocation); + } else { + mListener.onStart(mCurrentLocation); + } + for (int l : recordL) { + if (l == -1) continue; + Runnable task = mTask.get(l); + if (task != null && !mFixedThreadPool.isShutdown()) { + mFixedThreadPool.execute(task); + } + } + } + /** * 子线程下载信息类 */ @@ -342,68 +383,62 @@ final class DownloadUtil implements IDownloadUtil { long startLocation; long endLocation; File tempFile; - Context context; - - private ConfigEntity(Context context, long fileSize, String downloadUrl, File file, - int threadId, long startLocation, long endLocation) { - this.fileSize = fileSize; - this.downloadUrl = downloadUrl; - this.tempFile = file; - this.threadId = threadId; - this.startLocation = startLocation; - this.endLocation = endLocation; - this.context = context; - } } /** * 单个线程的下载任务 */ - private class DownLoadTask implements Runnable { - private static final String TAG = "DownLoadTask"; - private ConfigEntity dEntity; + private class SingleThreadTask implements Runnable { + private static final String TAG = "SingleThreadTask"; + private ConfigEntity configEntity; private String configFPath; private long currentLocation = 0; - private DownLoadTask(ConfigEntity downloadInfo) { - this.dEntity = downloadInfo; - configFPath = dEntity.context.getFilesDir().getPath() - + "/temp/" - + dEntity.tempFile.getName() - + ".properties"; + private SingleThreadTask(ConfigEntity downloadInfo) { + this.configEntity = downloadInfo; + if (isSupportBreakpoint) { + configFPath = mContext.getFilesDir().getPath() + + "/temp/" + + configEntity.tempFile.getName() + + ".properties"; + } } @Override public void run() { HttpURLConnection conn = null; InputStream is = null; try { - Log.d(TAG, "线程_" - + dEntity.threadId - + "_正在下载【开始位置 : " - + dEntity.startLocation - + ",结束位置:" - + dEntity.endLocation - + "】"); - URL url = new URL(dEntity.downloadUrl); + URL url = new URL(configEntity.downloadUrl); conn = (HttpURLConnection) url.openConnection(); - //在头里面请求下载开始位置和结束位置 - conn.setRequestProperty("Range", - "bytes=" + dEntity.startLocation + "-" + dEntity.endLocation); + if (isSupportBreakpoint) { + Log.d(TAG, "线程_" + + configEntity.threadId + + "_正在下载【开始位置 : " + + configEntity.startLocation + + ",结束位置:" + + configEntity.endLocation + + "】"); + //在头里面请求下载开始位置和结束位置 + conn.setRequestProperty("Range", + "bytes=" + configEntity.startLocation + "-" + configEntity.endLocation); + } else { + Log.w(TAG, "该下载不支持断点,即将重新下载"); + } setConnectParam(conn); conn.setConnectTimeout(mConnectTimeOut); conn.setReadTimeout(mReadTimeOut); //设置读取流的等待时间,必须设置该参数 is = conn.getInputStream(); //创建可设置位置的文件 - RandomAccessFile file = new RandomAccessFile(dEntity.tempFile, "rwd"); + RandomAccessFile file = new RandomAccessFile(configEntity.tempFile, "rwd"); //设置每条线程写入文件的位置 - file.seek(dEntity.startLocation); + file.seek(configEntity.startLocation); byte[] buffer = new byte[1024]; int len; //当前子线程的下载位置 - currentLocation = dEntity.startLocation; + currentLocation = configEntity.startLocation; while ((len = is.read(buffer)) != -1) { if (isCancel) { - Log.d(TAG, "++++++++++ thread_" + dEntity.threadId + "_cancel ++++++++++"); + Log.d(TAG, "++++++++++ thread_" + configEntity.threadId + "_cancel ++++++++++"); break; } if (isStop) { @@ -412,7 +447,6 @@ final class DownloadUtil implements IDownloadUtil { //把下载数据数据写入文件 file.write(buffer, 0, len); progress(len); - currentLocation += len; } file.close(); //close 为阻塞的,需要使用线程池来处理 @@ -420,32 +454,40 @@ final class DownloadUtil implements IDownloadUtil { conn.disconnect(); if (isCancel) { - cancel(); return; } //停止状态不需要删除记录文件 if (isStop) { - //stop(); return; } - Log.i(TAG, "线程【" + dEntity.threadId + "】下载完毕"); - writeConfig(dEntity.tempFile.getName() + "_state_" + dEntity.threadId, 1 + ""); - mListener.onChildComplete(dEntity.endLocation); - mCompleteThreadNum++; - if (mCompleteThreadNum == THREAD_NUM) { - File configFile = new File(configFPath); - if (configFile.exists()) { - configFile.delete(); + //支持断点的处理 + if (isSupportBreakpoint) { + Log.i(TAG, "线程【" + configEntity.threadId + "】下载完毕"); + writeConfig(configEntity.tempFile.getName() + "_state_" + configEntity.threadId, 1 + ""); + mListener.onChildComplete(configEntity.endLocation); + mCompleteThreadNum++; + if (mCompleteThreadNum == THREAD_NUM) { + File configFile = new File(configFPath); + if (configFile.exists()) { + configFile.delete(); + } + isDownloading = false; + mListener.onComplete(); } + } else { + Log.i(TAG, "下载任务完成"); isDownloading = false; mListener.onComplete(); } } catch (MalformedURLException e) { - failDownload(dEntity, currentLocation, "下载链接异常", e); + mFailNum++; + failDownload(configEntity, currentLocation, "下载链接异常", e); } catch (IOException e) { - failDownload(dEntity, currentLocation, "下载失败【" + dEntity.downloadUrl + "】", e); + mFailNum++; + failDownload(configEntity, currentLocation, "下载失败【" + configEntity.downloadUrl + "】", e); } catch (Exception e) { - failDownload(dEntity, currentLocation, "获取流失败", e); + mFailNum++; + failDownload(configEntity, currentLocation, "获取流失败", e); } } @@ -455,11 +497,19 @@ final class DownloadUtil implements IDownloadUtil { protected void stop() { synchronized (LOCK) { try { - mStopNum++; - String location = String.valueOf(currentLocation); - Log.i(TAG, "thread_" + dEntity.threadId + "_stop, stop location ==> " + currentLocation); - writeConfig(dEntity.tempFile.getName() + "_record_" + dEntity.threadId, location); - if (mStopNum == THREAD_NUM) { + if (isSupportBreakpoint) { + mStopNum++; + String location = String.valueOf(currentLocation); + Log.i(TAG, + "thread_" + configEntity.threadId + "_stop, stop location ==> " + currentLocation); + writeConfig(configEntity.tempFile.getName() + "_record_" + configEntity.threadId, + location); + if (mStopNum == THREAD_NUM) { + Log.d(TAG, "++++++++++++++++ onStop +++++++++++++++++"); + isDownloading = false; + mListener.onStop(mCurrentLocation); + } + } else { Log.d(TAG, "++++++++++++++++ onStop +++++++++++++++++"); isDownloading = false; mListener.onStop(mCurrentLocation); @@ -471,41 +521,41 @@ final class DownloadUtil implements IDownloadUtil { } /** - * 取消下载 + * 下载中 */ - private void cancel() { - synchronized (LOCK) { - mCancelNum++; - if (mCancelNum == THREAD_NUM) { - File configFile = new File(configFPath); - if (configFile.exists()) { - configFile.delete(); - } - if (dEntity.tempFile.exists()) { - dEntity.tempFile.delete(); - } - Log.d(TAG, "++++++++++++++++ onCancel +++++++++++++++++"); - isDownloading = false; - mListener.onCancel(); - } - } - } - private void progress(long len) { synchronized (LOCK) { + currentLocation += len; mCurrentLocation += len; mListener.onProgress(mCurrentLocation); } } /** - * 将记录写入到配置文件 + * 取消下载 */ - private void writeConfig(String key, String record) throws IOException { - File configFile = new File(configFPath); - Properties pro = CommonUtil.loadConfig(configFile); - pro.setProperty(key, record); - CommonUtil.saveConfig(configFile, pro); + private void cancel() { + synchronized (LOCK) { + if (isSupportBreakpoint) { + mCancelNum++; + if (mCancelNum == THREAD_NUM) { + File configFile = new File(configFPath); + if (configFile.exists()) { + configFile.delete(); + } + if (configEntity.tempFile.exists()) { + configEntity.tempFile.delete(); + } + Log.d(TAG, "++++++++++++++++ onCancel +++++++++++++++++"); + isDownloading = false; + mListener.onCancel(); + } + } else { + Log.d(TAG, "++++++++++++++++ onCancel +++++++++++++++++"); + isDownloading = false; + mListener.onCancel(); + } + } } /** @@ -517,19 +567,36 @@ final class DownloadUtil implements IDownloadUtil { try { isDownloading = false; isStop = true; - Log.e(TAG, msg); if (ex != null) { Log.e(TAG, CommonUtil.getPrintException(ex)); } - if (currentLocation != -1) { - String location = String.valueOf(currentLocation); - writeConfig(dEntity.tempFile.getName() + "_record_" + dEntity.threadId, location); + if (isSupportBreakpoint) { + if (currentLocation != -1) { + String location = String.valueOf(currentLocation); + writeConfig(dEntity.tempFile.getName() + "_record_" + dEntity.threadId, location); + } + if (mFailNum == THREAD_NUM) { + Log.d(TAG, "++++++++++++++++ onFail +++++++++++++++++"); + mListener.onFail(); + } + } else { + Log.d(TAG, "++++++++++++++++ onFail +++++++++++++++++"); + mListener.onFail(); } - mListener.onFail(); } catch (IOException e) { e.printStackTrace(); } } } + + /** + * 将记录写入到配置文件 + */ + private void writeConfig(String key, String record) throws IOException { + File configFile = new File(configFPath); + Properties pro = CommonUtil.loadConfig(configFile); + pro.setProperty(key, record); + CommonUtil.saveConfig(configFile, pro); + } } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/task/IDownloadListener.java b/Aria/src/main/java/com/arialyy/aria/core/task/IDownloadListener.java index 78a5d604..037bd808 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/task/IDownloadListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/task/IDownloadListener.java @@ -14,13 +14,20 @@ * limitations under the License. */ - package com.arialyy.aria.core.task; /** * 下载监听 */ public interface IDownloadListener { + + /** + * 支持断点回调 + * + * @param support true,支持;false 不支持 + */ + public void supportBreakpoint(boolean support); + /** * 取消下载 */ diff --git a/Aria/src/main/java/com/arialyy/aria/core/task/Task.java b/Aria/src/main/java/com/arialyy/aria/core/task/Task.java index 8b29994e..fdccc9d2 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/task/Task.java +++ b/Aria/src/main/java/com/arialyy/aria/core/task/Task.java @@ -18,14 +18,16 @@ package com.arialyy.aria.core.task; import android.content.Context; import android.content.Intent; -import android.net.Uri; import android.os.Handler; import android.util.Log; +import com.arialyy.aria.core.Aria; import com.arialyy.aria.core.DownloadManager; import com.arialyy.aria.core.scheduler.DownloadSchedulers; import com.arialyy.aria.core.scheduler.IDownloadSchedulers; import com.arialyy.aria.core.DownloadEntity; +import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.Configuration; +import java.lang.ref.WeakReference; /** * Created by lyy on 2016/8/11. @@ -43,17 +45,39 @@ public class Task { private Context mContext; private IDownloadUtil mUtil; - private Task(Context context, DownloadEntity entity) { + private Task(Context context, DownloadEntity entity, Handler outHandler) { mContext = context.getApplicationContext(); mEntity = entity; + mOutHandler = outHandler; init(); } private void init() { - mListener = new DListener(mContext, mEntity, mOutHandler); + mListener = new DListener(mContext, this, mOutHandler); mUtil = new DownloadUtil(mContext, mEntity, mListener); } + /** + * 获取下载速度 + */ + public long getSpeed() { + return mEntity.getSpeed(); + } + + /** + * 获取文件大小 + */ + public long getFileSize(){ + return mEntity.getFileSize(); + } + + /** + * 获取当前下载进度 + */ + public long getCurrentProgress(){ + return mEntity.getCurrentProgress(); + } + /** * 开始下载 */ @@ -62,7 +86,7 @@ public class Task { Log.d(TAG, "任务正在下载"); } else { if (mListener == null) { - mListener = new DListener(mContext, mEntity, mOutHandler); + mListener = new DListener(mContext, this, mOutHandler); } mUtil.startDownload(); } @@ -89,23 +113,17 @@ public class Task { } else { mEntity.setState(DownloadEntity.STATE_STOP); mEntity.save(); - sendInState2Target(DownloadSchedulers.STOP); - + if (mOutHandler != null) { + mOutHandler.obtainMessage(DownloadSchedulers.STOP, this).sendToTarget(); + } // 发送停止下载的广播 - Intent intent = createIntent(DownloadManager.ACTION_STOP); - intent.putExtra(DownloadManager.CURRENT_LOCATION, mEntity.getCurrentProgress()); - intent.putExtra(DownloadManager.ENTITY, mEntity); + Intent intent = CommonUtil.createIntent(mContext.getPackageName(), Aria.ACTION_STOP); + intent.putExtra(Aria.CURRENT_LOCATION, mEntity.getCurrentProgress()); + intent.putExtra(Aria.ENTITY, mEntity); mContext.sendBroadcast(intent); } } - /** - * 获取下载工具 - */ - public IDownloadUtil getDownloadUtil() { - return mUtil; - } - /** * 任务下载状态 */ @@ -125,38 +143,16 @@ public class Task { mUtil.delConfigFile(); mUtil.delTempFile(); mEntity.deleteData(); - sendInState2Target(DownloadSchedulers.CANCEL); - + if (mOutHandler != null) { + mOutHandler.obtainMessage(DownloadSchedulers.CANCEL, this).sendToTarget(); + } //发送取消下载的广播 - Intent intent = createIntent(DownloadManager.ACTION_CANCEL); - intent.putExtra(DownloadManager.ENTITY, mEntity); + Intent intent = CommonUtil.createIntent(mContext.getPackageName(), Aria.ACTION_CANCEL); + intent.putExtra(Aria.ENTITY, mEntity); mContext.sendBroadcast(intent); } } - /** - * 创建特定的Intent - */ - private Intent createIntent(String action) { - Uri.Builder builder = new Uri.Builder(); - builder.scheme(mContext.getPackageName()); - Uri uri = builder.build(); - Intent intent = new Intent(action); - intent.setData(uri); - return intent; - } - - /** - * 将任务状态发送给下载器 - * - * @param state {@link DownloadSchedulers#START} - */ - private void sendInState2Target(int state) { - if (mOutHandler != null) { - mOutHandler.obtainMessage(state, this).sendToTarget(); - } - } - static class Builder { DownloadEntity downloadEntity; Handler outHandler; @@ -169,7 +165,7 @@ public class Task { this("", context, downloadEntity); } - public Builder(String targetName, Context context, DownloadEntity downloadEntity) { + Builder(String targetName, Context context, DownloadEntity downloadEntity) { this.targetName = targetName; this.context = context; this.downloadEntity = downloadEntity; @@ -180,7 +176,7 @@ public class Task { * * @param schedulers {@link IDownloadSchedulers} */ - public Builder setOutHandler(IDownloadSchedulers schedulers) { + Builder setOutHandler(IDownloadSchedulers schedulers) { this.outHandler = new Handler(schedulers); return this; } @@ -204,8 +200,7 @@ public class Task { //} public Task build() { - Task task = new Task(context, downloadEntity); - task.mOutHandler = outHandler; + Task task = new Task(context, downloadEntity, outHandler); task.setTargetName(targetName); downloadEntity.save(); return task; @@ -215,29 +210,33 @@ public class Task { /** * 下载监听类 */ - private class DListener extends DownloadListener { - Handler outHandler; - Context context; - Intent sendIntent; + private static class DListener extends DownloadListener { + WeakReference outHandler; + WeakReference wTask; + Context context; + Intent sendIntent; long INTERVAL = 1024 * 10; //10k大小的间隔 long lastLen = 0; //上一次发送长度 long lastTime = 0; long INTERVAL_TIME = 1000; //1m更新周期 boolean isFirst = true; DownloadEntity downloadEntity; + Task task; - DListener(Context context, DownloadEntity downloadEntity, Handler outHandler) { + DListener(Context context, Task task, Handler outHandler) { this.context = context; - this.outHandler = outHandler; - this.downloadEntity = downloadEntity; - sendIntent = createIntent(DownloadManager.ACTION_RUNNING); - sendIntent.putExtra(DownloadManager.ENTITY, downloadEntity); + this.outHandler = new WeakReference<>(outHandler); + this.wTask = new WeakReference<>(task); + this.task = wTask.get(); + this.downloadEntity = this.task.getDownloadEntity(); + sendIntent = CommonUtil.createIntent(context.getPackageName(), Aria.ACTION_RUNNING); + sendIntent.putExtra(Aria.ENTITY, downloadEntity); } @Override public void onPre() { super.onPre(); downloadEntity.setState(DownloadEntity.STATE_PRE); - sendIntent(DownloadManager.ACTION_PRE, -1); + sendIntent(Aria.ACTION_PRE, -1); } @Override public void onPostPre(long fileSize) { @@ -245,30 +244,29 @@ public class Task { downloadEntity.setFileSize(fileSize); downloadEntity.setState(DownloadEntity.STATE_POST_PRE); sendInState2Target(DownloadSchedulers.PRE); - sendIntent(DownloadManager.ACTION_POST_PRE, -1); + sendIntent(Aria.ACTION_POST_PRE, -1); } @Override public void onResume(long resumeLocation) { super.onResume(resumeLocation); downloadEntity.setState(DownloadEntity.STATE_DOWNLOAD_ING); sendInState2Target(DownloadSchedulers.RESUME); - sendIntent(DownloadManager.ACTION_RESUME, resumeLocation); + sendIntent(Aria.ACTION_RESUME, resumeLocation); } @Override public void onStart(long startLocation) { super.onStart(startLocation); downloadEntity.setState(DownloadEntity.STATE_DOWNLOAD_ING); - downloadEntity.setFailNum(0); sendInState2Target(DownloadSchedulers.START); - sendIntent(DownloadManager.ACTION_START, startLocation); + sendIntent(Aria.ACTION_START, startLocation); } @Override public void onProgress(long currentLocation) { super.onProgress(currentLocation); if (System.currentTimeMillis() - lastTime > INTERVAL_TIME) { long speed = currentLocation - lastLen; - sendIntent.putExtra(DownloadManager.CURRENT_LOCATION, currentLocation); - sendIntent.putExtra(DownloadManager.CURRENT_SPEED, speed); + sendIntent.putExtra(Aria.CURRENT_LOCATION, currentLocation); + sendIntent.putExtra(Aria.CURRENT_SPEED, speed); lastTime = System.currentTimeMillis(); if (isFirst) { downloadEntity.setSpeed(0); @@ -288,14 +286,14 @@ public class Task { downloadEntity.setState(DownloadEntity.STATE_STOP); downloadEntity.setSpeed(0); sendInState2Target(DownloadSchedulers.STOP); - sendIntent(DownloadManager.ACTION_STOP, stopLocation); + sendIntent(Aria.ACTION_STOP, stopLocation); } @Override public void onCancel() { super.onCancel(); downloadEntity.setState(DownloadEntity.STATE_CANCEL); sendInState2Target(DownloadSchedulers.CANCEL); - sendIntent(DownloadManager.ACTION_CANCEL, -1); + sendIntent(Aria.ACTION_CANCEL, -1); downloadEntity.deleteData(); } @@ -305,7 +303,7 @@ public class Task { downloadEntity.setDownloadComplete(true); downloadEntity.setSpeed(0); sendInState2Target(DownloadSchedulers.COMPLETE); - sendIntent(DownloadManager.ACTION_COMPLETE, downloadEntity.getFileSize()); + sendIntent(Aria.ACTION_COMPLETE, downloadEntity.getFileSize()); } @Override public void onFail() { @@ -314,17 +312,28 @@ public class Task { downloadEntity.setState(DownloadEntity.STATE_FAIL); downloadEntity.setSpeed(0); sendInState2Target(DownloadSchedulers.FAIL); - sendIntent(DownloadManager.ACTION_FAIL, -1); + sendIntent(Aria.ACTION_FAIL, -1); + } + + /** + * 将任务状态发送给下载器 + * + * @param state {@link DownloadSchedulers#START} + */ + private void sendInState2Target(int state) { + if (outHandler.get() != null) { + outHandler.get().obtainMessage(state, task).sendToTarget(); + } } private void sendIntent(String action, long location) { - downloadEntity.setDownloadComplete(action.equals(DownloadManager.ACTION_COMPLETE)); + downloadEntity.setDownloadComplete(action.equals(Aria.ACTION_COMPLETE)); downloadEntity.setCurrentProgress(location); downloadEntity.update(); - Intent intent = createIntent(action); - intent.putExtra(DownloadManager.ENTITY, downloadEntity); + Intent intent = CommonUtil.createIntent(context.getPackageName(), action); + intent.putExtra(Aria.ENTITY, downloadEntity); if (location != -1) { - intent.putExtra(DownloadManager.CURRENT_LOCATION, location); + intent.putExtra(Aria.CURRENT_LOCATION, location); } if (Configuration.isOpenBreadCast) { context.sendBroadcast(intent); diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DbEntity.java b/Aria/src/main/java/com/arialyy/aria/orm/DbEntity.java index 1d24fd33..c1dfbe08 100644 --- a/Aria/src/main/java/com/arialyy/aria/orm/DbEntity.java +++ b/Aria/src/main/java/com/arialyy/aria/orm/DbEntity.java @@ -55,24 +55,28 @@ public class DbEntity { /** * 查询一组数据 + * + * DownloadEntity.findData(DownloadEntity.class, "downloadUrl=?", downloadUrl); + * * * @return 没有数据返回null */ - public static List findDatas(Class clazz, @NonNull String[] wheres, - @NonNull String[] values) { + public static List findDatas(Class clazz, String... expression) { DbUtil util = DbUtil.getInstance(); - return util.findData(clazz, wheres, values); + return util.findData(clazz, expression); } /** * 查询一行数据 + * + * DownloadEntity.findData(DownloadEntity.class, "downloadUrl=?", downloadUrl); + * * * @return 没有数据返回null */ - public static T findData(Class clazz, @NonNull String[] wheres, - @NonNull String[] values) { + public static T findData(Class clazz, String... expression) { DbUtil util = DbUtil.getInstance(); - List datas = util.findData(clazz, wheres, values); + List datas = util.findData(clazz, expression); return datas == null ? null : datas.size() > 0 ? datas.get(0) : null; } @@ -94,14 +98,19 @@ public class DbEntity { * 删除当前数据 */ public void deleteData() { - mUtil.delData(getClass(), new Object[] { "rowid" }, new Object[] { rowID }); + //mUtil.delData(getClass(), new Object[] { "rowid" }, new Object[] { rowID }); + deleteData(getClass(), "rowid=?", rowID + ""); } /** * 根据条件删除数据 + * + * DownloadEntity.deleteData(DownloadEntity.class, "downloadUrl=?", downloadUrl); + * */ - public void deleteData(@NonNull Object[] wheres, @NonNull Object[] values) { - mUtil.delData(getClass(), wheres, values); + public static void deleteData(Class clazz, String... expression) { + DbUtil util = DbUtil.getInstance(); + util.delData(clazz, expression); } /** @@ -128,7 +137,7 @@ public class DbEntity { * 查找数据在表中是否存在 */ private boolean thisIsExist() { - return findData(getClass(), new String[] { "rowid" }, new String[] { rowID + "" }) != null; + return findData(getClass(), "rowid=?", rowID + "") != null; } /** @@ -139,6 +148,13 @@ public class DbEntity { updateRowID(); } + private T findData(Class clazz, @NonNull String[] wheres, + @NonNull String[] values) { + DbUtil util = DbUtil.getInstance(); + List list = util.findData(clazz, wheres, values); + return list == null ? null : list.get(0); + } + private void updateRowID() { try { Field[] fields = CommonUtil.getFields(getClass()); diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DbUtil.java b/Aria/src/main/java/com/arialyy/aria/orm/DbUtil.java index a8c3bdab..4f6fe2b1 100644 --- a/Aria/src/main/java/com/arialyy/aria/orm/DbUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/orm/DbUtil.java @@ -22,6 +22,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; import android.util.Log; +import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CommonUtil; import java.lang.reflect.Field; import java.util.ArrayList; @@ -76,8 +77,8 @@ public class DbUtil { /** * 删除某条数据 */ - synchronized void delData(Class clazz, @NonNull Object[] wheres, - @NonNull Object[] values) { + @Deprecated private synchronized void delData(Class clazz, + @NonNull Object[] wheres, @NonNull Object[] values) { mDb = mHelper.getWritableDatabase(); if (wheres.length <= 0 || values.length <= 0) { Log.e(TAG, "输入删除条件"); @@ -99,6 +100,25 @@ public class DbUtil { close(); } + /** + * 删除某条数据 + */ + synchronized void delData(Class clazz, String... expression) { + CheckUtil.checkSqlExpression(expression); + mDb = mHelper.getWritableDatabase(); + String sql = "DELETE FROM " + CommonUtil.getClassName(clazz) + " WHERE " + 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] = "'" + expression[i + 1] + "'"; + } + sql = String.format(sql, params); + Log.d(TAG, sql); + print(DEL_DATA, sql); + mDb.execSQL(sql); + close(); + } + /** * 修改某行数据 */ @@ -151,8 +171,31 @@ public class DbUtil { /** * 条件查寻数据 */ - synchronized List findData(Class clazz, @NonNull String[] wheres, - @NonNull String[] values) { + synchronized List findData(Class clazz, String... expression) { + if (!tableExists(clazz)) { + createTable(clazz); + } + mDb = mHelper.getReadableDatabase(); + CheckUtil.checkSqlExpression(expression); + String sql = + "SELECT rowid, * FROM " + CommonUtil.getClassName(clazz) + " WHERE " + 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] = "'" + expression[i + 1] + "'"; + } + sql = String.format(sql, params); + Log.d(TAG, sql); + print(FIND_DATA, sql); + Cursor cursor = mDb.rawQuery(sql, null); + return cursor.getCount() > 0 ? newInstanceEntity(clazz, cursor) : null; + } + + /** + * 条件查寻数据 + */ + @Deprecated synchronized List findData(Class clazz, + @NonNull String[] wheres, @NonNull String[] values) { if (!tableExists(clazz)) { createTable(clazz); } @@ -436,7 +479,7 @@ public class DbUtil { } entity.rowID = cursor.getInt(cursor.getColumnIndex("rowid")); entitys.add(entity); - Log.d(TAG, "rowid ==> " + entity.rowID); + //Log.d(TAG, "rowid ==> " + entity.rowID); } } catch (InstantiationException e) { e.printStackTrace(); diff --git a/Aria/src/main/java/com/arialyy/aria/orm/SqlHelper.java b/Aria/src/main/java/com/arialyy/aria/orm/SqlHelper.java index a7b6c669..4ab30b02 100644 --- a/Aria/src/main/java/com/arialyy/aria/orm/SqlHelper.java +++ b/Aria/src/main/java/com/arialyy/aria/orm/SqlHelper.java @@ -26,7 +26,7 @@ import android.text.TextUtils; * Created by lyy on 2015/11/2. * sql帮助类 */ -public class SqlHelper extends SQLiteOpenHelper { +final class SqlHelper extends SQLiteOpenHelper { protected static String DB_NAME; protected static int VERSION = -1; 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 ba70cda1..7cffe259 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java @@ -14,13 +14,14 @@ * limitations under the License. */ - package com.arialyy.aria.util; import android.text.TextUtils; import android.util.Log; import com.arialyy.aria.core.DownloadEntity; import java.io.File; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Created by Lyy on 2016/9/23. @@ -29,6 +30,41 @@ import java.io.File; public class CheckUtil { private static final String TAG = "CheckUtil"; + /** + * 检查sql的expression是否合法 + */ + public static void checkSqlExpression(String... expression) { + if (expression.length == 0) { + throw new IllegalArgumentException("sql语句表达式不能为null"); + } + if (expression.length == 1) { + throw new IllegalArgumentException("表达式需要写入参数"); + } + String where = expression[0]; + if (!where.contains("?")) { + throw new IllegalArgumentException("请在where语句的'='后编写?"); + } + Pattern pattern = Pattern.compile("\\?"); + Matcher matcher = pattern.matcher(where); + int count = 0; + while (matcher.find()) { + count++; + } + if (count < expression.length - 1){ + throw new IllegalArgumentException("条件语句的?个数不能小于参数个数"); + } + if (count > expression.length - 1){ + throw new IllegalArgumentException("条件语句的?个数不能大于参数个数"); + } + } + + /** + * 检测下载链接是否为null + */ + public static void checkDownloadUrl(String downloadUrl) { + if (TextUtils.isEmpty(downloadUrl)) throw new IllegalArgumentException("下载链接不能为null"); + } + /** * 检测下载实体是否合法 * @@ -43,11 +79,10 @@ public class CheckUtil { Log.w(TAG, "下载链接不能为空"); return false; } else if (TextUtils.isEmpty(entity.getFileName())) { - Log.w(TAG, "文件名不能为空"); - return false; + //Log.w(TAG, "文件名不能为空"); + throw new IllegalArgumentException("文件名不能为null"); } else if (TextUtils.isEmpty(entity.getDownloadPath())) { - Log.w(TAG, "存储地址不能为空"); - return false; + throw new IllegalArgumentException("文件保存路径不能为null"); } String fileName = entity.getFileName(); if (fileName.contains(" ")) { 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 bdcf94c2..612078b3 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java @@ -17,7 +17,9 @@ package com.arialyy.aria.util; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; +import android.net.Uri; import android.util.Log; import com.arialyy.aria.core.command.CmdFactory; import com.arialyy.aria.core.DownloadEntity; @@ -46,6 +48,18 @@ public class CommonUtil { return CmdFactory.getInstance().createCmd(entity, cmd); } + /** + * 创建隐性的Intent + */ + public static Intent createIntent(String packageName, String action) { + Uri.Builder builder = new Uri.Builder(); + builder.scheme(packageName); + Uri uri = builder.build(); + Intent intent = new Intent(action); + intent.setData(uri); + return intent; + } + /** * 存储字符串到配置文件 * diff --git a/Aria/src/main/java/com/arialyy/aria/util/WeakHandler.java b/Aria/src/main/java/com/arialyy/aria/util/WeakHandler.java new file mode 100644 index 00000000..2d176dde --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/util/WeakHandler.java @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2014 Badoo Trading Limited + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Portions of documentation in this code are modifications based on work created and + * shared by Android Open Source Project and used according to terms described in the + * Apache License, Version 2.0 + */ +package com.arialyy.aria.util; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; + +import java.lang.ref.WeakReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Memory safer implementation of android.os.Handler + * Original implementation of Handlers always keeps hard reference to handler in queue of execution. + * If you create anonymous handler and post delayed message into it, it will keep all parent class + * for that time in memory even if it could be cleaned. + * This implementation is trickier, it will keep WeakReferences to runnables and messages, + * and GC could collect them once WeakHandler instance is not referenced any more + * + * @see android.os.Handler + * + * Created by Dmytro Voronkevych on 17/06/2014. + */ +@SuppressWarnings("unused") +public class WeakHandler { + private final Handler.Callback mCallback; // hard reference to Callback. We need to keep callback in memory + private final ExecHandler mExec; + private Lock mLock = new ReentrantLock(); + @SuppressWarnings("ConstantConditions") + @VisibleForTesting + final ChainedRef mRunnables = new ChainedRef(mLock, null); + + /** + * Default constructor associates this handler with the {@link Looper} for the + * current thread. + * + * If this thread does not have a looper, this handler won't be able to receive messages + * so an exception is thrown. + */ + public WeakHandler() { + mCallback = null; + mExec = new ExecHandler(); + } + + /** + * Constructor associates this handler with the {@link Looper} for the + * current thread and takes a callback interface in which you can handle + * messages. + * + * If this thread does not have a looper, this handler won't be able to receive messages + * so an exception is thrown. + * + * @param callback The callback interface in which to handle messages, or null. + */ + public WeakHandler(@Nullable Handler.Callback callback) { + mCallback = callback; // Hard referencing body + mExec = new ExecHandler(new WeakReference<>(callback)); // Weak referencing inside ExecHandler + } + + /** + * Use the provided {@link Looper} instead of the default one. + * + * @param looper The looper, must not be null. + */ + public WeakHandler(@NonNull Looper looper) { + mCallback = null; + mExec = new ExecHandler(looper); + } + + /** + * Use the provided {@link Looper} instead of the default one and take a callback + * interface in which to handle messages. + * + * @param looper The looper, must not be null. + * @param callback The callback interface in which to handle messages, or null. + */ + public WeakHandler(@NonNull Looper looper, @NonNull Handler.Callback callback) { + mCallback = callback; + mExec = new ExecHandler(looper, new WeakReference<>(callback)); + } + + /** + * Causes the Runnable r to be added to the message queue. + * The runnable will be run on the thread to which this handler is + * attached. + * + * @param r The Runnable that will be executed. + * + * @return Returns true if the Runnable was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean post(@NonNull Runnable r) { + return mExec.post(wrapRunnable(r)); + } + + /** + * Causes the Runnable r to be added to the message queue, to be run + * at a specific time given by uptimeMillis. + * The time-base is {@link android.os.SystemClock#uptimeMillis}. + * The runnable will be run on the thread to which this handler is attached. + * + * @param r The Runnable that will be executed. + * @param uptimeMillis The absolute time at which the callback should run, + * using the {@link android.os.SystemClock#uptimeMillis} time-base. + * + * @return Returns true if the Runnable was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the Runnable will be processed -- if + * the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + */ + public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) { + return mExec.postAtTime(wrapRunnable(r), uptimeMillis); + } + + /** + * Causes the Runnable r to be added to the message queue, to be run + * at a specific time given by uptimeMillis. + * The time-base is {@link android.os.SystemClock#uptimeMillis}. + * The runnable will be run on the thread to which this handler is attached. + * + * @param r The Runnable that will be executed. + * @param uptimeMillis The absolute time at which the callback should run, + * using the {@link android.os.SystemClock#uptimeMillis} time-base. + * + * @return Returns true if the Runnable was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the Runnable will be processed -- if + * the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + * + * @see android.os.SystemClock#uptimeMillis + */ + public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) { + return mExec.postAtTime(wrapRunnable(r), token, uptimeMillis); + } + + /** + * Causes the Runnable r to be added to the message queue, to be run + * after the specified amount of time elapses. + * The runnable will be run on the thread to which this handler + * is attached. + * + * @param r The Runnable that will be executed. + * @param delayMillis The delay (in milliseconds) until the Runnable + * will be executed. + * + * @return Returns true if the Runnable was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the Runnable will be processed -- + * if the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + */ + public final boolean postDelayed(Runnable r, long delayMillis) { + return mExec.postDelayed(wrapRunnable(r), delayMillis); + } + + /** + * Posts a message to an object that implements Runnable. + * Causes the Runnable r to executed on the next iteration through the + * message queue. The runnable will be run on the thread to which this + * handler is attached. + * This method is only for use in very special circumstances -- it + * can easily starve the message queue, cause ordering problems, or have + * other unexpected side-effects. + * + * @param r The Runnable that will be executed. + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean postAtFrontOfQueue(Runnable r) { + return mExec.postAtFrontOfQueue(wrapRunnable(r)); + } + + /** + * Remove any pending posts of Runnable r that are in the message queue. + */ + public final void removeCallbacks(Runnable r) { + final WeakRunnable runnable = mRunnables.remove(r); + if (runnable != null) { + mExec.removeCallbacks(runnable); + } + } + + /** + * Remove any pending posts of Runnable r with Object + * token that are in the message queue. If token is null, + * all callbacks will be removed. + */ + public final void removeCallbacks(Runnable r, Object token) { + final WeakRunnable runnable = mRunnables.remove(r); + if (runnable != null) { + mExec.removeCallbacks(runnable, token); + } + } + + /** + * Pushes a message onto the end of the message queue after all pending messages + * before the current time. It will be received in callback, + * in the thread attached to this handler. + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean sendMessage(Message msg) { + return mExec.sendMessage(msg); + } + + /** + * Sends a Message containing only the what value. + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean sendEmptyMessage(int what) { + return mExec.sendEmptyMessage(what); + } + + /** + * Sends a Message containing only the what value, to be delivered + * after the specified amount of time elapses. + * @see #sendMessageDelayed(android.os.Message, long) + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { + return mExec.sendEmptyMessageDelayed(what, delayMillis); + } + + /** + * Sends a Message containing only the what value, to be delivered + * at a specific time. + * @see #sendMessageAtTime(android.os.Message, long) + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) { + return mExec.sendEmptyMessageAtTime(what, uptimeMillis); + } + + /** + * Enqueue a message into the message queue after all pending messages + * before (current time + delayMillis). You will receive it in + * callback, in the thread attached to this handler. + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the message will be processed -- if + * the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + */ + public final boolean sendMessageDelayed(Message msg, long delayMillis) { + return mExec.sendMessageDelayed(msg, delayMillis); + } + + /** + * Enqueue a message into the message queue after all pending messages + * before the absolute time (in milliseconds) uptimeMillis. + * The time-base is {@link android.os.SystemClock#uptimeMillis}. + * You will receive it in callback, in the thread attached + * to this handler. + * + * @param uptimeMillis The absolute time at which the message should be + * delivered, using the + * {@link android.os.SystemClock#uptimeMillis} time-base. + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the message will be processed -- if + * the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + */ + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + return mExec.sendMessageAtTime(msg, uptimeMillis); + } + + /** + * Enqueue a message at the front of the message queue, to be processed on + * the next iteration of the message loop. You will receive it in + * callback, in the thread attached to this handler. + * This method is only for use in very special circumstances -- it + * can easily starve the message queue, cause ordering problems, or have + * other unexpected side-effects. + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean sendMessageAtFrontOfQueue(Message msg) { + return mExec.sendMessageAtFrontOfQueue(msg); + } + + /** + * Remove any pending posts of messages with code 'what' that are in the + * message queue. + */ + public final void removeMessages(int what) { + mExec.removeMessages(what); + } + + /** + * Remove any pending posts of messages with code 'what' and whose obj is + * 'object' that are in the message queue. If object is null, + * all messages will be removed. + */ + public final void removeMessages(int what, Object object) { + mExec.removeMessages(what, object); + } + + /** + * Remove any pending posts of callbacks and sent messages whose + * obj is token. If token is null, + * all callbacks and messages will be removed. + */ + public final void removeCallbacksAndMessages(Object token) { + mExec.removeCallbacksAndMessages(token); + } + + /** + * Check if there are any pending posts of messages with code 'what' in + * the message queue. + */ + public final boolean hasMessages(int what) { + return mExec.hasMessages(what); + } + + /** + * Check if there are any pending posts of messages with code 'what' and + * whose obj is 'object' in the message queue. + */ + public final boolean hasMessages(int what, Object object) { + return mExec.hasMessages(what, object); + } + + public final Looper getLooper() { + return mExec.getLooper(); + } + + private WeakRunnable wrapRunnable(@NonNull Runnable r) { + //noinspection ConstantConditions + if (r == null) { + throw new NullPointerException("Runnable can't be null"); + } + final ChainedRef hardRef = new ChainedRef(mLock, r); + mRunnables.insertAfter(hardRef); + return hardRef.wrapper; + } + + private static class ExecHandler extends Handler { + private final WeakReference mCallback; + + ExecHandler() { + mCallback = null; + } + + ExecHandler(WeakReference callback) { + mCallback = callback; + } + + ExecHandler(Looper looper) { + super(looper); + mCallback = null; + } + + ExecHandler(Looper looper, WeakReference callback) { + super(looper); + mCallback = callback; + } + + @Override + public void handleMessage(@NonNull Message msg) { + if (mCallback == null) { + return; + } + final Handler.Callback callback = mCallback.get(); + if (callback == null) { // Already disposed + return; + } + callback.handleMessage(msg); + } + } + + static class WeakRunnable implements Runnable { + private final WeakReference mDelegate; + private final WeakReference mReference; + + WeakRunnable(WeakReference delegate, WeakReference reference) { + mDelegate = delegate; + mReference = reference; + } + + @Override + public void run() { + final Runnable delegate = mDelegate.get(); + final ChainedRef reference = mReference.get(); + if (reference != null) { + reference.remove(); + } + if (delegate != null) { + delegate.run(); + } + } + } + + static class ChainedRef { + @Nullable + ChainedRef next; + @Nullable + ChainedRef prev; + @NonNull + final Runnable runnable; + @NonNull + final WeakRunnable wrapper; + + @NonNull + Lock lock; + + public ChainedRef(@NonNull Lock lock, @NonNull Runnable r) { + this.runnable = r; + this.lock = lock; + this.wrapper = new WeakRunnable(new WeakReference<>(r), new WeakReference<>(this)); + } + + public WeakRunnable remove() { + lock.lock(); + try { + if (prev != null) { + prev.next = next; + } + if (next != null) { + next.prev = prev; + } + prev = null; + next = null; + } finally { + lock.unlock(); + } + return wrapper; + } + + public void insertAfter(@NonNull ChainedRef candidate) { + lock.lock(); + try { + if (this.next != null) { + this.next.prev = candidate; + } + + candidate.next = this.next; + this.next = candidate; + candidate.prev = this; + } finally { + lock.unlock(); + } + } + + @Nullable + public WeakRunnable remove(Runnable obj) { + lock.lock(); + try { + ChainedRef curr = this.next; // Skipping head + while (curr != null) { + if (curr.runnable == obj) { // We do comparison exactly how Handler does inside + return curr.remove(); + } + curr = curr.next; + } + } finally { + lock.unlock(); + } + return null; + } + } +} \ No newline at end of file diff --git a/BroadCast.md b/BroadCast.md index 47c6791c..a8decd56 100644 --- a/BroadCast.md +++ b/BroadCast.md @@ -6,32 +6,32 @@ private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); //可以通过intent获取到下载实体,下载实体中包含了各种下载状态 - DownloadEntity entity = intent.getParcelableExtra(DownloadManager.ENTITY); + DownloadEntity entity = intent.getParcelableExtra(Aria.ENTITY); switch (action) { - case DownloadManager.ACTION_PRE: //预处理 + case Aria.ACTION_PRE: //预处理 break; - case DownloadManager.ACTION_POST_PRE: //预处理完成 + case Aria.ACTION_POST_PRE: //预处理完成 //预处理完成,便可以获取文件的下载长度了 len = entity.getFileSize(); break; - case DownloadManager.ACTION_START: //开始下载 + case Aria.ACTION_START: //开始下载 L.d(TAG, "download start"); break; - case DownloadManager.ACTION_RESUME: //恢复下载 + case Aria.ACTION_RESUME: //恢复下载 L.d(TAG, "download resume"); long location = intent.getLongExtra(DownloadManager.CURRENT_LOCATION, 1); break; - case DownloadManager.ACTION_RUNNING: //下载中 + case Aria.ACTION_RUNNING: //下载中 long current = intent.getLongExtra(DownloadManager.CURRENT_LOCATION, 0); break; - case DownloadManager.ACTION_STOP: //停止下载 + case Aria.ACTION_STOP: //停止下载 L.d(TAG, "download stop"); break; - case DownloadManager.ACTION_COMPLETE: //下载完成 + case Aria.ACTION_COMPLETE: //下载完成 break; - case DownloadManager.ACTION_CANCEL: //取消下载 + case Aria.ACTION_CANCEL: //取消下载 break; - case DownloadManager.ACTION_FAIL: // 下载失败 + case Aria.ACTION_FAIL: // 下载失败 break; } } @@ -44,15 +44,15 @@ private BroadcastReceiver mReceiver = new BroadcastReceiver() { super.onResume(); IntentFilter filter = new IntentFilter(); filter.addDataScheme(getPackageName()); - filter.addAction(DownloadManager.ACTION_PRE); - filter.addAction(DownloadManager.ACTION_POST_PRE); - filter.addAction(DownloadManager.ACTION_RESUME); - filter.addAction(DownloadManager.ACTION_START); - filter.addAction(DownloadManager.ACTION_RUNNING); - filter.addAction(DownloadManager.ACTION_STOP); - filter.addAction(DownloadManager.ACTION_CANCEL); - filter.addAction(DownloadManager.ACTION_COMPLETE); - filter.addAction(DownloadManager.ACTION_FAIL); + filter.addAction(Aria.ACTION_PRE); + filter.addAction(Aria.ACTION_POST_PRE); + filter.addAction(Aria.ACTION_RESUME); + filter.addAction(Aria.ACTION_START); + filter.addAction(Aria.ACTION_RUNNING); + filter.addAction(Aria.ACTION_STOP); + filter.addAction(Aria.ACTION_CANCEL); + filter.addAction(Aria.ACTION_COMPLETE); + filter.addAction(Aria.ACTION_FAIL); registerReceiver(mReceiver, filter); } -``` \ No newline at end of file +``` diff --git a/README.md b/README.md index f3900f9d..2974c45c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ ## 下载 [![Download](https://api.bintray.com/packages/arialyy/maven/Aria/images/download.svg)](https://bintray.com/arialyy/maven/Aria/_latestVersion)
```java -compile 'com.arialyy.aria:Aria:2.3.2' +compile 'com.arialyy.aria:Aria:2.3.3' ``` @@ -29,12 +29,13 @@ compile 'com.arialyy.aria:Aria:2.3.2' *** ## 使用 -### 一、Aria 是实体驱动型的工具,所以,第一步,你需要创建一个下载实体 +### 一、只需要以下参数,你便能很简单的使用Aria下载文件了 ```java - DownloadEntity mEntity = new DownloadEntity(); - mEntity.setFileName(fileName); //设置文件名 - mEntity.setDownloadUrl(downloadUrl); //设置下载链接 - mEntity.setDownloadPath(downloadPath); //设置存放路径 + Aria.whit(this) + .load(DOWNLOAD_URL) //下载地址,必填 + .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk") //文件保存路径,必填 + .setDownloadName("test.apk") //文件名,必填 + .start(); ``` ### 二、为了能接收到Aria传递的数据,你需要把你的Activity或fragment注册到Aria管理器中,注册的方式很简单,在onResume ```java @@ -43,26 +44,44 @@ compile 'com.arialyy.aria:Aria:2.3.2' Aria.whit(this).addSchedulerListener(new MySchedulerListener()); } ``` -### 三、还记得上面的DownloadEntity吗?现在是时候使用它进行下载了 +### 三、通过下载链接,你还能使用Aria执行很多操作,如: +- 添加任务(不进行下载) + + ```java + Aria.whit(this).load(DOWNLOAD_URL) + .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk") //文件保存路径,必填 + .setDownloadName("test.apk") //文件名,必填 + .add(); + ``` - 启动下载 ```java - Aria.whit(this).load(mEntity).start(); + Aria.whit(this).load(DOWNLOAD_URL).start(); ``` - 暂停下载 ```java - Aria.whit(this).load(mEntity).stop(); + Aria.whit(this).load(DOWNLOAD_URL).stop(); ``` - 恢复下载 ```java - Aria.whit(this).load(mEntity).resume(); + Aria.whit(this).load(DOWNLOAD_URL).resume(); ``` - 取消下载 ```java - Aria.whit(this).load(mEntity).cancel(); + Aria.whit(this).load(DOWNLOAD_URL).cancel(); + ``` +- 获取当前下载进度 + + ```java + Aria.whit(this).load(DOWNLOAD_URL).getCurrentProgress(); + ``` +- 获取文件大小 + + ```java + Aria.whit(this).load(DOWNLOAD_URL).getFileSize(); ``` ### 四、关于Aria,你还需要知道的一些东西 @@ -86,7 +105,7 @@ compile 'com.arialyy.aria:Aria:2.3.2' ```java Aria.get(this).setReTryInterval(5000); ``` -- 设置是否打开广播,如果你需要在Service后台获取下载完成情况,那么你需要打开Aria广播,[Aria广播配置](https://github.com/AriaLyy/Aria/blob/v_2.0/BroadCast.md) +- 设置是否打开广播,如果你需要在Service后台获取下载完成情况,那么你需要打开Aria广播,[Aria广播配置](https://github.com/AriaLyy/Aria/blob/v_2.0/BroadCast.md) ```java Aria.get(this).openBroadcast(true); @@ -94,9 +113,13 @@ compile 'com.arialyy.aria:Aria:2.3.2' *** ## 开发日志 - + v_2.1.0 修复大量bug - + v_2.1.1 增加,选择最大下载任务数接口 + + v_2.3.3 + - 添加断点支持 + - 修改下载逻辑,让使用更加简单 + - 修复一个内存泄露的bug + v_2.3.1 重命名为Aria,下载流程简化 + + v_2.1.1 增加,选择最大下载任务数接口 + + v_2.1.0 修复大量bug License ------- diff --git a/app/src/main/java/com/arialyy/simple/activity/MultiTaskActivity.java b/app/src/main/java/com/arialyy/simple/activity/MultiTaskActivity.java index 4e61682a..5556eafa 100644 --- a/app/src/main/java/com/arialyy/simple/activity/MultiTaskActivity.java +++ b/app/src/main/java/com/arialyy/simple/activity/MultiTaskActivity.java @@ -132,7 +132,7 @@ public class MultiTaskActivity extends BaseActivity { } } - private class MySchedulerListener extends AMTarget.SimpleSchedulerListener{ + private class MySchedulerListener extends Aria.SimpleSchedulerListener{ @Override public void onTaskPre(Task task) { super.onTaskPre(task); L.d(TAG, "download pre"); diff --git a/app/src/main/java/com/arialyy/simple/activity/SingleTaskActivity.java b/app/src/main/java/com/arialyy/simple/activity/SingleTaskActivity.java index e1ba249c..2446f2db 100644 --- a/app/src/main/java/com/arialyy/simple/activity/SingleTaskActivity.java +++ b/app/src/main/java/com/arialyy/simple/activity/SingleTaskActivity.java @@ -19,6 +19,8 @@ package com.arialyy.simple.activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Environment; import android.os.Handler; @@ -26,6 +28,7 @@ import android.os.Message; import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.Button; +import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import butterknife.Bind; @@ -53,6 +56,8 @@ public class SingleTaskActivity extends BaseActivity { public static final int DOWNLOAD_RUNNING = 0x07; private static final String DOWNLOAD_URL = "http://static.gaoshouyou.com/d/3a/93/573ae1db9493a801c24bf66128b11e39.apk"; + //private static final String DOWNLOAD_URL = + // "http://www.yangqiang.im/wp-content/uploads/2016/10/%E6%8A%BD%E8%B1%A1%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F.png"; @Bind(R.id.progressBar) HorizontalProgressBarWithNumber mPb; @Bind(R.id.start) Button mStart; @Bind(R.id.stop) Button mStop; @@ -60,11 +65,12 @@ public class SingleTaskActivity extends BaseActivity { @Bind(R.id.size) TextView mSize; @Bind(R.id.toolbar) Toolbar toolbar; @Bind(R.id.speed) TextView mSpeed; + @Bind(R.id.img) ImageView mImg; private DownloadEntity mEntity; private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(DownloadManager.ACTION_START)) { + if (action.equals(Aria.ACTION_START)) { L.d("START"); } } @@ -76,14 +82,14 @@ public class SingleTaskActivity extends BaseActivity { switch (msg.what) { case DOWNLOAD_RUNNING: Task task = (Task) msg.obj; - long current = task.getDownloadEntity().getCurrentProgress(); - long len = task.getDownloadEntity().getFileSize(); + long current = task.getCurrentProgress(); + long len = task.getFileSize(); if (len == 0) { mPb.setProgress(0); } else { mPb.setProgress((int) ((current * 100) / len)); } - mSpeed.setText(CommonUtil.formatFileSize(task.getDownloadEntity().getSpeed()) + "/s"); + mSpeed.setText(CommonUtil.formatFileSize(task.getSpeed()) + "/s"); break; case DOWNLOAD_PRE: mSize.setText(CommonUtil.formatFileSize((Long) msg.obj)); @@ -117,6 +123,11 @@ public class SingleTaskActivity extends BaseActivity { mStart.setText("重新开始?"); mCancel.setEnabled(false); setBtState(true); + + //String path = Environment.getExternalStorageDirectory().getPath() + "/test.jpg"; + //Bitmap bm = BitmapFactory.decodeFile(path); + //mImg.setImageBitmap(bm); + break; } } @@ -159,21 +170,10 @@ public class SingleTaskActivity extends BaseActivity { } private void init() { - mEntity = Aria.get(this).getDownloadEntity(DOWNLOAD_URL); - if (mEntity != null) { - mPb.setProgress((int) ((mEntity.getCurrentProgress() * 100) / mEntity.getFileSize())); - mSize.setText(CommonUtil.formatFileSize(mEntity.getFileSize())); - if (mEntity.getState() == DownloadEntity.STATE_DOWNLOAD_ING) { - setBtState(false); - } else if (mEntity.isDownloadComplete()) { - mStart.setText("重新开始?"); - setBtState(true); - } - } else { - mEntity = new DownloadEntity(); - mEntity.setFileName("test.apk"); - mEntity.setDownloadUrl(DOWNLOAD_URL); - mEntity.setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk"); + if (Aria.get(this).taskExists(DOWNLOAD_URL)) { + AMTarget target = Aria.whit(this).load(DOWNLOAD_URL); + int p = (int) (target.getCurrentProgress() * 100 / target.getFileSize()); + mPb.setProgress(p); } } @@ -197,22 +197,28 @@ public class SingleTaskActivity extends BaseActivity { } private void resume() { - Aria.whit(this).load(mEntity).resume(); + Aria.whit(this).load(DOWNLOAD_URL).resume(); } private void start() { - Aria.whit(this).load(mEntity).start(); + Aria.whit(this) + .load(DOWNLOAD_URL) + .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk") + .setDownloadName("test.apk") + //.setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.jpg") + //.setDownloadName("test.jpg") + .start(); } private void stop() { - Aria.whit(this).load(mEntity).stop(); + Aria.whit(this).load(DOWNLOAD_URL).stop(); } private void cancel() { - Aria.whit(this).load(mEntity).cancel(); + Aria.whit(this).load(DOWNLOAD_URL).cancel(); } - private class MySchedulerListener extends AMTarget.SimpleSchedulerListener { + private class MySchedulerListener extends Aria.SimpleSchedulerListener { @Override public void onTaskStart(Task task) { mUpdateHandler.obtainMessage(DOWNLOAD_PRE, task.getDownloadEntity().getFileSize()) .sendToTarget(); @@ -220,8 +226,7 @@ public class SingleTaskActivity extends BaseActivity { @Override public void onTaskResume(Task task) { super.onTaskResume(task); - mUpdateHandler.obtainMessage(DOWNLOAD_PRE, task.getDownloadEntity().getFileSize()) - .sendToTarget(); + mUpdateHandler.obtainMessage(DOWNLOAD_PRE, task.getFileSize()).sendToTarget(); } @Override public void onTaskStop(Task task) { diff --git a/app/src/main/java/com/arialyy/simple/adapter/DownloadAdapter.java b/app/src/main/java/com/arialyy/simple/adapter/DownloadAdapter.java index f1138402..02b26c9a 100644 --- a/app/src/main/java/com/arialyy/simple/adapter/DownloadAdapter.java +++ b/app/src/main/java/com/arialyy/simple/adapter/DownloadAdapter.java @@ -19,6 +19,7 @@ package com.arialyy.simple.adapter; import android.content.Context; import android.content.res.Resources; +import android.text.TextUtils; import android.view.View; import android.widget.Button; import android.widget.TextView; diff --git a/app/src/main/java/com/arialyy/simple/module/DownloadModule.java b/app/src/main/java/com/arialyy/simple/module/DownloadModule.java index 9b4d6470..186ddcd4 100644 --- a/app/src/main/java/com/arialyy/simple/module/DownloadModule.java +++ b/app/src/main/java/com/arialyy/simple/module/DownloadModule.java @@ -86,8 +86,8 @@ public class DownloadModule extends BaseModule { } private DownloadEntity createDownloadEntity(String url) { - String fileName = CommonUtil.keyToHashCode(url) + ".apk"; - DownloadEntity entity = new DownloadEntity(); + String fileName = CommonUtil.keyToHashCode(url) + ".apk"; + DownloadEntity entity = new DownloadEntity(); entity.setDownloadUrl(url); entity.setDownloadPath(getDownloadPath(url)); entity.setFileName(fileName); @@ -115,15 +115,15 @@ public class DownloadModule extends BaseModule { public IntentFilter getDownloadFilter() { IntentFilter filter = new IntentFilter(); filter.addDataScheme(getContext().getPackageName()); - filter.addAction(DownloadManager.ACTION_PRE); - filter.addAction(DownloadManager.ACTION_POST_PRE); - filter.addAction(DownloadManager.ACTION_RESUME); - filter.addAction(DownloadManager.ACTION_START); - filter.addAction(DownloadManager.ACTION_RUNNING); - filter.addAction(DownloadManager.ACTION_STOP); - filter.addAction(DownloadManager.ACTION_CANCEL); - filter.addAction(DownloadManager.ACTION_COMPLETE); - filter.addAction(DownloadManager.ACTION_FAIL); + filter.addAction(Aria.ACTION_PRE); + filter.addAction(Aria.ACTION_POST_PRE); + filter.addAction(Aria.ACTION_RESUME); + filter.addAction(Aria.ACTION_START); + filter.addAction(Aria.ACTION_RUNNING); + filter.addAction(Aria.ACTION_STOP); + filter.addAction(Aria.ACTION_CANCEL); + filter.addAction(Aria.ACTION_COMPLETE); + filter.addAction(Aria.ACTION_FAIL); return filter; } @@ -138,36 +138,36 @@ public class DownloadModule extends BaseModule { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action) { - case DownloadManager.ACTION_POST_PRE: - DownloadEntity entity = intent.getParcelableExtra(DownloadManager.ENTITY); + case Aria.ACTION_POST_PRE: + DownloadEntity entity = intent.getParcelableExtra(Aria.ENTITY); len = entity.getFileSize(); L.d(TAG, "download pre"); handler.obtainMessage(SingleTaskActivity.DOWNLOAD_PRE, len).sendToTarget(); break; - case DownloadManager.ACTION_START: + case Aria.ACTION_START: L.d(TAG, "download start"); break; - case DownloadManager.ACTION_RESUME: + case Aria.ACTION_RESUME: L.d(TAG, "download resume"); - long location = intent.getLongExtra(DownloadManager.CURRENT_LOCATION, 1); + long location = intent.getLongExtra(Aria.CURRENT_LOCATION, 1); handler.obtainMessage(SingleTaskActivity.DOWNLOAD_RESUME, location).sendToTarget(); break; - case DownloadManager.ACTION_RUNNING: - long current = intent.getLongExtra(DownloadManager.CURRENT_LOCATION, 0); + case Aria.ACTION_RUNNING: + long current = intent.getLongExtra(Aria.CURRENT_LOCATION, 0); int progress = len == 0 ? 0 : (int) ((current * 100) / len); handler.obtainMessage(SingleTaskActivity.DOWNLOAD_RUNNING, progress).sendToTarget(); break; - case DownloadManager.ACTION_STOP: + case Aria.ACTION_STOP: L.d(TAG, "download stop"); handler.sendEmptyMessage(SingleTaskActivity.DOWNLOAD_STOP); break; - case DownloadManager.ACTION_COMPLETE: + case Aria.ACTION_COMPLETE: handler.sendEmptyMessage(SingleTaskActivity.DOWNLOAD_COMPLETE); break; - case DownloadManager.ACTION_CANCEL: + case Aria.ACTION_CANCEL: handler.sendEmptyMessage(SingleTaskActivity.DOWNLOAD_CANCEL); break; - case DownloadManager.ACTION_FAIL: + case Aria.ACTION_FAIL: handler.sendEmptyMessage(SingleTaskActivity.DOWNLOAD_FAILE); break; } diff --git a/app/src/main/res/layout/content_single.xml b/app/src/main/res/layout/content_single.xml index 8a620180..65d19cfb 100644 --- a/app/src/main/res/layout/content_single.xml +++ b/app/src/main/res/layout/content_single.xml @@ -35,6 +35,7 @@ + +