diff --git a/.idea/misc.xml b/.idea/misc.xml index 5d199810..fbb68289 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -37,7 +37,7 @@ - + diff --git a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java index b02cdceb..b9dcb150 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; @@ -48,12 +49,12 @@ import java.util.Set; * Aria管理器,任务操作在这里执行 */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public class AriaManager { - private static final String TAG = "AriaManager"; - private static final Object LOCK = new Object(); - private static volatile AriaManager INSTANCE = null; - private Map mTargets = new HashMap<>(); + private static final String TAG = "AriaManager"; + private static final Object LOCK = new Object(); + private static volatile AriaManager INSTANCE = null; + private Map mTargets = new HashMap<>(); private DownloadManager mManager; - private LifeCallback mLifeCallback; + private LifeCallback mLifeCallback; private AriaManager(Context context) { regAppLifeCallback(context); @@ -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(); @@ -249,12 +268,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 +335,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/task/DownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/task/DownloadUtil.java index d5eb65e2..80714b08 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,7 +20,9 @@ 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; @@ -29,43 +31,47 @@ 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. * 下载工具类 */ final class DownloadUtil implements IDownloadUtil, Runnable { - private static final String TAG = "DownloadUtil"; + private static final String TAG = "DownloadUtil"; private static final Object LOCK = new Object(); /** * 线程数 */ - private final int THREAD_NUM; + private final int THREAD_NUM; //下载监听 - private IDownloadListener mListener; - private int mConnectTimeOut = 5000 * 4; //连接超时时间 - private int mReadTimeOut = 5000 * 20; //流读取的超时时间 + 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 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 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 File mDownloadFile; //下载的文件 + private File mConfigFile;//下载信息配置文件 private SparseArray mTask = new SparseArray<>(); DownloadUtil(Context context, DownloadEntity entity, IDownloadListener downloadListener) { @@ -222,8 +228,8 @@ final class DownloadUtil implements IDownloadUtil, Runnable { @Override public void run() { try { - URL url = new URL(mDownloadEntity.getDownloadUrl()); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + URL url = new URL(mDownloadEntity.getDownloadUrl()); + HttpURLConnection conn = handleConnection(url); setConnectParam(conn); conn.setRequestProperty("Range", "bytes=" + 0 + "-"); conn.setConnectTimeout(mConnectTimeOut * 4); @@ -242,7 +248,7 @@ 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){ + if (len < 0) { failDownload("任务【" + mDownloadEntity.getDownloadUrl() + "】下载失败,文件长度小于0"); return; } @@ -263,6 +269,30 @@ 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; + } + /** * 处理断点 */ @@ -305,15 +335,15 @@ 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; Log.d(TAG, "++++++++++ 线程_" + i + "_已经下载完成 ++++++++++"); @@ -381,12 +411,12 @@ final class DownloadUtil implements IDownloadUtil, Runnable { */ private static class ConfigEntity { //文件大小 - long fileSize; - String downloadUrl; - int threadId; - long startLocation; - long endLocation; - File tempFile; + long fileSize; + String downloadUrl; + int threadId; + long startLocation; + long endLocation; + File tempFile; } /** @@ -395,7 +425,7 @@ final class DownloadUtil implements IDownloadUtil, Runnable { private class SingleThreadTask implements Runnable { private static final String TAG = "SingleThreadTask"; private ConfigEntity configEntity; - private String configFPath; + private String configFPath; private long currentLocation = 0; private SingleThreadTask(ConfigEntity downloadInfo) { @@ -410,10 +440,11 @@ final class DownloadUtil implements IDownloadUtil, Runnable { @Override public void run() { HttpURLConnection conn = null; - InputStream is = null; + InputStream is = null; try { URL url = new URL(configEntity.downloadUrl); - conn = (HttpURLConnection) url.openConnection(); + //conn = (HttpURLConnection) url.openConnection(); + conn = handleConnection(url); if (isSupportBreakpoint) { Log.d(TAG, "线程_" + configEntity.threadId @@ -437,7 +468,7 @@ final class DownloadUtil implements IDownloadUtil, Runnable { //设置每条线程写入文件的位置 file.seek(configEntity.startLocation); byte[] buffer = new byte[1024]; - int len; + int len; //当前子线程的下载位置 currentLocation = configEntity.startLocation; while ((len = is.read(buffer)) != -1) { @@ -597,8 +628,8 @@ final class DownloadUtil implements IDownloadUtil, Runnable { * 将记录写入到配置文件 */ private void writeConfig(String key, String record) throws IOException { - File configFile = new File(configFPath); - Properties pro = CommonUtil.loadConfig(configFile); + File configFile = new File(configFPath); + Properties pro = CommonUtil.loadConfig(configFile); pro.setProperty(key, record); CommonUtil.saveConfig(configFile, pro); } 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..903653ce --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/util/CAConfiguration.java @@ -0,0 +1,9 @@ +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/SSLContextUtil.java b/Aria/src/main/java/com/arialyy/aria/util/SSLContextUtil.java new file mode 100644 index 00000000..6ab8f3dc --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/util/SSLContextUtil.java @@ -0,0 +1,112 @@ +package com.arialyy.aria.util; + +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) { + // 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/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..9972c4ab 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 @@ -49,10 +49,10 @@ 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://dl.google.com/dl/android/studio/install/2.2.3.0/android-studio-bundle-145.3537739-windows.exe"; @Bind(R.id.progressBar) HorizontalProgressBarWithNumber mPb; @Bind(R.id.start) Button mStart; @Bind(R.id.stop) Button mStop; @@ -114,6 +114,7 @@ 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);