您的位置:首页 > 理论基础 > 计算机网络

Android 基于OkHttp的下载,支持https,断点下载,优化下载速度

2017-03-17 15:21 330 查看
package com.lenovo.smarthcc.http.okhttp;

import android.text.TextUtils;

import com.lenovo.smarthcc.http.Listenner;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import okhttp3.Call;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okio.Buffer;
import okio.Okio;
import okio.Sink;
import okio.Source;

/**
* <p>Author:    shijiale</p>
* <p>Date:      2017/3/14.<p>

* <p>Describe:
*/

public class BRDownloadThread extends Thread {

private static OkHttpClient mClient;                 //okhttp client
private Builder mBuilder;                            //配置
private boolean isExit = false;                      //是否退出下载
private long mStartTime = 0;                         //当前这次下载起始时间
private long mDownloadLength;                        //当前这次下载的长度
private final int MAX_BUFF_SIZE = 4 * 1024;          //每次发送的字节(这个数值可能没有什么作用,因为大部分http流都会做缓存)
private Call mCall;                                  //当前请求
private DaemonThead mDaemonMsgThread;                //用于进度回调的线程

/***
* 退出当前下载线程
*/
public synchronized void exit() {
isExit = true;
}

private BRDownloadThread() {
mClient = OkHttpProxy.getClient();
if (mDaemonMsgThread == null) {
mDaemonMsgThread = new DaemonThead();
mDaemonMsgThread.isRunning = true;
}
}

@Override
public void run() {
super.run();
try {
downloadFile();
} catch (Exception e) {
mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, e.getMessage());
} finally {
if (mDaemonMsgThread != null) {
mDaemonMsgThread.isRunning = false;
mDaemonMsgThread = null;
}
}
}

/***
* 自动修改下载的文件名称
* @param fileLength 文件在服务器的大小
*/
private void autoChangeFileName(long fileLength) {
File file = new File(mBuilder.mSavePath);
if(file != null && file.exists() && file.length() == fileLength) {
String path = mBuilder.mSavePath;
path = path.substring(0,path.lastIndexOf(".") + 1);

String expresion = mBuilder.mSavePath.substring(path.lastIndexOf(".") + 1,mBuilder.mSavePath.length());
path += "(" + new Date(System.currentTimeMillis()).toLocaleString() + ")" + expresion;
mBuilder.mSavePath = path;
}
}

private boolean chekDirectory() {
String path = mBuilder.mSavePath;
path = path.substring(0,path.lastIndexOf(File.separator) + 1);
File file = new File(path);
if(file != null && file.exists()) {
return true;
}
if(file == null || !file.exists()) {
boolean mkdirs = file.mkdirs();
if(!mkdirs) {
return false;
}
}
return true;
}

/***
* 下载文件
* @throws IOException
*/
private void downloadFile() throws IOException {

if(!chekDirectory()) {
mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR,mBuilder.mSavePath,"directory create failed!");
return;
}
//获取文件长度
mBuilder.mFileAllLength = getNetFileSize();
//如果允许重复下载,则对文件进行重命名,修改后的文件名会会在 onStop onSuccess中体现
if(mBuilder.isAutoRename) {
autoChangeFileName(mBuilder.mFileAllLength);
}
RandomAccessFile file = new RandomAccessFile(mBuilder.mSavePath, "rw");
//如果本地已经存在该文件,并且大小和服务器相同则返回下载成功
if (file != null && file.length() == mBuilder.mFileAllLength && mBuilder.mFileAllLength != 0) {
mBuilder.mSuccessListenner.onChanged(Listenner.STATUS_SUCCESS, mBuilder.mSavePath,mBuilder.mFileAllLength + "");
return;
}
//设置文件起始指针
if (file != null && file.length() != 0) {
if (mBuilder.mStartOffset > 0 && mBuilder.mStartOffset < file.length()) {
file.seek(mBuilder.mStartOffset);
} else {
file.seek(file.length() - 1);
mBuilder.mStartOffset = file.length() - 1;
}
}
//设置请求参数
callRequest(mBuilder.mStartOffset);
if (mCall == null) {
mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, "CONNECTION ERROR!");
return;
}
//建立连接
Response res = mCall.execute();
if (!res.isSuccessful()) {
mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, "CONNECTION ERROR!");
return;
}
//更新文件长度
mBuilder.mLengthChangeListenner.onChanged(Listenner.STATUS_LENGTH_CHANGE, mBuilder.mFileAllLength + "", (mBuilder.mStartOffset + mDownloadLength) + "");
//开启进度刷新线程
mDaemonMsgThread.start();
//保存文件
boolean ret = saveData(res.body().byteStream(), new FileOutputStream(file.getFD()));
if (ret) {
mBuilder.mSuccessListenner.onChanged(Listenner.STATUS_SUCCESS, mBuilder.mSavePath,mBuilder.mFileAllLength + "");
} else {
mBuilder.mStopListenner.onChanged(Listenner.STATUS_STOP, mBuilder.mSavePath, mBuilder.mFileAllLength + "", (mBuilder.mStartOffset + mDownloadLength) + "");
}
mBuilder.mLengthChangeListenner.onChanged(Listenner.STATUS_LENGTH_CHANGE, mBuilder.mFileAllLength + "", (mBuilder.mStartOffset + mDownloadLength) + "");
}

