前言
在经过一段时间使用OKHttp之后,偶尔需要应用别人的jar,但是别人的jar中已经包含了OKHttp之后,又是各种麻烦修改,考虑种种之后想办法自己在HttpUrlConnection方面写一个断点下载,同时如果自己不需要断点下载,简单的设置以下就可以了。
知识点
(1)断点传递给服务器端,请求需要的从断点开始的数据
// 统一资源
URL httpUrl = new URL(url);
// 连接类的父类,抽象类
URLConnection urlConnection = httpUrl.openConnection();
// http的连接类
HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
if (url.toUpperCase().startsWith("HTTPS")) {
httpURLConnection = (HttpURLConnection) urlConnection;
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) httpURLConnection;
httpsURLConnection.setHostnameVerifier(new HttpsHostnameVerifier());
httpsURLConnection.setSSLSocketFactory(HttpsSSLSocketFactory.factory());
httpURLConnection = httpsURLConnection;
}
httpURLConnection.setDoInput(true);
// 设定请求的方法,默认是GET
httpURLConnection.setRequestMethod("GET");
// 设置字符编码
httpURLConnection.setRequestProperty("Charset", "UTF-8");
long downloadedLength = calculateDownloadedLength(url);
//设置下载位置
httpURLConnection.setRequestProperty("RANGE", "bytes=" + downloadedLength + "-");
// 打开到此 URL 引用的资源的通信链接(如果尚未建立这样的连接)。
httpURLConnection.connect();
(2)在写入数据的时候,调节对应的断点进行结合:
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
randomAccessFile.seek(downloadedLength);
方案一:
RandomAccessFile randomAccessFile = new RandomAccessFile(file, “rw”);
randomAccessFile.seek(downloadedLength);
方案二:
FileChannel channelOut = randomAccessFile.getChannel()
MappedByteBuffer mappedBuffer = channelOut.map(FileChannel.MapMode.READ_WRITE,response.body().contentLength());
此处注意的是,在写入数据的时候,有人喜欢用数据通道加缓存来写入数据,这样写的结果其实都是可以的,唯一的区别是用缓存加数据通道(方案二),第一次写入就相当于占用了内存空间,但是实际没有那么大的内存。
(3)在HttpUrlConnection设置对应的下载起点之后,是可以正常下载,但是如果遇到已经下载完的文件,再次点击文件下载,在getInputSteam()方法会IOException.这时候需要判断response返回的code,如果是416那么就返回已经存在的文件就行了。
int code = httpURLConnection.getResponseCode();
if (code == 416) {
sendCompletedMsg(createFile(url));
return;
}
使用
HucDownloader.Builder builder = new HucDownloader.Builder()
.url(file_url)
.name("Git.zip")
.folder("Downloader")
.isBreakpoint(true)
.listener(new OnDownloadListener() {
@Override
public void onDownloading(long total, long progress, int percent) {
Log.e("RRL", "onDownloading " + percent);
}
@Override
public void onDownloadCompleted(File file) {
Log.e("RRL", "onDownloadCompleted " + file.getAbsolutePath());
}
@Override
public void onDownloadFailed(Exception e) {
Log.e("RRL", "onDownloadFailed " + e.toString());
}
});
downloader = new HucDownloader(builder);
downloader.start();
源码
(1)HttpsHostnameVerifier
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
/**
* Created by Relin
* on 2018-11-01.
*/
public class HttpsHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
(2)HttpsX509TrustManager
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
/**
* Created by Relin
* on 2018-11-01.
*/
public class HttpsX509TrustManager implements 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];
}
}
(3)HttpsSSLSocketFactory
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
/**
* Created by Relin
* on 2018-11-01.
*/
public class HttpsSSLSocketFactory {
public static SSLSocketFactory factory() {
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("SSL");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
TrustManager[] tm = {new HttpsX509TrustManager()};
try {
sslContext.init(null, tm, new java.security.SecureRandom());
} catch (KeyManagementException e) {
e.printStackTrace();
}
return sslContext.getSocketFactory();
}
}
(4)HucDownload.java
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import com.android.image.http.HttpsHostnameVerifier;
import com.android.image.http.HttpsSSLSocketFactory;
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.net.URLConnection;
import java.text.SimpleDateFormat;
import javax.net.ssl.HttpsURLConnection;
/**
* Created by Relin
* on 2018/9/19.<br/>
* 下载助手可以帮助你简单的调用函数进行下载,同时如果你需要自定义下载网络的方式,<br/>
* 只需要继承该类然后重写download方法,在获取到文件流之后调用doHttpResponse()<br/>
* 处理对应逻辑就行了。<br/>
* download assistant can help you simply calling a function for download, at the same time,< br / >
* if you need a custom download network way, < br / >
* only needs to inherit the class and then rewrite the download method,< br / >
* after the access to file stream call doHttpResponse () < br / >
* handle corresponding logic. < br / >
*/
public class HucDownloader {
/**
* 缓存文件夹
*/
public static final String CACHE_FOLDER = "Downloader";
public static final int WHAT_DOWNLOADING = 0x001;
public static final int WHAT_DOWNLOAD_COMPLETED = 0x002;
public static final int WHAT_DOWNLOAD_FAILED = 0x003;
/**
* 总的大小
*/
private long totalSize = 0;
/**
* 是否取消
*/
private boolean isCancel;
/**
* 是否暂停
*/
private boolean isPause;
/**
* 是否在下载中
*/
private boolean isDownloading;
/**
* 资源地址
*/
public final String url;
/**
* 文件名称
*/
public final String name;
/**
* 缓存文件夹
*/
public final String folder;
/**
* 实发支持断点下载
*/
public final boolean isBreakpoint;
/**
* 下载监听
*/
public OnDownloadListener onDownloadListener;
public HucDownloader(Builder builder) {
this.url = builder.url;
this.name = builder.name;
this.folder = builder.folder;
this.isBreakpoint = builder.isBreakpoint;
this.onDownloadListener = builder.onDownloadListener;
}
public static class Builder {
private String url;
private String name;
private String folder;
private boolean isBreakpoint;
private OnDownloadListener onDownloadListener;
public Builder url(String url) {
this.url = url;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder folder(String folder) {
this.folder = folder;
return this;
}
public Builder isBreakpoint(boolean isBreakpoint) {
this.isBreakpoint = isBreakpoint;
return this;
}
public Builder listener(OnDownloadListener onDownloadListener) {
this.onDownloadListener = onDownloadListener;
return this;
}
public HucDownloader build() {
return new HucDownloader(this);
}
}
protected boolean isBreakpoint() {
return isBreakpoint;
}
protected boolean isPause() {
return isPause;
}
protected boolean isCancel() {
return isCancel;
}
protected boolean isDownloading() {
return isDownloading;
}
public void setDownloading(boolean downloading) {
this.isDownloading = downloading;
}
/**
* 开始下载
*/
public void start() {
isPause = false;
isCancel = false;
if (!isDownloading) {
download();
}
}
/**
* 暂停下载
*/
public void pause() {
isPause = true;
}
/**
* 取消下载
*/
public void cancel() {
isCancel = true;
}
/**
* 销毁下载
*/
public void destory() {
cancel();
if (handler != null) {
handler.removeCallbacksAndMessages(null);
handler = null;
}
}
/**
* 创建文件
*
* @param url 资源地址
* @return
*/
protected File createFile(String url) {
File cacheFolder = new File(new IOUtils().createFolder(folder == null ? CACHE_FOLDER : folder));
if (cacheFolder.isDirectory() && !cacheFolder.exists()) {
cacheFolder.mkdirs();
}
return new File(cacheFolder.getAbsolutePath() + File.separator + (name == null ? createName(url) : name));
}
/**
* 创建Url文件名称
*
* @param url 资源地址
* @return
*/
private String createName(String url) {
if (url.contains("/") && url.contains(".")) {
return url.substring(url.lastIndexOf("/") + 1);
}
SimpleDateFormat format = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
return format.format(format) + ".zip";
}
/**
* 下载文件
*/
private void download() {
if (TextUtils.isEmpty(url)) {
sendFailedMsg(new IOException("File download network address is empty."));
return;
}
if (!url.toUpperCase().startsWith("HTTP")) {
sendFailedMsg(new IOException("File download address error, unable to download normal."));
return;
}
setDownloading(true);
download(url);
}
protected void download(final String url) {
new Thread() {
@Override
public void run() {
super.run();
try {
// 统一资源
URL httpUrl = new URL(url);
// 连接类的父类,抽象类
URLConnection urlConnection = httpUrl.openConnection();
// http的连接类
HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
if (url.toUpperCase().startsWith("HTTPS")) {
httpURLConnection = (HttpURLConnection) urlConnection;
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) httpURLConnection;
httpsURLConnection.setHostnameVerifier(new HttpsHostnameVerifier());
httpsURLConnection.setSSLSocketFactory(HttpsSSLSocketFactory.factory());
httpURLConnection = httpsURLConnection;
}
httpURLConnection.setDoInput(true);
// 设定请求的方法,默认是GET
httpURLConnection.setRequestMethod("GET");
// 设置字符编码
httpURLConnection.setRequestProperty("Charset", "UTF-8");
long downloadedLength = calculateDownloadedLength(url);
//设置下载位置
httpURLConnection.setRequestProperty("RANGE", "bytes=" + downloadedLength + "-");
// 打开到此 URL 引用的资源的通信链接(如果尚未建立这样的连接)。
httpURLConnection.connect();
int code = httpURLConnection.getResponseCode();
if (code == 416) {
sendCompletedMsg(createFile(url));
return;
}
// 文件大小
int contentLength = httpURLConnection.getContentLength();
InputStream is = httpURLConnection.getInputStream();
doHttpResponse(is, contentLength, downloadedLength, createFile(url));
} catch (MalformedURLException e) {
e.printStackTrace();
sendFailedMsg(e);
} catch (IOException e) {
e.printStackTrace();
sendFailedMsg(e);
}
}
}.start();
}
/**
* 计算已经下载过的文件大小
*
* @param url
* @return
*/
private long calculateDownloadedLength(String url) {
File file = createFile(url);
if (file.exists()) {
if (isBreakpoint()) {
return file.length();
} else {
file.delete();
}
}
return 0;
}
/**
* 处理服务器返回数据
*/
protected void doHttpResponse(InputStream is, long contentLength, long downloadedLength, File file) {
long downloading = 0;
byte[] buf = new byte[2048];
int len;
RandomAccessFile randomAccessFile = null;
try {
if (downloadedLength == 0) {
totalSize = contentLength;
} else {
totalSize = downloadedLength + contentLength;
}
if (totalSize == downloadedLength) {
//已下载字节和文件总字节相等,说明下载已经完成了
sendCompletedMsg(file);
return;
}
if (totalSize == 0) {
if (downloadedLength == 0) {
sendFailedMsg(new IOException("The file length value is 0 and cannot be downloaded properly"));
} else {
if (isBreakpoint()) {
sendCompletedMsg(file);
} else {
file.delete();
}
}
return;
}
randomAccessFile = new RandomAccessFile(file, "rw");
randomAccessFile.seek(downloadedLength);
while ((len = is.read(buf)) != -1) {
if (isPause() || isCancel()) {
break;
}
randomAccessFile.write(buf, 0, len);
downloading += len;
long downSum = downloading + downloadedLength;
//传递更新信息
int percentage = (int) ((downloadedLength + downloading) * 100 / totalSize);
sendDownloadingMsg(totalSize, downSum, percentage);
}
randomAccessFile.close();
sendCompletedMsg(file);
} catch (Exception e) {
sendFailedMsg(e);
} finally {
setDownloading(false);
try {
if (is != null)
is.close();
} catch (IOException e) {
sendFailedMsg(e);
}
try {
if (randomAccessFile != null)
randomAccessFile.close();
} catch (IOException e) {
sendFailedMsg(e);
}
}
}
/**
* 发送成功的信息
*
* @param file
*/
protected void sendCompletedMsg(File file) {
Message msg = handler.obtainMessage();
msg.what = WHAT_DOWNLOAD_COMPLETED;
msg.obj = file;
handler.sendMessage(msg);
}
/**
* 发送下载失败信息
*
* @param e 文件异常
*/
protected void sendFailedMsg(Exception e) {
Message msg = handler.obtainMessage();
msg.what = WHAT_DOWNLOAD_FAILED;
msg.obj = e;
handler.sendMessage(msg);
}
/**
* 发送下载信息
*
* @param total 文件总大小
* @param progress 文件进度
* @param percent 百分比
*/
protected void sendDownloadingMsg(long total, long progress, int percent) {
Message message = handler.obtainMessage();
message.what = WHAT_DOWNLOADING;
Bundle bundle = new Bundle();
bundle.putLong("total", total);
bundle.putLong("progress", progress);
bundle.putInt("percent", percent);
message.setData(bundle);
handler.sendMessage(message);
}
/**
* 下载Handler处理
*/
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (onDownloadListener == null) {
return;
}
Bundle data = msg.getData();
Object obj = msg.obj;
switch (msg.what) {
case WHAT_DOWNLOADING:
onDownloadListener.onDownloading(data.getLong("total"), data.getLong("progress"), data.getInt("percent"));
break;
case WHAT_DOWNLOAD_COMPLETED:
onDownloadListener.onDownloadCompleted((File) obj);
break;
case WHAT_DOWNLOAD_FAILED:
onDownloadListener.onDownloadFailed((Exception) obj);
break;
}
}
};
}