添加断点支持回调,修复没有断点时的bug,修复下载事件导致的内存泄漏的bug

pull/2/head
AriaLyy 8 years ago
parent 58afab572c
commit 0d3c157f38
  1. 2
      .idea/misc.xml
  2. 11
      Aria/src/main/java/com/arialyy/aria/core/AMReceiver.java
  3. 22
      Aria/src/main/java/com/arialyy/aria/core/AMTarget.java
  4. 48
      Aria/src/main/java/com/arialyy/aria/core/Aria.java
  5. 35
      Aria/src/main/java/com/arialyy/aria/core/AriaManager.java
  6. 48
      Aria/src/main/java/com/arialyy/aria/core/DownloadManager.java
  7. 40
      Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java
  8. 20
      Aria/src/main/java/com/arialyy/aria/core/queue/ITaskQueue.java
  9. 69
      Aria/src/main/java/com/arialyy/aria/core/scheduler/DownloadSchedulers.java
  10. 4
      Aria/src/main/java/com/arialyy/aria/core/task/DownloadListener.java
  11. 329
      Aria/src/main/java/com/arialyy/aria/core/task/DownloadUtil.java
  12. 9
      Aria/src/main/java/com/arialyy/aria/core/task/IDownloadListener.java
  13. 112
      Aria/src/main/java/com/arialyy/aria/core/task/Task.java
  14. 14
      Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java
  15. 516
      Aria/src/main/java/com/arialyy/aria/util/WeakHandler.java
  16. 2
      app/src/main/java/com/arialyy/simple/activity/SingleTaskActivity.java
  17. 40
      app/src/main/java/com/arialyy/simple/module/DownloadModule.java

@ -37,7 +37,7 @@
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

@ -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;
}
}

@ -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<IDownloadCmd> 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();
}
/**

@ -43,6 +43,54 @@ import android.os.Build;
* </pre>
*/
@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() {
}

@ -168,25 +168,31 @@ 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) {
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;
@ -274,10 +280,11 @@ import java.util.Set;
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;

@ -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;

@ -36,7 +36,7 @@ public class DownloadTaskQueue implements ITaskQueue {
private CachePool mCachePool = CachePool.getInstance();
private ExecutePool mExecutePool = ExecutePool.getInstance();
private Context mContext;
private IDownloadSchedulers mSchedulers;
//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;
}
}

@ -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);
}

@ -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<String, OnSchedulerListener> 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<Map.Entry<String, OnSchedulerListener>> iter =
mSchedulerListeners.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry<String, OnSchedulerListener> 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;
}
}

@ -23,6 +23,10 @@ class DownloadListener implements IDownloadListener {
}
@Override public void supportBreakpoint(boolean support) {
}
@Override public void onCancel() {
}