/***
* 获取文件在服务器的长度,head请求
* @return
* @throws IOException
*/
private long getNetFileSize() throws IOException {
Request request = getRequestBuilder(0)
.head()
.build();
Call call = mClient.newCall(request);
if (call == null) {
mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, "CONNECTION ERROR,Get File Size!");
return -1;
}

Response response = call.execute();
if (!response.isSuccessful()) {
mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, "CONNECTION ERROR,Get File Size!");
return -1;
}

Headers headers = response.headers();
long fileSize = 0;
try {
fileSize = Long.parseLong(headers.get("Content-Length"));
} catch (Exception e) {
fileSize = -1;
}
return fileSize;
}

/***
* 执行请求
* @param offset 起始下载偏移量,该偏移量以当前已经下载的文件长度为准
*/
private void callRequest(long offset) {
Request request = getRequestBuilder(offset)
.build();
mCall = mClient.newCall(request);
}

/***
* 初始化请求参数
* @param offset 起始下载偏移量
* @return
*/
private Request.Builder getRequestBuilder(long offset) {
Headers.Builder builder = new Headers.Builder();
//header
if (mBuilder.mHeaders != null) {
for (String k : mBuilder.mHeaders.keySet()) {
builder.set(k, mBuilder.mHeaders.get(k));
}
}
if (builder.get("Range") == null) {
builder.set("Range", "bytes=" + offset + "-");
}
Headers headers = builder.build();
//urlparams url后面的参数
if (mBuilder.mUrlParams != null && !mBuilder.mUrlParams.isEmpty()) {
if (!mBuilder.mUrl.contains("?")) {
mBuilder.mUrl += "?";
}
for (String k : mBuilder.mUrlParams.keySet()) {
mBuilder.mUrl += k + "=" + mBuilder.mUrlParams.get(k);
mBuilder.mUrl += "&";
}
}
Request.Builder reBuilder = new Request.Builder()
.url(mBuilder.mUrl)
.headers(headers);
return reBuilder;
}

/***
* 保存文件
* @param is 源输入流
* @param os 目标输出流
* @return 如果是下载完成返回true,如果停止导致返回false
* @throws IOException
*/
private boolean saveData(InputStream is, OutputStream os) throws IOException {
Source source = Okio.source(is);
Sink sink = Okio.sink(os);
Buffer buf = new Buffer();
long len = 0;

while ((len = source.read(buf, MAX_BUFF_SIZE)) != -1 && !isExit) {
sink.write(buf, len);
mDownloadLength += len;
}

sink.flush();
sink.close();
source.close();
return !isExit;
}

/***
* 刷新当前下载进度
*/
private synchronized void changeSpeed() {
long endTime = System.currentTimeMillis();
mBuilder.mSpeedChangeListenner.onChanged(Listenner.STATUS_SPEED_CHANGE, mBuilder.mFileAllLength + "", (mBuilder.mStartOffset + mDownloadLength) + "",(endTime - mStartTime) + "", mDownloadLength + "");
}

