From 235dfef19d754d9c5565dbd8b5c9cc2477c7b696 Mon Sep 17 00:00:00 2001 From: AriaLyy <511455842@qq.com> Date: Wed, 18 Jan 2017 17:18:08 +0800 Subject: [PATCH] =?UTF-8?q?DownloadUtil=20=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aria/core/task/ConnectionHelp.java | 74 ++++ .../core/task/DownloadStateConstance.java | 76 ++++ .../arialyy/aria/core/task/DownloadUtil.java | 367 +++--------------- .../aria/core/task/SingleThreadTask.java | 251 ++++++++++++ .../arialyy/aria/exception/FileException.java | 13 + .../java/com/arialyy/aria/util/CheckUtil.java | 5 +- .../simple/multi_task/FileListAdapter.java | 3 +- 7 files changed, 468 insertions(+), 321 deletions(-) create mode 100644 Aria/src/main/java/com/arialyy/aria/core/task/ConnectionHelp.java create mode 100644 Aria/src/main/java/com/arialyy/aria/core/task/DownloadStateConstance.java create mode 100644 Aria/src/main/java/com/arialyy/aria/core/task/SingleThreadTask.java create mode 100644 Aria/src/main/java/com/arialyy/aria/exception/FileException.java 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..8d264d5a --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/task/ConnectionHelp.java @@ -0,0 +1,74 @@ +/* + * 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"); + 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 80714b08..21c8af7c 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,24 +20,15 @@ import android.content.Context; import android.util.Log; import android.util.SparseArray; import com.arialyy.aria.core.DownloadEntity; -import com.arialyy.aria.util.CAConfiguration; import com.arialyy.aria.util.CommonUtil; -import com.arialyy.aria.util.SSLContextUtil; 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.net.URLConnection; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; /** * Created by lyy on 2015/8/25. @@ -45,7 +36,6 @@ import javax.net.ssl.SSLSocketFactory; */ final class DownloadUtil implements IDownloadUtil, Runnable { private static final String TAG = "DownloadUtil"; - private static final Object LOCK = new Object(); /** * 线程数 */ @@ -54,25 +44,15 @@ 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) { this(context, entity, downloadListener, 3); @@ -85,6 +65,7 @@ final class DownloadUtil implements IDownloadUtil, Runnable { mListener = downloadListener; THREAD_NUM = threadNum; mFixedThreadPool = Executors.newFixedThreadPool(Integer.MAX_VALUE); + mConstance = new DownloadStateConstance(THREAD_NUM); init(); } @@ -128,19 +109,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); @@ -154,8 +135,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); @@ -195,13 +176,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(); } @@ -212,25 +187,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 = handleConnection(url); - setConnectParam(conn); + HttpURLConnection conn = ConnectionHelp.handleConnection(url); + conn = ConnectionHelp.setConnectParam(conn); conn.setRequestProperty("Range", "bytes=" + 0 + "-"); conn.setConnectTimeout(mConnectTimeOut * 4); conn.connect(); @@ -269,44 +235,23 @@ final class DownloadUtil implements IDownloadUtil, Runnable { } } - /** - * 处理链接 - * - * @throws IOException - */ - private 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; - } - /** * 处理断点 */ 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; @@ -345,17 +290,17 @@ final class DownloadUtil implements IDownloadUtil, Runnable { 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; @@ -365,7 +310,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; @@ -383,19 +328,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; @@ -409,229 +356,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(); - conn = handleConnection(url); - 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..d305d98c --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/task/SingleThreadTask.java @@ -0,0 +1,251 @@ +/* + * 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.util.Log; +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"; + 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(); + //创建可设置位置的文件 + RandomAccessFile file = new RandomAccessFile(mConfigEntity.TEMP_FILE, "rwd"); + //设置每条线程写入文件的位置 + file.seek(mConfigEntity.START_LOCATION); + byte[] buffer = new byte[1024]; + 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); + } + } + + /** + * 取消下载 + */ + 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/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/util/CheckUtil.java b/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java index 7cffe259..5326a0a6 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; @@ -80,9 +81,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/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; } }