From 0d3c157f381232a50038f0b5481175d4b6bd4f08 Mon Sep 17 00:00:00 2001 From: AriaLyy <511455842@qq.com> Date: Sat, 31 Dec 2016 14:40:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=AD=E7=82=B9=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=9B=9E=E8=B0=83=EF=BC=8C=E4=BF=AE=E5=A4=8D=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E6=96=AD=E7=82=B9=E6=97=B6=E7=9A=84bug=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=8B=E8=BD=BD=E4=BA=8B=E4=BB=B6=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E7=9A=84=E5=86=85=E5=AD=98=E6=B3=84=E6=BC=8F=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 2 +- .../com/arialyy/aria/core/AMReceiver.java | 11 +- .../java/com/arialyy/aria/core/AMTarget.java | 22 +- .../main/java/com/arialyy/aria/core/Aria.java | 48 ++ .../com/arialyy/aria/core/AriaManager.java | 67 ++- .../arialyy/aria/core/DownloadManager.java | 48 -- .../aria/core/queue/DownloadTaskQueue.java | 42 +- .../arialyy/aria/core/queue/ITaskQueue.java | 20 +- .../core/scheduler/DownloadSchedulers.java | 91 +-- .../aria/core/task/DownloadListener.java | 4 + .../arialyy/aria/core/task/DownloadUtil.java | 541 ++++++++++-------- .../aria/core/task/IDownloadListener.java | 9 +- .../java/com/arialyy/aria/core/task/Task.java | 116 ++-- .../com/arialyy/aria/util/CommonUtil.java | 14 + .../com/arialyy/aria/util/WeakHandler.java | 516 +++++++++++++++++ .../simple/activity/SingleTaskActivity.java | 2 +- .../arialyy/simple/module/DownloadModule.java | 44 +- 17 files changed, 1106 insertions(+), 491 deletions(-) create mode 100644 Aria/src/main/java/com/arialyy/aria/util/WeakHandler.java diff --git a/.idea/misc.xml b/.idea/misc.xml index fbb68289..5d199810 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -37,7 +37,7 @@ - + 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..f73d3b42 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/AMReceiver.java +++ b/Aria/src/main/java/com/arialyy/aria/core/AMReceiver.java @@ -15,6 +15,7 @@ */ package com.arialyy.aria.core; +import com.arialyy.aria.core.scheduler.DownloadSchedulers; import com.arialyy.aria.core.scheduler.OnSchedulerListener; /** @@ -25,7 +26,6 @@ public class AMReceiver { Object obj; OnSchedulerListener listener; DownloadEntity entity; - DownloadManager manager = DownloadManager.getInstance(); public AMTarget load(DownloadEntity entity) { this.entity = entity; @@ -37,7 +37,7 @@ public class AMReceiver { */ public AMReceiver addSchedulerListener(OnSchedulerListener listener) { this.listener = listener; - manager.getTaskQueue().getDownloadSchedulers().addSchedulerListener(obj, listener); + DownloadSchedulers.getInstance().addSchedulerListener(obj, listener); return this; } @@ -46,8 +46,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 46cafed4..6c9b23a4 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/AMTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/AMTarget.java @@ -38,8 +38,9 @@ public class AMTarget { * 添加任务 */ public void add() { - receiver.manager.setCmd( - CommonUtil.createCmd(receiver.obj, receiver.entity, CmdFactory.TASK_CREATE)).exe(); + DownloadManager.getInstance() + .setCmd(CommonUtil.createCmd(receiver.obj, receiver.entity, CmdFactory.TASK_CREATE)) + .exe(); } /** @@ -49,7 +50,7 @@ public class AMTarget { 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(); + DownloadManager.getInstance().setCmds(cmds).exe(); cmds.clear(); } @@ -57,24 +58,27 @@ public class AMTarget { * 停止下载 */ public void stop() { - receiver.manager.setCmd( - CommonUtil.createCmd(receiver.obj, receiver.entity, CmdFactory.TASK_STOP)).exe(); + DownloadManager.getInstance() + .setCmd(CommonUtil.createCmd(receiver.obj, receiver.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(receiver.obj, receiver.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(receiver.obj, receiver.entity, CmdFactory.TASK_CANCEL)) + .exe(); } /** 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 2d2b3aaf..3fce3d74 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/Aria.java +++ b/Aria/src/main/java/com/arialyy/aria/core/Aria.java @@ -43,6 +43,54 @@ import android.os.Build; * */ @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() { } 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 a7c7e095..eb72a04c 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java +++ b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java @@ -45,12 +45,12 @@ import java.util.Set; * Aria管理器,任务操作在这里执行 */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public class AriaManager { - private static final String TAG = "AriaManager"; - private static final Object LOCK = new Object(); - private static volatile AriaManager INSTANCE = null; - private Map mTargets = new HashMap<>(); + private static final String TAG = "AriaManager"; + private static final Object LOCK = new Object(); + private static volatile AriaManager INSTANCE = null; + private Map mTargets = new HashMap<>(); private DownloadManager mManager; - private LifeCallback mLifeCallback; + private LifeCallback mLifeCallback; private AriaManager(Context context) { regAppLifeCallback(context); @@ -93,7 +93,7 @@ import java.util.Set; */ public void stopAllTask() { List allEntity = mManager.getAllDownloadEntity(); - List stopCmds = new ArrayList<>(); + List stopCmds = new ArrayList<>(); for (DownloadEntity entity : allEntity) { if (entity.getState() == DownloadEntity.STATE_DOWNLOAD_ING) { stopCmds.add(CommonUtil.createCmd(entity, CmdFactory.TASK_STOP)); @@ -152,8 +152,8 @@ import java.util.Set; * 删除所有任务 */ public void cancelAllTask() { - List allEntity = mManager.getAllDownloadEntity(); - List cancelCmds = new ArrayList<>(); + List allEntity = mManager.getAllDownloadEntity(); + List cancelCmds = new ArrayList<>(); for (DownloadEntity entity : allEntity) { cancelCmds.add(CommonUtil.createCmd(entity, CmdFactory.TASK_CANCEL)); } @@ -167,26 +167,32 @@ 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; - String key = ""; + String clsName = obj.getClass().getName(); + AMReceiver target = null; + String key = ""; + if (!(obj instanceof Activity)) { if (obj instanceof android.support.v4.app.Fragment) { - key = "_" + ((Fragment) obj).getActivity().getClass().getName(); + key = clsName + "_" + ((Fragment) obj).getActivity().getClass().getName(); } else if (obj instanceof android.app.Fragment) { - key = "_" + ((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 = "_" + activity.getClass().getName(); + key = clsName + "_" + activity.getClass().getName(); } handleDialogDialogLift((Dialog) obj); } - - if (!TextUtils.isEmpty(key)) { - mTargets.put(clsName, target); + } 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); } } return target; @@ -197,12 +203,12 @@ import java.util.Set; */ private void handleDialogDialogLift(Dialog dialog) { try { - Field dismissField = CommonUtil.getField(dialog.getClass(), "mDismissMessage"); - Message dismissMsg = (Message) dismissField.get(dialog); + 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); + 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();来注销事件"); @@ -270,14 +276,15 @@ import java.util.Set; * onDestroy */ private void destroySchedulerListener(Object obj) { - Set keys = mTargets.keySet(); - String clsName = obj.getClass().getName(); + Set keys = mTargets.keySet(); + String clsName = obj.getClass().getName(); for (String key : keys) { 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(); + 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; 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 59bd9de3..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 @@ -35,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() { } @@ -118,9 +118,9 @@ public class DownloadTaskQueue implements ITaskQueue { } } - @Override public IDownloadSchedulers getDownloadSchedulers() { - return mSchedulers; - } + //@Override public IDownloadSchedulers getDownloadSchedulers() { + // return mSchedulers; + //} @Override public int size() { return mExecutePool.size(); @@ -130,7 +130,7 @@ public class DownloadTaskQueue implements ITaskQueue { //原始长度 int size = Configuration.getInstance().getDownloadNum(); int diff = downloadNum - size; - if (size == downloadNum){ + if (size == downloadNum) { Log.d(TAG, "设置的下载任务数和配置文件的下载任务数一直,跳过"); return; } @@ -158,10 +158,14 @@ public class DownloadTaskQueue implements ITaskQueue { @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; @@ -193,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; @@ -205,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/scheduler/DownloadSchedulers.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/DownloadSchedulers.java index 19ca81fa..33416706 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; @@ -36,63 +36,87 @@ public class DownloadSchedulers implements IDownloadSchedulers { /** * 任务预加载 */ - public static final int PRE = 0; + public static final int PRE = 0; /** * 任务开始 */ - public static final int START = 1; + public static final int START = 1; /** * 任务停止 */ - public static final int STOP = 2; + public static final int STOP = 2; /** * 任务失败 */ - public static final int FAIL = 3; + public static final int FAIL = 3; /** * 任务取消 */ - public static final int CANCEL = 4; + public static final int CANCEL = 4; /** * 任务完成 */ - public static final int COMPLETE = 5; + public static final int COMPLETE = 5; /** * 下载中 */ - public static final int RUNNING = 6; + public static final int RUNNING = 6; /** * 恢复下载 */ - public static final int RESUME = 7; - private static final String TAG = "DownloadSchedulers"; - private static final Object LOCK = new Object(); + public static final int RESUME = 7; + private static final String TAG = "DownloadSchedulers"; + 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); @@ -205,26 +230,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"); - } - OnSchedulerListener listener = mSchedulerListeners.get(target.getClass().getName()); - mSchedulerListeners.remove(listener); - listener = null; - } -} \ 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 987d2b1f..481d9b09 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 @@ -29,10 +29,7 @@ import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.ProtocolException; import java.net.URL; -import java.util.List; -import java.util.Map; import java.util.Properties; -import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -40,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(); /** @@ -49,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) { @@ -78,6 +79,25 @@ final class DownloadUtil implements IDownloadUtil { mListener = downloadListener; THREAD_NUM = threadNum; 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() { @@ -117,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(); } @@ -132,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(); } @@ -175,148 +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.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) { - 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.isShutdown()) { - mFixedThreadPool.execute(task); - } - } - } else if (code == HttpURLConnection.HTTP_OK) { - //在conn.setRequestProperty("Range", "bytes=" + 0 + "-");下,200为不支持断点状态 - Log.w(TAG, "该下载链接不支持断点下载"); - } else { - failDownload("下载失败,返回码:" + code); - } - } catch (IOException e) { - failDownload("下载失败【downloadUrl:" - + downloadUrl - + "】\n【filePath:" - + filePath - + "】" - + CommonUtil.getPrintException(e)); - } - } - }).start(); + new Thread(this).start(); } @Override public void resumeDownload() { @@ -339,6 +220,157 @@ 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) { + //在conn.setRequestProperty("Range", "bytes=" + 0 + "-");下,200为不支持断点状态 + 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); + } + } + } + /** * 子线程下载信息类 */ @@ -350,68 +382,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) { @@ -420,7 +446,6 @@ final class DownloadUtil implements IDownloadUtil { //把下载数据数据写入文件 file.write(buffer, 0, len); progress(len); - currentLocation += len; } file.close(); //close 为阻塞的,需要使用线程池来处理 @@ -428,32 +453,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); } } @@ -463,11 +496,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); @@ -479,26 +520,8 @@ 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) { mCurrentLocation += len; @@ -507,13 +530,30 @@ final class DownloadUtil implements IDownloadUtil { } /** - * 将记录写入到配置文件 + * 取消下载 */ - 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(); + } + } } /** @@ -525,19 +565,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..141fd8d0 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,14 +45,15 @@ 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); } @@ -62,7 +65,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,12 +92,13 @@ 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); } } @@ -125,38 +129,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; @@ -204,8 +186,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,10 +196,11 @@ public class Task { /** * 下载监听类 */ - private class DListener extends DownloadListener { - Handler outHandler; - Context context; - Intent sendIntent; + private static class DListener extends DownloadListener { + WeakReference outHandler; + WeakReference task; + Context context; + Intent sendIntent; long INTERVAL = 1024 * 10; //10k大小的间隔 long lastLen = 0; //上一次发送长度 long lastTime = 0; @@ -226,18 +208,19 @@ public class Task { boolean isFirst = true; DownloadEntity downloadEntity; - 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.task = new WeakReference<>(task); + this.downloadEntity = this.task.get().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,14 +228,14 @@ 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) { @@ -260,15 +243,15 @@ public class Task { 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 +271,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 +288,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 +297,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.get()).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/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..060c5647 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/util/WeakHandler.java @@ -0,0 +1,516 @@ +/* + * 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/app/src/main/java/com/arialyy/simple/activity/SingleTaskActivity.java b/app/src/main/java/com/arialyy/simple/activity/SingleTaskActivity.java index e1ba249c..29c6c1dd 100644 --- a/app/src/main/java/com/arialyy/simple/activity/SingleTaskActivity.java +++ b/app/src/main/java/com/arialyy/simple/activity/SingleTaskActivity.java @@ -64,7 +64,7 @@ public class SingleTaskActivity extends BaseActivity { 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"); } } 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; }