/**
* 用于在一个新的线程中刷新下载进度,防止回调中有耗时操作阻塞下载线程影响下载速度
*/
private final class DaemonThead extends Thread {
//是否开启
private boolean isRunning = true;

@Override
public void run() {
while (isRunning) {
//防止下载完成以及其他状态回调时,进度还在刷新
synchronized (mBuilder) {
changeSpeed();
try {
Thread.sleep(mBuilder.mSpeedRefreshHZ);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

/***
* Listtenner 的简单封装,可以简单的得到状态回调中的数值
*/
public static abstract class SimpleDownLoadListenner implements Listenner {

@Override
public void onChanged(int code, String... value) {
switch (code) {
case Listenner.STATUS_ERROR: {
onDownloadError(value[0], value[1]);
}
break;
case Listenner.STATUS_SUCCESS: {
onDownloadSuccessful(value[0],getLongValue(value[1]));
}
break;
case Listenner.STATUS_SPEED_CHANGE: {
onDownloadSpeedChanged(getLongValue(value[0]), getLongValue(value[1]), getLongValue(value[2]),getLongValue(value[3]));
}
break;
case Listenner.STATUS_STOP: {
onDownloadStoped(value[0], getLongValue(value[1]), getLongValue(value[2]));
}
break;
case Listenner.STATUS_LENGTH_CHANGE: {
onDownloadLengthChanged(getLongValue(value[0]), getLongValue(value[1]));
}
break;
}
}

private long getLongValue(String value) {
try {
return Long.parseLong(value);
} catch (Exception e) {
return 0;
}
}

public void onDownloadSuccessful(String path,long allFileLength) {

}

public void onDownloadStoped(String path, long allLength, long allDownloadlength) {

}

public void onDownloadError(String path, String msg) {

}

public void onDownloadLengthChanged(long allLength, long allDownloadLength) {

}

public synchronized void onDownloadSpeedChanged(long allLength,long allDownloadLenght , long times, long nowDownloadLength) {

}
}

/***
* BRDownloadThread以Builder模式创建,不允许new
*/
public static class Builder implements Listenner {
private Map<String, String> mUrlParams;        //url后的参数
private Map<String, String> mHeaders;          //请求头
private long mFileAllLength;                   //文件总长度
private long mStartOffset = 0;                 //起始下载偏移量
private String mUrl;                           //请求地址
private String mSavePath;                      //保存路径(全路径)
private String mBasePath;                      //保存文件夹名称
private String mSaveName;                      //文件名称
private Listenner mSuccessListenner;           //成功回调
private Listenner mErrorListenner;             //失败回调
private Listenner mLengthChangeListenner;      //文件长度变化回调(只在获取到服务器文件大小时回调,以及线程终止后回调(包括完成和停止))
private Listenner mSpeedChangeListenner;       //进度刷新 刷新频率为(1/mSpeedRefreshHZ)
private Listenner mStopListenner;              //停止回调,只在下载手动终止时回调
private long mSpeedRefreshHZ = 500;            //进度刷新间隔 毫秒
private boolean isAutoRename = false;          //是否支持文件重命名,如果本地已存在 false 不再重复下载 true 重命名后继续下载

public Builder setAutoRename(boolean auto) {
isAutoRename = auto;
return this;
}

/***
* 设置进度刷新间隔
* @param times 间隔时间 ms
* @return
*/
public Builder setDaemonRefreshDelayTime(long times) {
mSpeedRefreshHZ = times;
return this;
}

/***
* 设置保存的的全路径
* @param path
* @return
*/
public Builder setSavePath(String path) {
mSavePath = path;
return this;
}

/**
* 设置保存文件夹名称
* @param basePath 文件夹名称
* @return
*/
public Builder setBasePath(String basePath) {
mBasePath = basePath;
return this;
}

/***
* 保存文件名
* @param name 文件名
* @return
*/
public Builder setSaveName(String name) {
mSaveName = name;
return this;
}

/***
* 请求地址
* @param url
*/
public Builder(String url) {
mUrl = url;
}

public Builder() {

}

/***
* 设置状态监听回调
* @param l
* @return
*/
public Builder setListenners(Listenner l) {
mSuccessListenner = l;
mErrorListenner = l;
mLengthChangeListenner = l;
mSpeedChangeListenner = l;
mStopListenner = l;
return this;
}

/***
* 成功监听
* @param l
* @return
*/
public Builder setSuccessListenner(Listenner l) {
mSuccessListenner = l;
return this;
}

/***
* 失败监听
* @param l
* @return
*/
public Builder setErrorListenner(Listenner l) {
mErrorListenner = l;
return this;
}

public Builder setStopListenner(Listenner l) {
mStopListenner = l;
return this;
}

/***
* 长度变化监听
* @param l
* @return
*/
public Builder setLengthChangeListenner(Listenner l) {
mLengthChangeListenner = l;
return this;
}

/***
* 进度刷新监听
* @param l
* @return
*/
public Builder setOnSpeedListenner(Listenner l) {
mSpeedChangeListenner = l;
return this;
}

/***
* 设置起始下载偏移量和文件长度
* @param offset 偏移量 首次下载为 0,该值只作为参考实际已文件长度为准
* @param fileAllLength 文件长度,可有可无
* @return
*/
public Builder setOffset(long offset, long fileAllLength) {
mStartOffset = offset;
mFileAllLength = fileAllLength;
return this;
}

public Builder setUrl(String url) {
mUrl = url;
return this;
}

/***
* 请求头(如果header头中包含Range 则会替换默认的Range属性,建议不要包含)
* @param headers
* @return
*/
public Builder setHeaders(Map<String, String> headers) {
mHeaders = headers;
return this;
}

/***
* 请求头
* @param headers
* @return
*/
public synchronized Builder addHeaders(Map<String, String> headers) {
if (mHeaders == null) {
mHeaders = headers;
} else {
mHeaders.putAll(headers);
}
return this;
}

/***
* 请求头
* @param k
* @param v
* @return
*/
public synchronized Builder addHeader(String k, String v) {
if (mHeaders == null) {
mHeaders = new HashMap<>();
}
return this;
}

/***
* 如果url后面包含参数,可以使用该方法为url添加参数
* @param params
* @return
*/
public synchronized Builder setUrlParams(Map<String, String> params) {
mUrlParams = params;
return this;
}

/***
* url参数
* @param params
* @return
*/
public synchronized Builder addUrlParams(Map<String, String> params) {
if (mUrlParams == null) {
mUrlParams = params;
} else {
mUrlParams.putAll(mUrlParams);
}
return this;
}

/**
* url 参数
* @param k
* @param v
* @return
*/
public synchronized Builder addUrlParams(String k, String v) {
if (mUrlParams == null) {
mUrlParams = new HashMap<>();
}
mUrlParams.put(k, v);
return this;
}

public BRDownloadThread build() throws RuntimeException {

if (mErrorListenner == null) {
mErrorListenner = this;
}

if (mSpeedChangeListenner == null) {
mSpeedChangeListenner = this;
}

if (mSuccessListenner == null) {
mSuccessListenner = this;
}

if (mStopListenner == null) {
mStopListenner = this;
}

if (mLengthChangeListenner == null) {
mLengthChangeListenner = this;
}

//文件保存路径错误
if (TextUtils.isEmpty(mSavePath) && (TextUtils.isEmpty(mBasePath)
|| TextUtils.isEmpty(mSaveName))) {
throw new RuntimeException("can not build BRDownloadthread,because savepath or (saveBasePasth and saveName is null)!");
}

//起始下载偏移量大于文件长度
if (mStartOffset > mFileAllLength) {
throw new RuntimeException("can not continue to download form startOffset,Because file length is null!");
}

if (TextUtils.isEmpty(mSavePath) && !TextUtils.isEmpty(mBasePath) && !TextUtils.isEmpty(mSaveName)) {
mSavePath = mBasePath + File.separator + mSaveName;
}

BRDownloadThread thread = new BRDownloadThread();
thread.mBuilder = this;
return thread;
}

@Override
public void onChanged(int code, String... value) {

}
}
}

示例:

mDownloadThread = new BRDownloadThread.Builder(downloadPath)
.setSavePath(savePasth)
.setOffset(0,0)
.setListenners(new BRDownloadThread.SimpleDownLoadListenner() {
@Override
public void onChanged(int code, String... value) {
switch (code) {
case Listenner.STATUS_ERROR: {
LogUtil.i("ERROR == > " + getString(value));
}break;
case Listenner.STATUS_LENGTH_CHANGE: {
LogUtil.i("LENGTH == >" + getString(value));
}break;
case Listenner.STATUS_SPEED_CHANGE: {
LogUtil.i("SPEED == >" + getString(value));
}break;
case Listenner.STATUS_STOP: {
LogUtil.i("STOP == >" + getString(value));
}break;
case Listenner.STATUS_SUCCESS: {
LogUtil.i("SUCCESSFUL == >" + getString(value));
}break;
}
}
})
.build();
mDownloadThread.start();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