多线程断点继续下载,在很多互联网APP中经常使用。最近在项目中也用到了,总结下实现的方法。
1,创建清单文件记录断点
2,创建一个文件存储下载内容
3,确定用几个线程下载,分出每个线程开始结束位置
4,创建下载线程
5,根据需求考虑使用哪种线程池
6,通过接口监听线程开始,下载进度(用于记录断点),下载完成。下载错误等
上面的就是多线程断点继续下载的流程。
创建清单文件记录断点:这里可以有很多方法,可以用文件存储,以json格式存储下来以便于解析。也可以用数据库存储这要看自己觉得怎么简单怎么用了。
创建一个文件存储下载内容:这里可以创建一个临时文件等文件全部下载完成以后再重命名。
创建下载线程:这里要说的就是下载文件的拼接,由于我们把文件分成了好几段来下载怎么把下载的文件组成一个文件,这里就要用到RandomAccessFile类,
因为RandomAccessFile类中有个seek(long offset)方法它可以指定从哪个位置读写,这样就可以解决文件组合了。
下面来来看代码:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
/**
* 线程管理类负责线程创建
*/
@SuppressLint("HandlerLeak")
public class DownloadTaskThread {
private ExecutorService fixedThreadPool;
private long[] startPos = new long[3];
private long[] endPos = new long[3];
private long fristPos = 0;
private long secondPos = 0;
private long thirdPos = 0;
private long iFileSize;
private String downloadFilePath = null;
private String mDownloadUrl = "";
private boolean isFrist = true;
private long lCurrentDownloadSize = 0;
private int isCompelet = 0;
private Handler handler;
private long blockSize;
private boolean isContinue;
/**
* @param url
* 下载的URL
* @param filepath
* 下载的文件路径
* @param filesize
* 下载文件总大小
* @param filepath
* 保存下载的文件路径
* @param handler
* 这里用handler为了把下载的信息传递到调用它的类中归总处理
* @param isContinue
* 是否是点断继续下载
*/
public DownloadTaskThread(String url, String filepath, long filesize, Handler handler,boolean isContinue) {
this.downloadFilePath = filepath;
this.mDownloadUrl = url;
this.iFileSize = filesize;
this.handler = handler;
blockSize = iFileSize / 3;//这里我分了3段来下载,用了3个线程,多了会影响手机的cpu
startDownload();
}
private void startDownload() {
isFrist = true;
fixedThreadPool = Executors.newFixedThreadPool(3);//这里用的是线程同步
if(!isContinue){
getDownloadPosition();
}
for (int i = 0; i < 3; i++) {
fixedThreadPool.execute(new DownloadThread(startPos[i], endPos[i], mDownloadUrl, downloadFilePath,
callback, i));
}
}
/**
* 获取加载的位置
*/
private void getDownloadPosition() {
for (int i = 0; i < 3; i++) {
if (i == 0) {
startPos[i] = blockSize * i ;
} else {
startPos[i] = blockSize * i + 1;
}
if (i == 2) {
endPos[i] = iFileSize;
} else {
endPos[i] = blockSize * (i + 1);
}
}
}
/**
* 断点继续下载(通过清单文件获取点断位置)
*/
public void getContinuePosition(long frist,long second,long third) {
startPos[0] = frist ;
startPos[1] = second ;
startPos[2] = third ;
for (int i = 0; i < 3; i++) {
if (i == 2) {
endPos[i] = iFileSize;
} else {
endPos[i] = blockSize * (i + 1);
}
}
}
//返回值得接口
private DownloadCallback callback = new DownloadCallback() {
@Override
public void downloadStart(int Id) {//开始下载,Id表示第几个线程
if (Id == 0) {
if (isFrist) {
isFrist = false;
Message message = new Message();
message.what = DDHandler.DOWNLOAD_START;//这个静态常量区分状态
handler.sendMessage(message);
}
}
}
@Override
public void downloadProgress(long current, int Id) {//current下载进度,Id表示第几个线程
updataProgress(Id, current);
}
@Override
public void downloadSucess() {//下载完成
downloadSuccess();
}
@Override
public void downloadFailure(String msg, int Id) {//强行终止
Message message = new Message();
message.what = DDHandler.DOWNLOAD_FAILED;
message.obj = msg;
handler.sendMessage(message);
}
@Override
public void downloadInterrupt() {//加载错误
handler.sendEmptyMessage(DDHandler.DOWNLOAD_INTERRUPT);
}
@Override
public void playerStoped() {
}
};
private void downloadSuccess() {
isCompelet++;
if (isCompelet == 3) {
isCompelet = 0;
handler.sendEmptyMessage(DDHandler.DOWNLOAD_SUCCESS);
}
}
private void updataProgress(int arg1, long current) {
switch (arg1) {
case 0:
fristPos = current;//第一个线程的下载进度
break;
case 1:
secondPos = current;//第二个线程的下载进度
break;
case 2:
thirdPos = current;//第三个线程的下载进度
break;
}
currentDownloadSize();
}
private void currentDownloadSize() {
if (fristPos < blockSize) {//如果第一段没下完就显示段进度(第二段什么不加上第一段呢,因为返回的数据中包括了第一段了)
lCurrentDownloadSize = fristPos;
} else if (secondPos < blockSize * 2) {
lCurrentDownloadSize = secondPos;
} else {
lCurrentDownloadSize = thirdPos;
}
Message message = new Message();
message.what = DDHandler.DOWNLOAD_PROGRESS;
message.obj = lCurrentDownloadSize;
handler.sendMessage(message);
}
public void stopExecutors() {//停止线程池
if (fixedThreadPool != null) {
fixedThreadPool.shutdown();
fixedThreadPool = null;
}
}
}
下载线程代码如下:
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.CountDownLatch;
import android.annotation.SuppressLint;
@SuppressLint("HandlerLeak")
public class DownloadThread extends Thread {
private final int LENGTH = 102400;
private long startPos;
private long endPos;
private long compeleteSize;
private String urlstr;
private boolean mIsContinue = true;
private String downloadPath = null;
private DownloadThreadManager taskManager;
private int count = 0;// 重新下载次数,3次后就不下载
private int threadId;
private DownloadCallback callback = null;
/**
* @param startPos
* 下载的起始位置
* @param block
* 每段下载的长度
* @param urlstr
* 下载链接
* @param filepath
* 保存下载的文件路径
* @param callback
* 回调接口
* @param threadId
* 线程Id
*/
public DownloadThread(long startPos, long endPos, String urlstr,
String filepath, DownloadCallback callback, int threadId) {
this.startPos = startPos;
this.endPos = endPos;
this.urlstr = urlstr;
this.downloadPath = filepath;
this.callback = callback;
this.threadId = threadId;
//线程管理类便于停止线程下载
taskManager = DownloadThreadManager.getInstance();
}
@Override
public void run() {
//添加到线程管理类便于停止线程下载
taskManager.addTask(this);
startDownload();
//移除线程下载
taskManager.removeTask(this);
}
private void startDownload() {
RandomAccessFile threadfile = null;
InputStream inStream = null;
HttpURLConnection http = null;
try {
URL downUrl = new URL(urlstr);
http = (HttpURLConnection) downUrl.openConnection();
http.setConnectTimeout(10 * 1000); // 10秒超时连接
http.setRequestProperty("Accept-Encoding", "musixmatch");
http.setRequestProperty(
"Accept",
"image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
http.setRequestProperty("Accept-Language", "zh-CN");
http.setRequestProperty("Referer", downUrl.toString());
http.setRequestProperty("Charset", "UTF-8");
http.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);// 设置获取实体数据的范围
http.setRequestProperty(
"User-Agent",
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
http.setRequestProperty("Connection", "Keep-Alive");
http.connect();
long iFileSize = http.getContentLength();
if (iFileSize < 10240) {
iFileSize = 0;
}
downloadStart();
inStream = http.getInputStream();
byte[] buffer = new byte[LENGTH];
int offset = 0;
threadfile = new RandomAccessFile(downloadPath, "rwd");//读写类
threadfile.seek(startPos);//指定位置读写
compeleteSize = startPos;
while ((offset = inStream.read(buffer, 0, LENGTH)) != -1
&& mIsContinue) {
threadfile.write(buffer, 0, offset);
compeleteSize += offset;
// 实时保存下载的进度到配置文件
if (mIsContinue) {
updateDownloadProgress(compeleteSize);
}
}
if (!mIsContinue) {
downloadInterrupt();
} else {
downloadSuccess();
}
threadfile.close();
inStream.close();
http.disconnect();
} catch (Exception e) {
try {
if (threadfile != null) {
threadfile.close();
}
if (inStream != null) {
inStream.close();
}
if (http != null) {
http.disconnect();
}
} catch (IOException e2) {
e2.printStackTrace();
}
count++;
if (e.toString().contains("recvfrom failed")) {
if (count > 10) {
count = 0;
downloadFailed("网络连接失败");
} else {
retryAgain();
}
} else {
if (count > 10) {
count = 0;
downloadFailed("下载失败");
} else {
retryAgain();
}
}
}
}
private void retryAgain() {
try {
sleep(2000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
startDownload();
}
private void downloadStart() {
if (this.callback != null) {
this.callback.downloadStart(threadId);
}
}
private void updateDownloadProgress(long current) {
if (this.callback != null) {
this.callback.downloadProgress(current, threadId);
}
}
private void downloadFailed(String msg) {
taskManager.removeTask(this);
if (this.callback != null) {
this.callback.downloadFailure(msg, threadId);
}
}
private void downloadSuccess() {
if (this.callback != null) {
this.callback.downloadSucess();
}
}
private void downloadInterrupt() {
if (this.callback != null) {
this.callback.downloadInterrupt();
}
}
public void stopDownload() {
this.mIsContinue = false;
}
}
核心代码大致只是这些了,注释也很清楚,可以自己去扩展