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/.idea/modules.xml b/.idea/modules.xml index b0a774eb..5f6de159 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,7 +3,9 @@ + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddf..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Aria/build.gradle b/Aria/build.gradle index 4aae21af..b6e5869b 100644 --- a/Aria/build.gradle +++ b/Aria/build.gradle @@ -7,8 +7,8 @@ android { defaultConfig { minSdkVersion 9 targetSdkVersion 23 - versionCode 85 - versionName "2.3.7" + versionCode 86 + versionName "2.4.2" } buildTypes { release { 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 f6d3e804..5ae43a1a 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/AMReceiver.java +++ b/Aria/src/main/java/com/arialyy/aria/core/AMReceiver.java @@ -15,9 +15,7 @@ */ package com.arialyy.aria.core; -import android.os.Build; import android.support.annotation.NonNull; -import android.text.TextUtils; import com.arialyy.aria.core.scheduler.DownloadSchedulers; import com.arialyy.aria.core.scheduler.OnSchedulerListener; import com.arialyy.aria.util.CheckUtil; diff --git a/Aria/src/main/java/com/arialyy/aria/core/AMUplodReceiver.java b/Aria/src/main/java/com/arialyy/aria/core/AMUplodReceiver.java new file mode 100644 index 00000000..981c5e6d --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/AMUplodReceiver.java @@ -0,0 +1,8 @@ +package com.arialyy.aria.core; + +/** + * Created by Aria.Lao on 2017/1/18. + * AM 上传文件接收器 + */ +public class AMUplodReceiver { +} 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 4aab85f2..b3c5a6c9 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/Aria.java +++ b/Aria/src/main/java/com/arialyy/aria/core/Aria.java @@ -28,6 +28,7 @@ import android.os.Build; import android.widget.PopupWindow; import com.arialyy.aria.core.scheduler.OnSchedulerListener; import com.arialyy.aria.core.task.Task; +import com.arialyy.aria.util.CheckUtil; /** * Created by lyy on 2016/12/1. @@ -98,12 +99,12 @@ import com.arialyy.aria.core.task.Task; private Aria() { } + /** * 接受Activity、Service、Application */ public static AMReceiver whit(Context context) { - //if (context == null) throw new IllegalArgumentException("context 不能为 null"); - checkNull(context); + CheckUtil.checkNull(context); if (context instanceof Activity || context instanceof Service || context instanceof Application) { @@ -117,7 +118,7 @@ import com.arialyy.aria.core.task.Task; * 处理Fragment */ public static AMReceiver whit(Fragment fragment) { - checkNull(fragment); + CheckUtil.checkNull(fragment); return AriaManager.getInstance( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? fragment.getContext() : fragment.getActivity()).get(fragment); @@ -127,7 +128,7 @@ import com.arialyy.aria.core.task.Task; * 处理Fragment */ public static AMReceiver whit(android.support.v4.app.Fragment fragment) { - checkNull(fragment); + CheckUtil.checkNull(fragment); return AriaManager.getInstance( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? fragment.getContext() : fragment.getActivity()).get(fragment); @@ -137,7 +138,7 @@ import com.arialyy.aria.core.task.Task; * 处理Fragment、或者DialogFragment */ public static AMReceiver whit(DialogFragment dialog) { - checkNull(dialog); + CheckUtil.checkNull(dialog); return AriaManager.getInstance( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? dialog.getContext() : dialog.getActivity()) .get(dialog); @@ -147,7 +148,7 @@ import com.arialyy.aria.core.task.Task; * 处理popupwindow */ public static AMReceiver whit(PopupWindow popupWindow) { - checkNull(popupWindow); + CheckUtil.checkNull(popupWindow); return AriaManager.getInstance(popupWindow.getContentView().getContext()).get(popupWindow); } @@ -155,7 +156,7 @@ import com.arialyy.aria.core.task.Task; * 处理Dialog */ public static AMReceiver whit(Dialog dialog) { - checkNull(dialog); + CheckUtil.checkNull(dialog); return AriaManager.getInstance(dialog.getContext()).get(dialog); } @@ -177,7 +178,7 @@ import com.arialyy.aria.core.task.Task; * 处理Dialog的通用任务 */ public static AriaManager get(Dialog dialog) { - checkNull(dialog); + CheckUtil.checkNull(dialog); return AriaManager.getInstance(dialog.getContext()); } @@ -185,7 +186,7 @@ import com.arialyy.aria.core.task.Task; * 处理Dialog的通用任务 */ public static AriaManager get(PopupWindow popupWindow) { - checkNull(popupWindow); + CheckUtil.checkNull(popupWindow); return AriaManager.getInstance(popupWindow.getContentView().getContext()); } @@ -193,7 +194,7 @@ import com.arialyy.aria.core.task.Task; * 处理Fragment的通用任务 */ public static AriaManager get(Fragment fragment) { - checkNull(fragment); + CheckUtil.checkNull(fragment); return AriaManager.getInstance( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? fragment.getContext() : fragment.getActivity()); @@ -203,16 +204,12 @@ import com.arialyy.aria.core.task.Task; * 处理Fragment的通用任务 */ public static AriaManager get(android.support.v4.app.Fragment fragment) { - checkNull(fragment); + CheckUtil.checkNull(fragment); return AriaManager.getInstance( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? fragment.getContext() : fragment.getActivity()); } - private static void checkNull(Object obj) { - if (obj == null) throw new IllegalArgumentException("不能传入空对象"); - } - public static class SimpleSchedulerListener implements OnSchedulerListener { @Override public void onTaskPre(Task task) { diff --git a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java index b02cdceb..c7062c66 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java +++ b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java @@ -30,6 +30,7 @@ import android.text.TextUtils; import android.util.Log; import android.widget.PopupWindow; import com.arialyy.aria.core.command.CmdFactory; +import com.arialyy.aria.util.CAConfiguration; import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.core.command.IDownloadCmd; @@ -73,6 +74,24 @@ import java.util.Set; return getTarget(obj); } + /** + * 设置CA证书信息 + * + * @param caAlias ca证书别名 + * @param caPath assets 文件夹下的ca证书完整路径 + */ + public void setCAInfo(String caAlias, String caPath) { + if (TextUtils.isEmpty(caAlias)) { + Log.e(TAG, "ca证书别名不能为null"); + return; + } else if (TextUtils.isEmpty(caPath)) { + Log.e(TAG, "ca证书路径不能为null"); + return; + } + CAConfiguration.CA_ALIAS = caAlias; + CAConfiguration.CA_PATH = caPath; + } + /** * 获取下载列表 */ @@ -100,7 +119,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)); @@ -159,8 +178,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)); } @@ -174,9 +193,9 @@ import java.util.Set; } private AMReceiver putTarget(Object obj) { - String clsName = obj.getClass().getName(); - AMReceiver target = null; - 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 = clsName + "_" + ((Fragment) obj).getActivity().getClass().getName(); @@ -198,8 +217,15 @@ import java.util.Set; key = clsName; } handlePopupWindowLift((PopupWindow) obj); + } else if (obj instanceof Service) { + key = clsName; + } else if (obj instanceof Application) { + key = clsName; } - } else { + } + if (obj instanceof Activity || obj instanceof Service) { + key = clsName; + } else if (obj instanceof Application) { key = clsName; } if (TextUtils.isEmpty(key)) { @@ -249,12 +275,12 @@ import java.util.Set; */ private void handleDialogLift(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();来注销事件"); @@ -316,12 +342,12 @@ 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 (Iterator> iter = mTargets.entrySet().iterator(); iter.hasNext(); ) { Map.Entry entry = iter.next(); - String key = entry.getKey(); + String key = entry.getKey(); if (key.equals(clsName) || key.contains(clsName)) { AMReceiver receiver = mTargets.get(key); if (receiver.obj != null) { diff --git a/Aria/src/main/java/com/arialyy/aria/core/RequestEnum.java b/Aria/src/main/java/com/arialyy/aria/core/RequestEnum.java new file mode 100644 index 00000000..43423f82 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/RequestEnum.java @@ -0,0 +1,16 @@ +package com.arialyy.aria.core; + +/** + * Created by Aria.Lao on 2017/1/23. + */ + +public enum RequestEnum { + GET("GET"), POST("POST"); + + String name; + + RequestEnum(String name) { + this.name = name; + } + +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/TaskEntity.java b/Aria/src/main/java/com/arialyy/aria/core/TaskEntity.java new file mode 100644 index 00000000..0cc6be80 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/TaskEntity.java @@ -0,0 +1,10 @@ +package com.arialyy.aria.core; + +/** + * Created by Aria.Lao on 2017/1/23. + * 任务实体 + */ +public class TaskEntity { + public DownloadEntity downloadEntity; + public RequestEnum requestEnum = RequestEnum.GET; +} 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 06228179..de033a65 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 @@ -33,9 +33,9 @@ import com.arialyy.aria.util.Configuration; * 下载任务队列 */ public class DownloadTaskQueue implements ITaskQueue { - private static final String TAG = "DownloadTaskQueue"; - private CachePool mCachePool = CachePool.getInstance(); - private ExecutePool mExecutePool = ExecutePool.getInstance(); + private static final String TAG = "DownloadTaskQueue"; + private CachePool mCachePool = CachePool.getInstance(); + private ExecutePool mExecutePool = ExecutePool.getInstance(); private Context mContext; //private IDownloadSchedulers mSchedulers; @@ -89,21 +89,15 @@ public class DownloadTaskQueue implements ITaskQueue { @Override public void stopTask(Task task) { if (!task.isDownloading()) Log.w(TAG, "停止任务失败,【任务已经停止】"); - task.stop(); - //if (task.isDownloading()) { - // if (mExecutePool.removeTask(task)) { - // task.stop(); - // } - //} else { - // task.stop(); - // Log.w(TAG, "停止任务失败,【任务已经停止】"); - //} + if (mExecutePool.removeTask(task)) { + task.stop(); + } else { + task.stop(); + Log.w(TAG, "停止任务失败,【任务已经停止】"); + } } @Override public void cancelTask(Task task) { - //if (mExecutePool.removeTask(task) || mCachePool.removeTask(task)) { - // task.cancel(); - //} task.cancel(); } @@ -119,10 +113,6 @@ public class DownloadTaskQueue implements ITaskQueue { } } - //@Override public IDownloadSchedulers getDownloadSchedulers() { - // return mSchedulers; - //} - @Override public int size() { return mExecutePool.size(); } @@ -159,12 +149,10 @@ public class DownloadTaskQueue implements ITaskQueue { @Override public Task createTask(String target, DownloadEntity entity) { Task task; if (TextUtils.isEmpty(target)) { - //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, mContext, entity, DownloadSchedulers.getInstance()); } mCachePool.putTask(task); @@ -183,13 +171,10 @@ public class DownloadTaskQueue implements ITaskQueue { Task task = mExecutePool.getTask(entity.getDownloadUrl()); if (task != null) { Log.d(TAG, "从执行池删除任务,删除" + (mExecutePool.removeTask(task) ? "成功" : "失败")); - } else { - task = mCachePool.getTask(entity.getDownloadUrl()); } + task = mCachePool.getTask(entity.getDownloadUrl()); if (task != null) { Log.d(TAG, "从缓存池删除任务,删除" + (mCachePool.removeTask(task) ? "成功" : "失败")); - } else { - Log.w(TAG, "没有找到下载链接为【" + entity.getDownloadUrl() + "】的任务"); } } @@ -197,29 +182,15 @@ public class DownloadTaskQueue implements ITaskQueue { return mCachePool.pollTask(); } - //@Override public void setScheduler(IDownloadSchedulers schedulers) { - // mSchedulers = schedulers; - //} - public static class Builder { Context context; - IDownloadSchedulers schedulers; public Builder(Context context) { this.context = context.getApplicationContext(); } - //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.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 73163766..68576119 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 @@ -73,11 +73,4 @@ public interface ITaskQueue extends IDownloader { * @return 下载任务 or null */ public Task getNextTask(); - - ///** - // * 设置下载调度器 - // * - // * @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 5f5b8a4e..0e062e03 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 @@ -16,6 +16,7 @@ package com.arialyy.aria.core.scheduler; +import android.os.CountDownTimer; import android.os.Message; import android.text.TextUtils; import android.util.Log; @@ -37,44 +38,44 @@ 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; /** * 下载器任务监听 */ Map mSchedulerListeners = new ConcurrentHashMap<>(); - DownloadManager mManager = DownloadManager.getInstance(); + DownloadManager mManager = DownloadManager.getInstance(); ITaskQueue mQueue; private DownloadSchedulers() { @@ -120,16 +121,16 @@ public class DownloadSchedulers implements IDownloadSchedulers { case STOP: case CANCEL: mQueue.removeTask(entity); - mQueue.removeTask(entity); - if (mQueue.size() != Configuration.getInstance().getDownloadNum()) { + if (mQueue.size() < Configuration.getInstance().getDownloadNum()) { startNextTask(entity); } break; case COMPLETE: + mQueue.removeTask(entity); startNextTask(entity); break; case FAIL: - handleFailTask(entity); + handleFailTask(task); break; } return true; @@ -154,10 +155,8 @@ public class DownloadSchedulers implements IDownloadSchedulers { private void callback(int state, Task task, OnSchedulerListener listener) { if (listener != null) { - //Task task = mQueue.getTask(entity); if (task == null) { - //Log.e(TAG, "队列中没有下载链接【" + entity.getDownloadUrl() + "】的任务"); - Log.e(TAG, "传递的下载任务"); + Log.e(TAG, "TASK 为null,回调失败"); return; } switch (state) { @@ -192,12 +191,17 @@ public class DownloadSchedulers implements IDownloadSchedulers { /** * 处理下载任务下载失败的情形 * - * @param entity 失败实体 + * @param task 下载任务 */ - @Override public void handleFailTask(final DownloadEntity entity) { - new Thread(new Runnable() { - @Override public void run() { - final Configuration config = Configuration.getInstance(); + @Override public void handleFailTask(final Task task) { + final Configuration config = Configuration.getInstance(); + CountDownTimer timer = new CountDownTimer(config.getReTryInterval(), 1000) { + @Override public void onTick(long millisUntilFinished) { + + } + + @Override public void onFinish() { + DownloadEntity entity = task.getDownloadEntity(); if (entity.getFailNum() <= config.getReTryNum()) { Task task = mQueue.getTask(entity); mQueue.reTryStart(task); @@ -207,10 +211,12 @@ public class DownloadSchedulers implements IDownloadSchedulers { e.printStackTrace(); } } else { + mQueue.removeTask(entity); startNextTask(entity); } } - }).start(); + }; + timer.start(); } /** @@ -219,7 +225,6 @@ public class DownloadSchedulers implements IDownloadSchedulers { * @param entity 通过Handler传递的下载实体 */ @Override public void startNextTask(DownloadEntity entity) { - mQueue.removeTask(entity); Task newTask = mQueue.getNextTask(); if (newTask == null) { Log.w(TAG, "没有下一任务"); diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/IDownloadSchedulers.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/IDownloadSchedulers.java index 5b38ceb5..3e75061f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/IDownloadSchedulers.java +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/IDownloadSchedulers.java @@ -18,6 +18,7 @@ package com.arialyy.aria.core.scheduler; import android.os.Handler; import com.arialyy.aria.core.DownloadEntity; +import com.arialyy.aria.core.task.Task; /** * Created by “AriaLyy@outlook.com” on 2016/11/2. @@ -42,9 +43,9 @@ public interface IDownloadSchedulers extends Handler.Callback { /** * 处理下载任务下载失败的情形 * - * @param entity 下载实体 + * @param task 下载任务 */ - public void handleFailTask(DownloadEntity entity); + public void handleFailTask(Task task); /** * 启动下一个任务,条件:任务停止,取消下载,任务完成 diff --git a/Aria/src/main/java/com/arialyy/aria/core/task/ConnectionHelp.java b/Aria/src/main/java/com/arialyy/aria/core/task/ConnectionHelp.java new file mode 100644 index 00000000..38ee2867 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/task/ConnectionHelp.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.task; + +import com.arialyy.aria.util.CAConfiguration; +import com.arialyy.aria.util.SSLContextUtil; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.ProtocolException; +import java.net.URL; +import java.net.URLConnection; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; + +/** + * Created by lyy on 2017/1/18. + * 链接帮助类 + */ +class ConnectionHelp { + /** + * 处理链接 + * + * @throws IOException + */ + static HttpURLConnection handleConnection(URL url) throws IOException { + HttpURLConnection conn; + URLConnection urlConn = url.openConnection(); + if (urlConn instanceof HttpsURLConnection) { + conn = (HttpsURLConnection) urlConn; + SSLContext sslContext = + SSLContextUtil.getSSLContext(CAConfiguration.CA_ALIAS, CAConfiguration.CA_ALIAS); + if (sslContext == null) { + sslContext = SSLContextUtil.getDefaultSLLContext(); + } + SSLSocketFactory ssf = sslContext.getSocketFactory(); + ((HttpsURLConnection) conn).setSSLSocketFactory(ssf); + ((HttpsURLConnection) conn).setHostnameVerifier(SSLContextUtil.HOSTNAME_VERIFIER); + } else { + conn = (HttpURLConnection) urlConn; + } + return conn; + } + + /** + * 设置头部参数 + * + * @throws ProtocolException + */ + static HttpURLConnection 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, */*"); + ////用于处理Disconnect 不起作用问题 + //conn.setRequestProperty("Connection", "close"); + conn.setRequestProperty("Connection", "Keep-Alive"); + return conn; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/task/DownloadStateConstance.java b/Aria/src/main/java/com/arialyy/aria/core/task/DownloadStateConstance.java new file mode 100644 index 00000000..00da093b --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/task/DownloadStateConstance.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.task; + +/** + * Created by lyy on 2017/1/18. + * 下载状态常量 + */ +final class DownloadStateConstance { + int CANCEL_NUM = 0; + int STOP_NUM = 0; + int FAIL_NUM = 0; + int CONNECT_TIME_OUT = 5000 * 4; //连接超时时间 + int READ_TIME_OUT = 1000 * 20; //流读取的超时时间 + int COMPLETE_THREAD_NUM = 0; + int THREAD_NUM = 3; + long CURRENT_LOCATION = 0; + boolean isDownloading = false; + boolean isCancel = false; + boolean isStop = false; + + DownloadStateConstance(int num) { + THREAD_NUM = num; + } + + void cleanState() { + isCancel = false; + isStop = false; + isDownloading = true; + CURRENT_LOCATION = 0; + CANCEL_NUM = 0; + STOP_NUM = 0; + FAIL_NUM = 0; + } + + /** + * 所有子线程是否都已经停止下载 + */ + boolean isStop() { + return STOP_NUM == THREAD_NUM; + } + + /** + * 所有子线程是否都已经下载失败 + */ + boolean isFail() { + return FAIL_NUM == THREAD_NUM; + } + + /** + * 所有子线程是否都已经完成下载 + */ + boolean isComplete() { + return COMPLETE_THREAD_NUM == THREAD_NUM; + } + + /** + * 所有子线程是否都已经取消下载 + */ + boolean isCancel() { + return CANCEL_NUM == THREAD_NUM; + } +} 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 c69ee94a..6f14d883 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 @@ -20,14 +20,12 @@ import android.content.Context; import android.util.Log; import android.util.SparseArray; import com.arialyy.aria.core.DownloadEntity; +import com.arialyy.aria.util.BufferedRandomAccessFile; import com.arialyy.aria.util.CommonUtil; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.ProtocolException; import java.net.URL; import java.util.Properties; import java.util.concurrent.ExecutorService; @@ -37,9 +35,8 @@ import java.util.concurrent.Executors; * Created by lyy on 2015/8/25. * 下载工具类 */ -final class DownloadUtil implements IDownloadUtil, Runnable { - private static final String TAG = "DownloadUtil"; - private static final Object LOCK = new Object(); +public class DownloadUtil implements IDownloadUtil, Runnable { + private static final String TAG = "DownloadUtil"; /** * 线程数 */ @@ -48,27 +45,17 @@ final class DownloadUtil implements IDownloadUtil, Runnable { private IDownloadListener mListener; private int mConnectTimeOut = 5000 * 4; //连接超时时间 private int mReadTimeOut = 5000 * 20; //流读取的超时时间 - /** - * 已经完成下载任务的线程数量 - */ - 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<>(); + private DownloadStateConstance mConstance; - DownloadUtil(Context context, DownloadEntity entity, IDownloadListener downloadListener) { + public DownloadUtil(Context context, DownloadEntity entity, IDownloadListener downloadListener) { this(context, entity, downloadListener, 3); } @@ -79,6 +66,7 @@ final class DownloadUtil implements IDownloadUtil, Runnable { mListener = downloadListener; THREAD_NUM = threadNum; mFixedThreadPool = Executors.newFixedThreadPool(Integer.MAX_VALUE); + mConstance = new DownloadStateConstance(THREAD_NUM); init(); } @@ -122,19 +110,19 @@ final class DownloadUtil implements IDownloadUtil, Runnable { * 获取当前下载位置 */ @Override public long getCurrentLocation() { - return mCurrentLocation; + return mConstance.CURRENT_LOCATION; } @Override public boolean isDownloading() { - return isDownloading; + return mConstance.isDownloading; } /** * 取消下载 */ @Override public void cancelDownload() { - isCancel = true; - isDownloading = false; + mConstance.isCancel = true; + mConstance.isDownloading = false; mFixedThreadPool.shutdown(); for (int i = 0; i < THREAD_NUM; i++) { SingleThreadTask task = (SingleThreadTask) mTask.get(i); @@ -148,8 +136,8 @@ final class DownloadUtil implements IDownloadUtil, Runnable { * 停止下载 */ @Override public void stopDownload() { - isStop = true; - isDownloading = false; + mConstance.isStop = true; + mConstance.isDownloading = false; mFixedThreadPool.shutdown(); for (int i = 0; i < THREAD_NUM; i++) { SingleThreadTask task = (SingleThreadTask) mTask.get(i); @@ -189,13 +177,7 @@ final class DownloadUtil implements IDownloadUtil, Runnable { * 多线程断点续传下载文件,开始下载 */ @Override public void startDownload() { - isDownloading = true; - mCurrentLocation = 0; - isStop = false; - isCancel = false; - mCancelNum = 0; - mStopNum = 0; - mFailNum = 0; + mConstance.cleanState(); mListener.onPre(); new Thread(this).start(); } @@ -206,25 +188,16 @@ final class DownloadUtil implements IDownloadUtil, Runnable { private void failDownload(String msg) { Log.e(TAG, msg); - isDownloading = false; + mConstance.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(mDownloadEntity.getDownloadUrl()); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - setConnectParam(conn); + URL url = new URL(mDownloadEntity.getDownloadUrl()); + HttpURLConnection conn = ConnectionHelp.handleConnection(url); + conn = ConnectionHelp.setConnectParam(conn); conn.setRequestProperty("Range", "bytes=" + 0 + "-"); conn.setConnectTimeout(mConnectTimeOut * 4); conn.connect(); @@ -242,12 +215,16 @@ final class DownloadUtil implements IDownloadUtil, Runnable { handleBreakpoint(conn); } else if (code == HttpURLConnection.HTTP_OK || len < 0) { //在conn.setRequestProperty("Range", "bytes=" + 0 + "-");下,200为不支持断点状态 + if (len < 0) { + failDownload("任务【" + mDownloadEntity.getDownloadUrl() + "】下载失败,文件长度小于0"); + return; + } isSupportBreakpoint = false; mListener.supportBreakpoint(false); Log.w(TAG, "该下载链接不支持断点下载"); handleBreakpoint(conn); } else { - failDownload("下载失败,返回码:" + code); + failDownload("任务【" + mDownloadEntity.getDownloadUrl() + "】下载失败,返回码:" + code); } } catch (IOException e) { failDownload("下载失败【downloadUrl:" @@ -263,16 +240,19 @@ final class DownloadUtil implements IDownloadUtil, Runnable { * 处理断点 */ 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); + entity.FILE_SIZE = conn.getContentLength(); + entity.DOWNLOAD_URL = mDownloadEntity.getDownloadUrl(); + entity.TEMP_FILE = mDownloadFile; + entity.THREAD_ID = 0; + entity.START_LOCATION = 0; + entity.END_LOCATION = entity.FILE_SIZE; + entity.CONFIG_FILE_PATH = mConfigFile.getPath(); + entity.isSupportBreakpoint = isSupportBreakpoint; + SingleThreadTask task = new SingleThreadTask(mConstance, mListener, entity); mFixedThreadPool.execute(task); mListener.onStart(0); return; @@ -280,7 +260,10 @@ final class DownloadUtil implements IDownloadUtil, Runnable { int fileLength = conn.getContentLength(); //必须建一个文件 CommonUtil.createFile(mDownloadFile.getPath()); - RandomAccessFile file = new RandomAccessFile(mDownloadFile.getPath(), "rwd"); + //RandomAccessFile file = new RandomAccessFile(mDownloadFile.getPath(), "rwd"); + ////设置文件长度 + //file.setLength(fileLength); + BufferedRandomAccessFile file = new BufferedRandomAccessFile(mDownloadFile.getPath(), "rwd", 8192); //设置文件长度 file.setLength(fileLength); mListener.onPostPre(fileLength); @@ -301,27 +284,27 @@ final class DownloadUtil implements IDownloadUtil, Runnable { } } } - int blockSize = fileLength / THREAD_NUM; - int[] recordL = new int[THREAD_NUM]; - int rl = 0; + 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); + 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; + mConstance.CURRENT_LOCATION += endL - startL; Log.d(TAG, "++++++++++ 线程_" + i + "_已经下载完成 ++++++++++"); - mCompleteThreadNum++; - mStopNum++; - mCancelNum++; - if (mCompleteThreadNum == THREAD_NUM) { + mConstance.COMPLETE_THREAD_NUM++; + mConstance.STOP_NUM++; + mConstance.CANCEL_NUM++; + if (mConstance.isComplete()) { if (mConfigFile.exists()) { mConfigFile.delete(); } mListener.onComplete(); - isDownloading = false; + mConstance.isDownloading = false; return; } continue; @@ -331,7 +314,7 @@ final class DownloadUtil implements IDownloadUtil, Runnable { //如果有记录,则恢复下载 if (!isNewTask && record != null && Long.parseLong(record + "") > 0) { Long r = Long.parseLong(record + ""); - mCurrentLocation += r - startL; + mConstance.CURRENT_LOCATION += r - startL; Log.d(TAG, "++++++++++ 线程_" + i + "_恢复下载 ++++++++++"); mListener.onChildResume(r); startL = r; @@ -349,19 +332,21 @@ final class DownloadUtil implements IDownloadUtil, Runnable { 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); + entity.FILE_SIZE = fileLength; + entity.DOWNLOAD_URL = mDownloadEntity.getDownloadUrl(); + entity.TEMP_FILE = mDownloadFile; + entity.THREAD_ID = i; + entity.START_LOCATION = startL; + entity.END_LOCATION = endL; + entity.CONFIG_FILE_PATH = mConfigFile.getPath(); + entity.isSupportBreakpoint = isSupportBreakpoint; + SingleThreadTask task = new SingleThreadTask(mConstance, mListener, entity); mTask.put(i, task); } - if (mCurrentLocation > 0) { - mListener.onResume(mCurrentLocation); + if (mConstance.CURRENT_LOCATION > 0) { + mListener.onResume(mConstance.CURRENT_LOCATION); } else { - mListener.onStart(mCurrentLocation); + mListener.onStart(mConstance.CURRENT_LOCATION); } for (int l : recordL) { if (l == -1) continue; @@ -375,228 +360,15 @@ final class DownloadUtil implements IDownloadUtil, Runnable { /** * 子线程下载信息类 */ - private static class ConfigEntity { + final static class ConfigEntity { //文件大小 - long fileSize; - String downloadUrl; - int threadId; - long startLocation; - long endLocation; - File tempFile; - } - - /** - * 单个线程的下载任务 - */ - private class SingleThreadTask implements Runnable { - private static final String TAG = "SingleThreadTask"; - private ConfigEntity configEntity; - private String configFPath; - private long currentLocation = 0; - - 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 { - URL url = new URL(configEntity.downloadUrl); - conn = (HttpURLConnection) url.openConnection(); - 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(configEntity.tempFile, "rwd"); - //设置每条线程写入文件的位置 - file.seek(configEntity.startLocation); - byte[] buffer = new byte[1024]; - int len; - //当前子线程的下载位置 - currentLocation = configEntity.startLocation; - while ((len = is.read(buffer)) != -1) { - if (isCancel) { - Log.d(TAG, "++++++++++ thread_" + configEntity.threadId + "_cancel ++++++++++"); - break; - } - if (isStop) { - break; - } - //把下载数据数据写入文件 - file.write(buffer, 0, len); - progress(len); - } - file.close(); - //close 为阻塞的,需要使用线程池来处理 - is.close(); - conn.disconnect(); - - if (isCancel) { - return; - } - //停止状态不需要删除记录文件 - if (isStop) { - return; - } - //支持断点的处理 - 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) { - mFailNum++; - failDownload(configEntity, currentLocation, "下载链接异常", e); - } catch (IOException e) { - mFailNum++; - failDownload(configEntity, currentLocation, "下载失败【" + configEntity.downloadUrl + "】", e); - } catch (Exception e) { - mFailNum++; - failDownload(configEntity, currentLocation, "获取流失败", e); - } - } - - /** - * 停止下载 - */ - protected void stop() { - synchronized (LOCK) { - try { - 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); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - /** - * 下载中 - */ - private void progress(long len) { - synchronized (LOCK) { - currentLocation += len; - 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 (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 failDownload(ConfigEntity dEntity, long currentLocation, String msg, - Exception ex) { - synchronized (LOCK) { - try { - isDownloading = false; - isStop = true; - 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); - } + long FILE_SIZE; + String DOWNLOAD_URL; + int THREAD_ID; + long START_LOCATION; + long END_LOCATION; + File TEMP_FILE; + boolean isSupportBreakpoint = true; + String CONFIG_FILE_PATH; } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/task/SingleThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/task/SingleThreadTask.java new file mode 100644 index 00000000..d09e1ed7 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/task/SingleThreadTask.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.task; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import com.arialyy.aria.util.BufferedRandomAccessFile; +import com.arialyy.aria.util.CommonUtil; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Properties; + +/** + * Created by lyy on 2017/1/18. + * 下载线程 + */ +final class SingleThreadTask implements Runnable { + private static final String TAG = "SingleThreadTask"; + // TODO: 2017/2/22 不能使用1024 否则最大速度不能超过3m + private static final int BUF_SIZE = 8192; + private DownloadUtil.ConfigEntity mConfigEntity; + private String mConfigFPath; + private long mChildCurrentLocation = 0; + private static final Object LOCK = new Object(); + private IDownloadListener mListener; + private DownloadStateConstance mConstance; + + SingleThreadTask(DownloadStateConstance constance, IDownloadListener listener, + DownloadUtil.ConfigEntity downloadInfo) { + mConstance = constance; + mListener = listener; + this.mConfigEntity = downloadInfo; + if (mConfigEntity.isSupportBreakpoint) { + mConfigFPath = downloadInfo.CONFIG_FILE_PATH; + } + } + + @Override public void run() { + HttpURLConnection conn = null; + InputStream is = null; + try { + URL url = new URL(mConfigEntity.DOWNLOAD_URL); + //conn = (HttpURLConnection) url.openConnection(); + conn = ConnectionHelp.handleConnection(url); + if (mConfigEntity.isSupportBreakpoint) { + Log.d(TAG, "线程_" + + mConfigEntity.THREAD_ID + + "_正在下载【开始位置 : " + + mConfigEntity.START_LOCATION + + ",结束位置:" + + mConfigEntity.END_LOCATION + + "】"); + //在头里面请求下载开始位置和结束位置 + conn.setRequestProperty("Range", + "bytes=" + mConfigEntity.START_LOCATION + "-" + mConfigEntity.END_LOCATION); + } else { + Log.w(TAG, "该下载不支持断点,即将重新下载"); + } + conn = ConnectionHelp.setConnectParam(conn); + conn.setConnectTimeout(mConstance.CONNECT_TIME_OUT); + conn.setReadTimeout(mConstance.READ_TIME_OUT); //设置读取流的等待时间,必须设置该参数 + is = conn.getInputStream(); + //创建可设置位置的文件 + BufferedRandomAccessFile file = + new BufferedRandomAccessFile(mConfigEntity.TEMP_FILE, "rwd", BUF_SIZE); + //设置文件长度 + file.seek(mConfigEntity.START_LOCATION); + + byte[] buffer = new byte[BUF_SIZE]; + int len; + //当前子线程的下载位置 + mChildCurrentLocation = mConfigEntity.START_LOCATION; + while ((len = is.read(buffer)) != -1) { + if (mConstance.isCancel) { + break; + } + if (mConstance.isStop) { + Log.i(TAG, "stop"); + break; + } + //把下载数据数据写入文件 + file.write(buffer, 0, len); + progress(len); + } + file.close(); + //close 为阻塞的,需要使用线程池来处理 + is.close(); + conn.disconnect(); + if (mConstance.isCancel) { + return; + } + //停止状态不需要删除记录文件 + if (mConstance.isStop) { + return; + } + //支持断点的处理 + if (mConfigEntity.isSupportBreakpoint) { + Log.i(TAG, "线程【" + mConfigEntity.THREAD_ID + "】下载完毕"); + writeConfig(mConfigEntity.TEMP_FILE.getName() + "_state_" + mConfigEntity.THREAD_ID, + 1 + ""); + mListener.onChildComplete(mConfigEntity.END_LOCATION); + mConstance.COMPLETE_THREAD_NUM++; + if (mConstance.isComplete()) { + File configFile = new File(mConfigFPath); + if (configFile.exists()) { + configFile.delete(); + } + mConstance.isDownloading = false; + mListener.onComplete(); + } + } else { + Log.i(TAG, "下载任务完成"); + mConstance.isDownloading = false; + mListener.onComplete(); + } + } catch (MalformedURLException e) { + mConstance.FAIL_NUM++; + failDownload(mConfigEntity, mChildCurrentLocation, "下载链接异常", e); + } catch (IOException e) { + mConstance.FAIL_NUM++; + failDownload(mConfigEntity, mChildCurrentLocation, "下载失败【" + mConfigEntity.DOWNLOAD_URL + "】", + e); + } catch (Exception e) { + mConstance.FAIL_NUM++; + failDownload(mConfigEntity, mChildCurrentLocation, "获取流失败", e); + } + } + + /** + * 停止下载 + */ + protected void stop() { + synchronized (LOCK) { + try { + if (mConfigEntity.isSupportBreakpoint) { + mConstance.STOP_NUM++; + String location = String.valueOf(mChildCurrentLocation); + Log.d(TAG, "thread_" + + mConfigEntity.THREAD_ID + + "_stop, stop location ==> " + + mChildCurrentLocation); + writeConfig(mConfigEntity.TEMP_FILE.getName() + "_record_" + mConfigEntity.THREAD_ID, + location); + if (mConstance.isStop()) { + Log.d(TAG, "++++++++++++++++ onStop +++++++++++++++++"); + mConstance.isDownloading = false; + mListener.onStop(mConstance.CURRENT_LOCATION); + } + } else { + Log.d(TAG, "++++++++++++++++ onStop +++++++++++++++++"); + mConstance.isDownloading = false; + mListener.onStop(mConstance.CURRENT_LOCATION); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 下载中 + */ + private void progress(long len) { + synchronized (LOCK) { + mChildCurrentLocation += len; + mConstance.CURRENT_LOCATION += len; + mListener.onProgress(mConstance.CURRENT_LOCATION); + //mHandler.sendEmptyMessage(1); + //mHandler.post(t); + } + } + + Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override public void handleMessage(Message msg) { + super.handleMessage(msg); + mListener.onProgress(mConstance.CURRENT_LOCATION); + } + }; + + Thread t = new Thread(new Runnable() { + @Override public void run() { + mListener.onProgress(mConstance.CURRENT_LOCATION); + } + }); + + //Handler handler = new Handler(){ + // @Override public void handleMessage(Message msg) { + // super.handleMessage(msg); + // mListener.onProgress(mConstance.CURRENT_LOCATION); + // } + //}; + + Thread thread = new Thread(); + + /** + * 取消下载 + */ + protected void cancel() { + synchronized (LOCK) { + if (mConfigEntity.isSupportBreakpoint) { + mConstance.CANCEL_NUM++; + Log.d(TAG, "++++++++++ thread_" + mConfigEntity.THREAD_ID + "_cancel ++++++++++"); + if (mConstance.isCancel()) { + File configFile = new File(mConfigFPath); + if (configFile.exists()) { + configFile.delete(); + } + if (mConfigEntity.TEMP_FILE.exists()) { + mConfigEntity.TEMP_FILE.delete(); + } + Log.d(TAG, "++++++++++++++++ onCancel +++++++++++++++++"); + mConstance.isDownloading = false; + mListener.onCancel(); + } + } else { + Log.d(TAG, "++++++++++++++++ onCancel +++++++++++++++++"); + mConstance.isDownloading = false; + mListener.onCancel(); + } + } + } + + /** + * 下载失败 + */ + private void failDownload(DownloadUtil.ConfigEntity dEntity, long currentLocation, String msg, + Exception ex) { + synchronized (LOCK) { + try { + mConstance.isDownloading = false; + mConstance.isStop = true; + if (ex != null) { + Log.e(TAG, CommonUtil.getPrintException(ex)); + } + if (mConfigEntity.isSupportBreakpoint) { + if (currentLocation != -1) { + String location = String.valueOf(currentLocation); + writeConfig(dEntity.TEMP_FILE.getName() + "_record_" + dEntity.THREAD_ID, location); + } + if (mConstance.isFail()) { + 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(mConfigFPath); + Properties pro = CommonUtil.loadConfig(configFile); + pro.setProperty(key, record); + CommonUtil.saveConfig(configFile, pro); + } +} 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 c66b5f67..016ab8ef 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 @@ -22,6 +22,7 @@ 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.TaskEntity; import com.arialyy.aria.core.scheduler.DownloadSchedulers; import com.arialyy.aria.core.scheduler.IDownloadSchedulers; import com.arialyy.aria.core.DownloadEntity; @@ -196,20 +197,9 @@ public class Task { return this; } - ///** - // * 设置自定义下载工具 - // * - // * @param downloadUtil {@link IDownloadUtil} - // */ - //public Builder setDownloadUtil(IDownloadUtil downloadUtil) { - // this.downloadUtil = downloadUtil; - // return this; - //} - public Task build() { Task task = new Task(context, downloadEntity, outHandler); task.setTargetName(targetName); - //Log.w(TAG, "downloadEntity hashcode ==> " + downloadEntity.hashCode()); downloadEntity.save(); return task; } diff --git a/Aria/src/main/java/com/arialyy/aria/exception/FileException.java b/Aria/src/main/java/com/arialyy/aria/exception/FileException.java new file mode 100644 index 00000000..f4fbb700 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/exception/FileException.java @@ -0,0 +1,13 @@ +package com.arialyy.aria.exception; + +/** + * Created by Aria.Lao on 2017/1/18. + * Aria 文件异常 + */ +public class FileException extends NullPointerException { + private static final String ARIA_FILE_EXCEPTION = "Aria Exception:"; + + public FileException(String detailMessage) { + super(ARIA_FILE_EXCEPTION + detailMessage); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DbUtil.java b/Aria/src/main/java/com/arialyy/aria/orm/DbUtil.java index 4f6fe2b1..10b64600 100644 --- a/Aria/src/main/java/com/arialyy/aria/orm/DbUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/orm/DbUtil.java @@ -113,7 +113,6 @@ public class DbUtil { params[i] = "'" + expression[i + 1] + "'"; } sql = String.format(sql, params); - Log.d(TAG, sql); print(DEL_DATA, sql); mDb.execSQL(sql); close(); @@ -185,7 +184,6 @@ public class DbUtil { params[i] = "'" + expression[i + 1] + "'"; } sql = String.format(sql, params); - Log.d(TAG, sql); print(FIND_DATA, sql); Cursor cursor = mDb.rawQuery(sql, null); return cursor.getCount() > 0 ? newInstanceEntity(clazz, cursor) : null; diff --git a/Aria/src/main/java/com/arialyy/aria/util/BufferedRandomAccessFile.java b/Aria/src/main/java/com/arialyy/aria/util/BufferedRandomAccessFile.java new file mode 100644 index 00000000..a0f36785 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/util/BufferedRandomAccessFile.java @@ -0,0 +1,328 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arialyy.aria.util; + +import java.io.*; +import java.util.Arrays; + +//import org.apache.log4j.Logger; + +/** + * A BufferedRandomAccessFile is like a + * RandomAccessFile, but it uses a private buffer so that most + * operations do not require a disk access. + *

+ * + * Note: The operations on this class are unmonitored. Also, the correct + * functioning of the RandomAccessFile methods that are not + * overridden here relies on the implementation of those methods in the + * superclass. + * Author : Avinash Lakshman ( alakshman@facebook.com) Prashant Malik ( pmalik@facebook.com ) + */ + +public final class BufferedRandomAccessFile extends RandomAccessFile { + //private static final Logger logger_ = Logger.getLogger(BufferedRandomAccessFile.class); + static final int LogBuffSz_ = 16; // 64K buffer + public static final int BuffSz_ = (1 << LogBuffSz_); + static final long BuffMask_ = ~(((long) BuffSz_) - 1L); + + /* + * This implementation is based on the buffer implementation in Modula-3's + * "Rd", "Wr", "RdClass", and "WrClass" interfaces. + */ + private boolean dirty_; // true iff unflushed bytes exist + private boolean closed_; // true iff the file is closed + private long curr_; // current position in file + private long lo_, hi_; // bounds on characters in "buff" + private byte[] buff_; // local buffer + private long maxHi_; // this.lo + this.buff.length + private boolean hitEOF_; // buffer contains last file block? + private long diskPos_; // disk position + + /* + * To describe the above fields, we introduce the following abstractions for + * the file "f": + * + * len(f) the length of the file curr(f) the current position in the file + * c(f) the abstract contents of the file disk(f) the contents of f's + * backing disk file closed(f) true iff the file is closed + * + * "curr(f)" is an index in the closed interval [0, len(f)]. "c(f)" is a + * character sequence of length "len(f)". "c(f)" and "disk(f)" may differ if + * "c(f)" contains unflushed writes not reflected in "disk(f)". The flush + * operation has the effect of making "disk(f)" identical to "c(f)". + * + * A file is said to be *valid* if the following conditions hold: + * + * V1. The "closed" and "curr" fields are correct: + * + * f.closed == closed(f) f.curr == curr(f) + * + * V2. The current position is either contained in the buffer, or just past + * the buffer: + * + * f.lo <= f.curr <= f.hi + * + * V3. Any (possibly) unflushed characters are stored in "f.buff": + * + * (forall i in [f.lo, f.curr): c(f)[i] == f.buff[i - f.lo]) + * + * V4. For all characters not covered by V3, c(f) and disk(f) agree: + * + * (forall i in [f.lo, len(f)): i not in [f.lo, f.curr) => c(f)[i] == + * disk(f)[i]) + * + * V5. "f.dirty" is true iff the buffer contains bytes that should be + * flushed to the file; by V3 and V4, only part of the buffer can be dirty. + * + * f.dirty == (exists i in [f.lo, f.curr): c(f)[i] != f.buff[i - f.lo]) + * + * V6. this.maxHi == this.lo + this.buff.length + * + * Note that "f.buff" can be "null" in a valid file, since the range of + * characters in V3 is empty when "f.lo == f.curr". + * + * A file is said to be *ready* if the buffer contains the current position, + * i.e., when: + * + * R1. !f.closed && f.buff != null && f.lo <= f.curr && f.curr < f.hi + * + * When a file is ready, reading or writing a single byte can be performed + * by reading or writing the in-memory buffer without performing a disk + * operation. + */ + + /** + * Open a new BufferedRandomAccessFile on file + * in mode mode, which should be "r" for reading only, or + * "rw" for reading and writing. + */ + public BufferedRandomAccessFile(File file, String mode) throws IOException { + super(file, mode); + this.init(0); + } + + public BufferedRandomAccessFile(File file, String mode, int size) throws IOException { + super(file, mode); + this.init(size); + } + + /** + * Open a new BufferedRandomAccessFile on the file named + * name in mode mode, which should be "r" for + * reading only, or "rw" for reading and writing. + */ + public BufferedRandomAccessFile(String name, String mode) throws IOException { + super(name, mode); + this.init(0); + } + + public BufferedRandomAccessFile(String name, String mode, int size) throws FileNotFoundException { + super(name, mode); + this.init(size); + } + + private void init(int size) { + this.dirty_ = this.closed_ = false; + this.lo_ = this.curr_ = this.hi_ = 0; + this.buff_ = (size > BuffSz_) ? new byte[size] : new byte[BuffSz_]; + this.maxHi_ = (long) BuffSz_; + this.hitEOF_ = false; + this.diskPos_ = 0L; + } + + public void close() throws IOException { + this.flush(); + this.closed_ = true; + super.close(); + } + + /** + * Flush any bytes in the file's buffer that have not yet been written to + * disk. If the file was created read-only, this method is a no-op. + */ + public void flush() throws IOException { + this.flushBuffer(); + } + + /* Flush any dirty bytes in the buffer to disk. */ + private void flushBuffer() throws IOException { + if (this.dirty_) { + if (this.diskPos_ != this.lo_) super.seek(this.lo_); + int len = (int) (this.curr_ - this.lo_); + super.write(this.buff_, 0, len); + this.diskPos_ = this.curr_; + this.dirty_ = false; + } + } + + /* + * Read at most "this.buff.length" bytes into "this.buff", returning the + * number of bytes read. If the return result is less than + * "this.buff.length", then EOF was read. + */ + private int fillBuffer() throws IOException { + int cnt = 0; + int rem = this.buff_.length; + while (rem > 0) { + int n = super.read(this.buff_, cnt, rem); + if (n < 0) break; + cnt += n; + rem -= n; + } + if ((cnt < 0) && (this.hitEOF_ = (cnt < this.buff_.length))) { + // make sure buffer that wasn't read is initialized with -1 + Arrays.fill(this.buff_, cnt, this.buff_.length, (byte) 0xff); + } + this.diskPos_ += cnt; + return cnt; + } + + /* + * This method positions this.curr at position pos. + * If pos does not fall in the current buffer, it flushes the + * current buffer and loads the correct one.

+ * + * On exit from this routine this.curr == this.hi iff pos + * is at or past the end-of-file, which can only happen if the file was + * opened in read-only mode. + */ + public void seek(long pos) throws IOException { + if (pos >= this.hi_ || pos < this.lo_) { + // seeking outside of current buffer -- flush and read + this.flushBuffer(); + this.lo_ = pos & BuffMask_; // start at BuffSz boundary + this.maxHi_ = this.lo_ + (long) this.buff_.length; + if (this.diskPos_ != this.lo_) { + super.seek(this.lo_); + this.diskPos_ = this.lo_; + } + int n = this.fillBuffer(); + this.hi_ = this.lo_ + (long) n; + } else { + // seeking inside current buffer -- no read required + if (pos < this.curr_) { + // if seeking backwards, we must flush to maintain V4 + this.flushBuffer(); + } + } + this.curr_ = pos; + } + + public long getFilePointer() { + return this.curr_; + } + + public long length() throws IOException { + return Math.max(this.curr_, super.length()); + } + + public int read() throws IOException { + if (this.curr_ >= this.hi_) { + // test for EOF + // if (this.hi < this.maxHi) return -1; + if (this.hitEOF_) return -1; + + // slow path -- read another buffer + this.seek(this.curr_); + if (this.curr_ == this.hi_) return -1; + } + byte res = this.buff_[(int) (this.curr_ - this.lo_)]; + this.curr_++; + return ((int) res) & 0xFF; // convert byte -> int + } + + public int read(byte[] b) throws IOException { + return this.read(b, 0, b.length); + } + + public int read(byte[] b, int off, int len) throws IOException { + if (this.curr_ >= this.hi_) { + // test for EOF + // if (this.hi < this.maxHi) return -1; + if (this.hitEOF_) return -1; + + // slow path -- read another buffer + this.seek(this.curr_); + if (this.curr_ == this.hi_) return -1; + } + len = Math.min(len, (int) (this.hi_ - this.curr_)); + int buffOff = (int) (this.curr_ - this.lo_); + System.arraycopy(this.buff_, buffOff, b, off, len); + this.curr_ += len; + return len; + } + + public void write(int b) throws IOException { + if (this.curr_ >= this.hi_) { + if (this.hitEOF_ && this.hi_ < this.maxHi_) { + // at EOF -- bump "hi" + this.hi_++; + } else { + // slow path -- write current buffer; read next one + this.seek(this.curr_); + if (this.curr_ == this.hi_) { + // appending to EOF -- bump "hi" + this.hi_++; + } + } + } + this.buff_[(int) (this.curr_ - this.lo_)] = (byte) b; + this.curr_++; + this.dirty_ = true; + } + + public void write(byte[] b) throws IOException { + this.write(b, 0, b.length); + } + + public void write(byte[] b, int off, int len) throws IOException { + while (len > 0) { + int n = this.writeAtMost(b, off, len); + off += n; + len -= n; + this.dirty_ = true; + } + } + + /* + * Write at most "len" bytes to "b" starting at position "off", and return + * the number of bytes written. + */ + private int writeAtMost(byte[] b, int off, int len) throws IOException { + if (this.curr_ >= this.hi_) { + if (this.hitEOF_ && this.hi_ < this.maxHi_) { + // at EOF -- bump "hi" + this.hi_ = this.maxHi_; + } else { + // slow path -- write current buffer; read next one + this.seek(this.curr_); + if (this.curr_ == this.hi_) { + // appending to EOF -- bump "hi" + this.hi_ = this.maxHi_; + } + } + } + len = Math.min(len, (int) (this.hi_ - this.curr_)); + int buffOff = (int) (this.curr_ - this.lo_); + System.arraycopy(b, off, this.buff_, buffOff, len); + this.curr_ += len; + return len; + } +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/util/CAConfiguration.java b/Aria/src/main/java/com/arialyy/aria/util/CAConfiguration.java new file mode 100644 index 00000000..ac755396 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/util/CAConfiguration.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arialyy.aria.util; + +/** + * Created by Aria.Lao on 2017/1/11. + */ + +public class CAConfiguration { + public static String CA_PATH, CA_ALIAS; +} diff --git a/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java b/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java index 7cffe259..fd07b7c1 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java @@ -19,6 +19,7 @@ package com.arialyy.aria.util; import android.text.TextUtils; import android.util.Log; import com.arialyy.aria.core.DownloadEntity; +import com.arialyy.aria.exception.FileException; import java.io.File; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -30,6 +31,10 @@ import java.util.regex.Pattern; public class CheckUtil { private static final String TAG = "CheckUtil"; + public static void checkNull(Object obj) { + if (obj == null) throw new IllegalArgumentException("不能传入空对象"); + } + /** * 检查sql的expression是否合法 */ @@ -80,9 +85,9 @@ public class CheckUtil { return false; } else if (TextUtils.isEmpty(entity.getFileName())) { //Log.w(TAG, "文件名不能为空"); - throw new IllegalArgumentException("文件名不能为null"); + throw new FileException("文件名不能为null"); } else if (TextUtils.isEmpty(entity.getDownloadPath())) { - throw new IllegalArgumentException("文件保存路径不能为null"); + throw new FileException("文件保存路径不能为null"); } String fileName = entity.getFileName(); if (fileName.contains(" ")) { diff --git a/Aria/src/main/java/com/arialyy/aria/util/SSLContextUtil.java b/Aria/src/main/java/com/arialyy/aria/util/SSLContextUtil.java new file mode 100644 index 00000000..af40e0de --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/util/SSLContextUtil.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.util; + +import android.text.TextUtils; +import com.arialyy.aria.core.DownloadManager; +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +/** + * Created by Aria.Lao on 2017/1/11. + */ + +public class SSLContextUtil { + + /** + * 颁发服务器证书的 CA 未知 + * + * @param caAlias CA证书别名 + * @param caPath 保存在assets目录下的CA证书完整路径 + */ + public static SSLContext getSSLContext(String caAlias, String caPath) { + if (TextUtils.isEmpty(caAlias) || TextUtils.isEmpty(caPath)) { + return null; + } + // Load CAs from an InputStream + // (could be from a resource or ByteArrayInputStream or ...) + CertificateFactory cf = null; + try { + cf = CertificateFactory.getInstance("X.509"); + InputStream caInput = DownloadManager.APP.getAssets().open(caPath); + Certificate ca; + ca = cf.generateCertificate(caInput); + System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); + + // Create a KeyStore containing our trusted CAs + String keyStoreType = KeyStore.getDefaultType(); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry(caAlias, ca); + + // Create a TrustManager that trusts the CAs in our KeyStore + String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + KeyManagerFactory kmf = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, null); + + // Create an SSLContext that uses our TrustManager + SSLContext context = SSLContext.getInstance("TLS"); + context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); + return context; + } catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException | KeyManagementException | UnrecoverableKeyException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 服务器证书不是由 CA 签署的,而是自签署时,获取默认的SSL + */ + public static SSLContext getDefaultSLLContext() { + SSLContext sslContext = null; + try { + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] { trustManagers }, new SecureRandom()); + } catch (Exception e) { + e.printStackTrace(); + } + return sslContext; + } + + /** + * 创建自己的 TrustManager,这次直接信任服务器证书。这种方法具有前面所述的将应用与证书直接关联的所有弊端,但可以安全地操作。 + */ + private static TrustManager trustManagers = new X509TrustManager() { + + @Override public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + } + + @Override public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + + } + + @Override public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + + public static final HostnameVerifier HOSTNAME_VERIFIER = new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; +} diff --git a/README.md b/README.md index c63f85e8..fbf16549 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - 支持多线程、多任务下载 - 支持任务自动切换 - 支持下载速度直接获取 + - 支持https地址下载 [Aria怎样使用?](#使用) @@ -16,11 +17,9 @@ ## 下载 [![Download](https://api.bintray.com/packages/arialyy/maven/Aria/images/download.svg)](https://bintray.com/arialyy/maven/Aria/_latestVersion)
```java -compile 'com.arialyy.aria:Aria:2.3.8' +compile 'com.arialyy.aria:Aria:2.4.2' ``` - - ## 示例 ![多任务下载](https://github.com/AriaLyy/DownloadUtil/blob/v_2.0/img/download_img.gif) ![Dialog使用](https://github.com/AriaLyy/DownloadUtil/blob/v_2.0/img/dialog_use.gif "") @@ -52,6 +51,7 @@ compile 'com.arialyy.aria:Aria:2.3.8' } ``` ### 四、通过下载链接,你还能使用Aria执行很多操作,如: +Aria支持https下载,如果你希望使用自己的ca证书,那么你需要进行[Aria https证书配置](#https证书配置) - 添加任务(不进行下载) ```java @@ -118,8 +118,25 @@ compile 'com.arialyy.aria:Aria:2.3.8' Aria.get(this).openBroadcast(true); ``` +### https证书配置 + + 将你的证书导入`assets`目录 + + 调用以下代码配置ca证书相关信息 + + ```java + /** + * 设置CA证书信息 + * + * @param caAlias ca证书别名 + * @param caPath assets 文件夹下的ca证书完整路径 + */ + Aria.get(this).setCAInfo("caAlias","caPath"); + ``` + *** ## 开发日志 + + v_2.4.2 修复失败重试无效的bug + + v_2.4.1 修复下载慢的问题,修复application、service 不能使用的问题 + + v_2.4.0 支持https链接下载 + v_2.3.8 修复数据错乱的bug、添加fragment支持 + v_2.3.6 添加dialog、popupWindow支持 + v_2.3.3 @@ -128,7 +145,6 @@ compile 'com.arialyy.aria:Aria:2.3.8' - 修复一个内存泄露的bug + v_2.3.1 重命名为Aria,下载流程简化 + v_2.1.1 增加,选择最大下载任务数接口 - + v_2.1.0 修复大量bug License ------- diff --git a/app/src/main/java/com/arialyy/simple/MainActivity.java b/app/src/main/java/com/arialyy/simple/MainActivity.java index 33a10ee8..0df80bbc 100644 --- a/app/src/main/java/com/arialyy/simple/MainActivity.java +++ b/app/src/main/java/com/arialyy/simple/MainActivity.java @@ -33,6 +33,7 @@ import com.arialyy.simple.databinding.ActivityMainBinding; import com.arialyy.simple.dialog_task.DownloadDialog; import com.arialyy.simple.fragment_task.FragmentActivity; import com.arialyy.simple.multi_task.MultiTaskActivity; +import com.arialyy.simple.notification.SimpleNotification; import com.arialyy.simple.pop_task.DownloadPopupWindow; import com.arialyy.simple.single_task.SingleTaskActivity; @@ -104,6 +105,10 @@ public class MainActivity extends BaseActivity { case R.id.fragment_task: startActivity(new Intent(this, FragmentActivity.class)); break; + case R.id.notification: + SimpleNotification notification = new SimpleNotification(this); + notification.start(); + break; } } } \ No newline at end of file diff --git a/app/src/main/java/com/arialyy/simple/multi_task/FileListAdapter.java b/app/src/main/java/com/arialyy/simple/multi_task/FileListAdapter.java index 97e9b773..c3e87256 100644 --- a/app/src/main/java/com/arialyy/simple/multi_task/FileListAdapter.java +++ b/app/src/main/java/com/arialyy/simple/multi_task/FileListAdapter.java @@ -67,7 +67,6 @@ final class FileListAdapter extends AbsRVAdapter keys = mBtStates.keySet(); for (String key : keys) { if (key.equals(downloadUrl)) { - Log.d(TAG, "able ==> " + able); mBtStates.put(downloadUrl, able); notifyItemChanged(indexItem(downloadUrl)); return; @@ -80,7 +79,7 @@ final class FileListAdapter extends AbsRVAdapter " + index); + //Log.d(TAG, "index ==> " + index); return index; } } diff --git a/app/src/main/java/com/arialyy/simple/notification/SimpleNotification.java b/app/src/main/java/com/arialyy/simple/notification/SimpleNotification.java new file mode 100644 index 00000000..3d11fd69 --- /dev/null +++ b/app/src/main/java/com/arialyy/simple/notification/SimpleNotification.java @@ -0,0 +1,98 @@ +package com.arialyy.simple.notification; + +import android.app.NotificationManager; +import android.content.Context; +import android.os.Environment; +import android.support.v4.app.NotificationCompat; +import com.arialyy.aria.core.Aria; +import com.arialyy.aria.core.task.Task; +import com.arialyy.frame.util.show.L; +import com.arialyy.frame.util.show.T; +import com.arialyy.simple.R; + +/** + * Created by Aria.Lao on 2017/1/18. + */ + +public class SimpleNotification { + private static final String DOWNLOAD_URL = + "http://static.gaoshouyou.com/d/6e/e5/ff6ecaaf45e532e6d07747af82357472.apk"; + + private NotificationManager mManager; + private Context mContext; + private NotificationCompat.Builder mBuilder; + private static final int mNotifiyId = 0; + + public SimpleNotification(Context context) { + mContext = context; + init(); + } + + private void init() { + mManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + mBuilder = new NotificationCompat.Builder(mContext); + mBuilder.setContentTitle("Aria Download Test") + .setContentText("进度条") + .setProgress(100, 0, false) + .setSmallIcon(R.mipmap.ic_launcher); + mManager.notify(mNotifiyId, mBuilder.build()); + Aria.whit(mContext).addSchedulerListener(new DownloadCallback(mBuilder, mManager)); + } + + public void start() { + Aria.whit(mContext) + .load(DOWNLOAD_URL) + .setDownloadName("notification_test.apk") + .setDownloadPath( + Environment.getExternalStorageDirectory() + "/Download/notification_test.apk") + .start(); + } + + public void stop() { + Aria.whit(mContext).load(DOWNLOAD_URL).stop(); + } + + private static class DownloadCallback extends Aria.SimpleSchedulerListener { + NotificationCompat.Builder mBuilder; + NotificationManager mManager; + + private DownloadCallback(NotificationCompat.Builder builder, NotificationManager manager) { + mBuilder = builder; + mManager = manager; + } + + @Override public void onTaskStart(Task task) { + super.onTaskStart(task); + } + + @Override public void onTaskPre(Task task) { + super.onTaskPre(task); + } + + @Override public void onTaskStop(Task task) { + super.onTaskStop(task); + } + + @Override public void onTaskRunning(Task task) { + super.onTaskRunning(task); + long len = task.getFileSize(); + int p = (int) (task.getCurrentProgress() * 100 / len); + if (mBuilder != null) { + mBuilder.setProgress(100, p, false); + mManager.notify(mNotifiyId, mBuilder.build()); + } + } + + @Override public void onTaskComplete(Task task) { + super.onTaskComplete(task); + if (mBuilder != null) { + mBuilder.setProgress(100, 100, false); + mManager.notify(mNotifiyId, mBuilder.build()); + } + } + + @Override public void onTaskCancel(Task task) { + super.onTaskCancel(task); + } + } +} diff --git a/app/src/main/java/com/arialyy/simple/single_task/SingleTaskActivity.java b/app/src/main/java/com/arialyy/simple/single_task/SingleTaskActivity.java index b66ae0b4..4d0bdff2 100644 --- a/app/src/main/java/com/arialyy/simple/single_task/SingleTaskActivity.java +++ b/app/src/main/java/com/arialyy/simple/single_task/SingleTaskActivity.java @@ -33,6 +33,8 @@ import butterknife.Bind; import com.arialyy.aria.core.AMTarget; import com.arialyy.aria.core.Aria; import com.arialyy.aria.core.DownloadEntity; +import com.arialyy.aria.core.task.DownloadUtil; +import com.arialyy.aria.core.task.IDownloadListener; import com.arialyy.aria.core.task.Task; import com.arialyy.aria.util.CommonUtil; import com.arialyy.frame.util.show.L; @@ -49,10 +51,11 @@ public class SingleTaskActivity extends BaseActivity { public static final int DOWNLOAD_RESUME = 0x05; public static final int DOWNLOAD_COMPLETE = 0x06; public static final int DOWNLOAD_RUNNING = 0x07; + private static final String DOWNLOAD_URL = - "http://static.gaoshouyou.com/d/3a/93/573ae1db9493a801c24bf66128b11e39.apk"; - //private static final String DOWNLOAD_URL = - // "http://www.yangqiang.im/wp-content/uploads/2016/10/%E6%8A%BD%E8%B1%A1%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F.png"; + //"http://kotlinlang.org/docs/kotlin-docs.pdf"; + //"https://atom-installer.github.com/v1.13.0/AtomSetup.exe?s=1484074138&ext=.exe"; + "http://static.gaoshouyou.com/d/21/e8/61218d78d0e8b79df68dbc18dd484c97.apk"; @Bind(R.id.progressBar) HorizontalProgressBarWithNumber mPb; @Bind(R.id.start) Button mStart; @Bind(R.id.stop) Button mStop; @@ -89,7 +92,7 @@ public class SingleTaskActivity extends BaseActivity { case DOWNLOAD_PRE: mSize.setText(CommonUtil.formatFileSize((Long) msg.obj)); setBtState(false); - mStart.setText("暂停"); + //mStart.setText("暂停"); break; case DOWNLOAD_FAILE: Toast.makeText(SingleTaskActivity.this, "下载失败", Toast.LENGTH_SHORT).show(); @@ -114,15 +117,11 @@ public class SingleTaskActivity extends BaseActivity { setBtState(false); break; case DOWNLOAD_COMPLETE: + mPb.setProgress(100); Toast.makeText(SingleTaskActivity.this, "下载完成", Toast.LENGTH_SHORT).show(); mStart.setText("重新开始?"); mCancel.setEnabled(false); setBtState(true); - - //String path = Environment.getExternalStorageDirectory().getPath() + "/test.jpg"; - //Bitmap bm = BitmapFactory.decodeFile(path); - //mImg.setImageBitmap(bm); - break; } } @@ -200,9 +199,68 @@ public class SingleTaskActivity extends BaseActivity { .load(DOWNLOAD_URL) .setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk") .setDownloadName("test.apk") - //.setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.jpg") - //.setDownloadName("test.jpg") .start(); + //DownloadEntity entity = new DownloadEntity(); + //entity.setDownloadUrl(DOWNLOAD_URL); + //entity.setDownloadPath(Environment.getExternalStorageDirectory().getPath() + "/test.apk"); + //entity.setFileName("test.apk"); + //DownloadUtil util = new DownloadUtil(this, entity, new IDownloadListener() { + // long fileSize = 1; + // @Override public void supportBreakpoint(boolean support) { + // + // } + // + // @Override public void onCancel() { + // + // } + // + // @Override public void onFail() { + // + // } + // + // @Override public void onPre() { + // + // } + // + // @Override public void onPostPre(long fileSize) { + // this.fileSize = fileSize; + // } + // + // @Override public void onProgress(long currentLocation) { + // long current = currentLocation; + // long len = fileSize; + // if (len == 0) { + // mPb.setProgress(0); + // } else { + // mPb.setProgress((int) ((current * 100) / len)); + // } + // } + // + // @Override public void onChildComplete(long finishLocation) { + // + // } + // + // @Override public void onStart(long startLocation) { + // + // } + // + // @Override public void onChildResume(long resumeLocation) { + // + // } + // + // @Override public void onResume(long resumeLocation) { + // + // } + // + // @Override public void onStop(long stopLocation) { + // + // } + // + // @Override public void onComplete() { + // + // } + //}); + //util.startDownload(); } private void stop() { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 464bb3a5..08ccd012 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -56,5 +56,14 @@ style="?buttonBarButtonStyle" /> +