package com.dl7.downloaderlib.service;
import android.os.Process;
import android.os.SystemClock;
import android.util.Log;
import com.dl7.downloaderlib.DownloadListener;
import com.dl7.downloaderlib.db.FileDAOImpl;
import com.dl7.downloaderlib.entity.FileInfo;
import com.dl7.downloaderlib.exception.DownloadException;
import com.dl7.downloaderlib.model.DownloadStatus;
import com.dl7.downloaderlib.model.FileUtils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.util.Locale;
import okhttp3.CacheControl;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* Created by long on 2016/5/25.
* 下载线程
*/
public class DownloadTask implements Runnable {
private final static String TAG = "DownloadTask";
private static final int MIN_PROCESS_STEP = 65535;
private static final int MIN_PROCESS_TIME = 2000;
private static final int CALC_SPEED_TIME = 500;
private static final int BUFFER_SIZE = 1024 * 4;
private final OkHttpClient mClient;
private FileInfo mFileInfo;
private int mRetryTimes;
private final DownloadListener mListener;
private boolean mIsResumeAvailable = false;
private boolean mIsRunning = false;
private boolean mIsCancel = false;
private boolean mIsStop = false;
private boolean mIsRetry = false;
private int mLastUpdateBytes;
private long mLastUpdateTime;
private int mLastCalcBytes;
private long mLastCalcTime;
public DownloadTask(OkHttpClient client, FileInfo fileInfo, int retryTimes, DownloadListener listener) {
this.mClient = client;
this.mFileInfo = fileInfo;
this.mRetryTimes = retryTimes;
this.mListener = listener;
}
public void setRetry(boolean retry) {
mIsRetry = retry;
}
@Override
public void run() {
mIsRunning = true;
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 1.检测是否存在未下载完的文件
_checkIsResumeAvailable();
Response response = null;
Call call = null;
try {
// 2.构建请求
Request.Builder requestBuilder = new Request.Builder().url(mFileInfo.getUrl());
requestBuilder.addHeader("Range", String.format(Locale.ENGLISH, "bytes=%d-", mFileInfo.getLoadBytes()));
// 目前没有指定cache,下载任务非普通REST请求,用户已经有了存储的地方
requestBuilder.cacheControl(CacheControl.FORCE_NETWORK);
// 3.初始化请求
Request request = requestBuilder.build();
call = mClient.newCall(request);
// 4.执行请求
response = call.execute();
final boolean isSucceedStart = response.code() == HttpURLConnection.HTTP_OK;
final boolean isSucceedResume = response.code() == HttpURLConnection.HTTP_PARTIAL;
if (isSucceedResume || isSucceedStart) {
int total = mFileInfo.getTotalBytes();
final String transferEncoding = response.header("Transfer-Encoding");
// 5.获取文件长度
if (isSucceedStart || total <= 0) {
if (transferEncoding == null) {
total = (int) response.body().contentLength();
} else {
// if transfer not nil, ignore content-length
total = -1;
}
mFileInfo.setTotalBytes(total);
}
if (total < 0) {
throw new DownloadException("Get content length error!");
}
// 6.网络状态已连接
_onConnected();
// 7.开始获取数据
_onDownload(response);
} else {
if (!_onRetry()) {
mListener.onError(mFileInfo, "Numeric status code is error!");
_updateDb();
}
}
} catch (Throwable e) {
if (e instanceof DownloadException) {
if (_onRetry()) {
return;
}
}
mListener.onError(mFileInfo, e.toString());
_updateDb();
} finally {
if (response != null && response.body() != null) {
response.body().close();
}
if (call != null) {
call.cancel();
}
}
}
/**
* 重试处理
*
* @return 是否进行处理
*/
private boolean _onRetry() {
mIsRunning = false;
if (mRetryTimes > 0) {
mRetryTimes--;
DownloadTask runnable = new DownloadTask(mClient, mFileInfo, mRetryTimes, mListener);
runnable.setRetry(true);
DownloadThreadPool.getInstance().execute(runnable);
return true;
}
return false;
}
/**
* 开始下载
*
* @param response 应答
*/
private void _onDownload(Response response) throws Throwable {
InputStream inputStream = null;
final RandomAccessFile accessFile = FileUtils.getRandomAccessFile(
mFileInfo.getPath() + mFileInfo.getName() + ".tmp",
mFileInfo.getLoadBytes(), mFileInfo.getTotalBytes());
int loadBytes = mFileInfo.getLoadBytes();
try {
// 1.获取数据流
inputStream = response.body().byteStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream, BUFFER_SIZE);
byte[] buff = new byte[BUFFER_SIZE];
do {
// 2.读数据
int byteCount = bufferedInputStream.read(buff);
if (byteCount == -1) {
break;
}
// 3.写文件
accessFile.write(buff, 0, byteCount);
// 4.累加下载量
loadBytes += byteCount;
// 5.检测文件是否被其他操作
if (accessFile.length() < loadBytes) {
mIsRunning = false;
throw new RuntimeException(
FileUtils.formatString("the file was changed by others when" +
" downloading. %d %d", accessFile.length(), loadBytes));
} else {
mFileInfo.setLoadBytes(loadBytes);
_onProcess();
}
// 6.检测停止状态
if (mIsCancel || mIsStop) {
// callback on paused
mIsRunning = false;
if (mIsCancel) {
mListener.onCancel(mFileInfo);
} else {
mListener.onStop(mFileInfo);
}
_updateDb();
return;
}
} while (mIsRunning);
// 7.处理 transfer encoding = chunked 的情况
if (mFileInfo.getTotalBytes() == -1) {
mFileInfo.setTotalBytes(loadBytes);
}
// 8.判断下载成功还是失败
if (mFileInfo.getLoadBytes() == mFileInfo.getTotalBytes()) {
_onComplete();
} else {
throw new DownloadException(FileUtils.formatString(
"Unfinished: load[%d] is not equal total[%d]!",
mFileInfo.getLoadBytes(), mFileInfo.getTotalBytes()));
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (accessFile != null) {
accessFile.close();
}
}
}
/**
* 下载完成
*/
private void _onComplete() {
// 重命名文件
File tmpFile = new File(mFileInfo.getPath(), mFileInfo.getName() + ".tmp");
File appFile = new File(mFileInfo.getPath(), mFileInfo.getName());
tmpFile.renameTo(appFile);
mListener.onComplete(mFileInfo);
// // 下载完就从数据库删除
// FileDAOImpl.getInstance().delete(mFileInfo.getUrl());
_updateDb();
}
/**
* 更新进度
*/
private void _onProcess() {
if (mFileInfo.getLoadBytes() == mFileInfo.getTotalBytes()) {
// 下载完成
mIsRunning = false;
return;
}
long now = SystemClock.uptimeMillis();
if (mLastCalcTime == 0) {
mLastCalcTime = now;
mLastCalcBytes = mFileInfo.getLoadBytes();
}
long diffBytes;
long diffTimes = now - mLastCalcTime;
if (diffTimes > CALC_SPEED_TIME) {
diffBytes = mFileInfo.getLoadBytes() - mLastCalcBytes;
int speed = (int) (diffBytes * 1000 / diffTimes);
mFileInfo.setSpeed(speed);
mLastCalcTime = now;
mLastCalcBytes = mFileInfo.getLoadBytes();
}
mListener.onUpdate(mFileInfo);
diffBytes = mFileInfo.getLoadBytes() - mLastUpdateBytes;
diffTimes = now - mLastUpdateTime;
if (diffBytes > MIN_PROCESS_STEP && diffTimes > MIN_PROCESS_TIME) {
_updateDb();
mLastUpdateBytes = mFileInfo.getLoadBytes();
mLastUpdateTime = now;
}
}
/**
* 连接已建立,准备下载
*/
private void _onConnected() {
mIsRunning = true;
if (!mIsRetry) {
// 如果为异常重试则不进行回调
mListener.onStart(mFileInfo);
}
if (!mIsResumeAvailable) {
// 插入数据库
mIsResumeAvailable = true;
DownloadThreadPool.getInstance().update(new Runnable() {
@Override
public void run() {
FileDAOImpl.getInstance().insert(mFileInfo);
}
});
}
}
/**
* 检测是否存在资源
*/
private void _checkIsResumeAvailable() {
FileInfo info = FileDAOImpl.getInstance().query(mFileInfo.getUrl());
if (info != null) {
mIsResumeAvailable = true;
if (info.getStatus() != DownloadStatus.COMPLETE && info.getLoadBytes() != info.getTotalBytes()) {
mFileInfo = info;
}
}
}
/**
* 判断是否正在执行
*
* @return
*/
public boolean isRunning() {
return mIsRunning;
}
/**
* 线程的标识
*
* @return
*/
public String tag() {
return mFileInfo.getUrl();
}
/**
* 取消
*/
public void cancel() {
mIsCancel = true;
}
/**
* 停止
*/
public void stop() {
mIsStop = true;
}
/**
* 更新数据库
*/
private void _updateDb() {
DownloadThreadPool.getInstance().update(new Runnable() {
@Override
public void run() {
FileDAOImpl.getInstance().update(mFileInfo);
}
});
}
}