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();