@ -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();
/**
@ -54,17 +51,21 @@ final class DownloadUtil implements IDownloadUtil {
/**
* 已经完成下载任务的线程数量
*/
private int mCompleteThreadNum = 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<Runnable> 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,29 +195,34 @@ 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();
mFailNum = 0;
mListener.onPre();
new Thread(this).start();
}
} catch (Exception e) {
e.printStackTrace();
failDownload("下载失败,记录文件被删除");
return;
@Override public void resumeDownload() {
startDownload();
}
mListener.onPre();
new Thread(new Runnable() {
private void failDownload(String msg) {
Log.e(TAG, msg);
isDownloading = false;
stopDownload();
mListener.onFail();
}
private void setConnectParam(HttpURLConnection conn) throws ProtocolException {
conn.setRequestMethod("GET");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("User-Agent",
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Accept",
"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(downloadUrl);
URL url = new URL(mDownloadEntity.getDownloadUrl());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
setConnectParam(conn);
conn.setRequestProperty("Range", "bytes=" + 0 + "-");
@ -212,22 +237,61 @@ final class DownloadUtil implements IDownloadUtil {
//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(filePath);
RandomAccessFile file = new RandomAccessFile(filePath, "rwd");
CommonUtil.createFile(mDownloadFile.getPath());
RandomAccessFile file = new RandomAccessFile(mDownloadFile.getPath(), "rwd");
//设置文件长度
file.setLength(fileLength);
mListener.onPostPre(fileLength);
//分配每条线程的下载区间
Properties pro = null;
pro = CommonUtil.loadConfig(configFile);
pro = CommonUtil.loadConfig(mConfigFile);
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 (pro.getProperty(mDownloadFile.getName() + "_record_" + i) == null) {
Object state = pro.getProperty(mDownloadFile.getName() + "_state_" + i);
if (state != null && Integer.parseInt(state + "") == 1) {
continue;
}
@ -244,7 +308,7 @@ final class DownloadUtil implements IDownloadUtil {
}
for (int i = 0; i < THREAD_NUM; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize;
Object state = pro.getProperty(dFile.getName() + "_state_" + i);
Object state = pro.getProperty(mDownloadFile.getName() + "_state_" + i);
if (state != null && Integer.parseInt(state + "") == 1) { //该线程已经完成
mCurrentLocation += endL - startL;
Log.d(TAG, "++++++++++ 线程_" + i + "_已经下载完成 ++++++++++");
@ -252,8 +316,8 @@ final class DownloadUtil implements IDownloadUtil {
mStopNum++;
mCancelNum++;
if (mCompleteThreadNum == THREAD_NUM) {
if (configFile.exists()) {
configFile.delete();
if (mConfigFile.exists()) {
mConfigFile.delete();
}
mListener.onComplete();
isDownloading = false;
@ -262,10 +326,9 @@ final class DownloadUtil implements IDownloadUtil {
continue;
}
//分配下载位置
Object record = pro.getProperty(dFile.getName() + "_record_" + i);
if (!isNewTask
&& record != null
&& Long.parseLong(record + "") > 0) { //如果有记录,则恢复下载
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 + "_恢复下载 ++++++++++");
@ -284,9 +347,14 @@ final class DownloadUtil implements IDownloadUtil {
//如果整个文件的大小不为线程个数的整数倍,则最后一个线程的结束位置即为文件的总长度
endL = fileLength;
}
ConfigEntity entity =
new ConfigEntity(mContext, fileLength, downloadUrl, dFile, i, startL, endL);
DownLoadTask task = new DownLoadTask(entity);
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) {
@ -301,42 +369,6 @@ final class DownloadUtil implements IDownloadUtil {
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();
}
@Override public void resumeDownload() {
startDownload();
}
private void failDownload(String msg) {
Log.e(TAG, msg);
isDownloading = false;
stopDownload();
mListener.onFail();
}
private void setConnectParam(HttpURLConnection conn) throws ProtocolException {
conn.setRequestMethod("GET");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("User-Agent",
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Accept",
"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, */*");
}
/**
@ -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()
private SingleThreadTask(ConfigEntity downloadInfo) {
this.configEntity = downloadInfo;
if (isSupportBreakpoint) {
configFPath = mContext.getFilesDir().getPath()
+ "/temp/"
+ dEntity.tempFile.getName()
+ configEntity.tempFile.getName()
+ ".properties";
}
}
@Override public void run() {
HttpURLConnection conn = null;
InputStream is = null;
try {
URL url = new URL(configEntity.downloadUrl);
conn = (HttpURLConnection) url.openConnection();
if (isSupportBreakpoint) {
Log.d(TAG, "线程_"
+ dEntity.threadId
+ configEntity.threadId
+ "_正在下载【开始位置 : "
+ dEntity.startLocation
+ configEntity.startLocation
+ ",结束位置:"
+ dEntity.endLocation
+ configEntity.endLocation
+ "】");
URL url = new URL(dEntity.downloadUrl);
conn = (HttpURLConnection) url.openConnection();
//在头里面请求下载开始位置和结束位置
conn.setRequestProperty("Range",
"bytes=" + dEntity.startLocation + "-" + dEntity.endLocation);
"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,17 +453,17 @@ 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);
//支持断点的处理
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);
@ -448,12 +473,20 @@ final class DownloadUtil implements IDownloadUtil {
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,57 +496,64 @@ final class DownloadUtil implements IDownloadUtil {
protected void stop() {
synchronized (LOCK) {
try {
if (isSupportBreakpoint) {
mStopNum++;
String location = String.valueOf(currentLocation);
Log.i(TAG, "thread_" + dEntity.threadId + "_stop, stop location ==> " + currentLocation);
writeConfig(dEntity.tempFile.getName() + "_record_" + dEntity.threadId, location);
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);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 下载中
*/
private void progress(long len) {
synchronized (LOCK) {
mCurrentLocation += len;
mListener.onProgress(mCurrentLocation);
}
}
/**
* 取消下载
*/
private void cancel() {
synchronized (LOCK) {
if (isSupportBreakpoint) {
mCancelNum++;
if (mCancelNum == THREAD_NUM) {
File configFile = new File(configFPath);
if (configFile.exists()) {
configFile.delete();
}
if (dEntity.tempFile.exists()) {
dEntity.tempFile.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();
}
}
private void progress(long len) {
synchronized (LOCK) {
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);
}
/**
@ -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 (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();
}
} 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);
}
}
}

@ -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);
/**
* 取消下载
*/

@ -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,8 +196,9 @@ public class Task {
/**
* 下载监听类
*/
private class DListener extends DownloadListener {
Handler outHandler;
private static class DListener extends DownloadListener {
WeakReference<Handler> outHandler;
WeakReference<Task> task;
Context context;
Intent sendIntent;
long INTERVAL = 1024 * 10; //10k大小的间隔
@ -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);

@ -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;
}
/**
* 存储字符串到配置文件
*

@ -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
* <p/>
* 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.
* <p/>
* 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
* <p/>
*
* @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 <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* 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 <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* 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.
* <b>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.</b>
*
* @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 <var>r</var> with Object
* <var>token</var> that are in the message queue. If <var>token</var> 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) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* 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.
* <b>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.</b>
*
* @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 <var>object</var> 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
* <var>obj</var> is <var>token</var>. If <var>token</var> 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<Handler.Callback> mCallback;
ExecHandler() {
mCallback = null;
}
ExecHandler(WeakReference<Handler.Callback> callback) {
mCallback = callback;
}
ExecHandler(Looper looper) {
super(looper);
mCallback = null;
}
ExecHandler(Looper looper, WeakReference<Handler.Callback> 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<Runnable> mDelegate;
private final WeakReference<ChainedRef> mReference;
WeakRunnable(WeakReference<Runnable> delegate, WeakReference<ChainedRef> 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;
}
}
}

@ -64,7 +64,7 @@ public class SingleTaskActivity extends BaseActivity<ActivitySingleBinding> {
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");
}
}

@ -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;
}

Loading…
Cancel
Save