多线程下载
时间:2022-04-24
本文章向大家介绍多线程下载,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
楼主三年磨剑(当然不是磨着一把剑),倾血奉献Android多线程下载Demo。有的人就问了“怎么写来写去还是Demo?”,因为老哥我实在太忙了, 每天写一点,写到现在也才写了个下载器,也就差下载管理类就是个完整的模块了。对于新手学习这已经足够了,不对,是完全足够了。
这不仅仅只是一个简单的Demo,这绝对是你前所未见的商业级别的范例,集支持多线程下载,断点续传,只使用wifi网络下载,显示下载速度,人性化提示 及超强的容错机制多功能于一体,绝对的实用,绝对的专业。
当然我写这个是为了下载apk的,大家稍微改一改就可以写成更通用的下载器。唯一有点不足的地方就是在Android上使用RandomAccessFile在创建大文件的时候 速度有些慢,导致前几秒的进度都为0。不知道有没有人可以帮我解决这个问题。
下面给出关键代码。
package com.h3c.DownloadEngine;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import com.h3c.DownloadEngine.common.DownloaderErrorException;
import com.h3c.DownloadEngine.common.EngineConstants;
import com.h3c.DownloadEngine.common.EngineUtil;
import com.h3c.DownloadEngine.common.EngineVariable;
import com.h3c.DownloadEngine.db.EngineDBOperator;
import com.h3c.DownloadEngine.entity.DownloadBean;
public class Downloader {
private final static String TAG = "Downloader";
private final static byte[] lock_getFileSize = new byte[1];
private final static byte[] lock_refresh_progress = new byte[1];
private int mThreadCount = 4;// 默认子线程数为4个
private int bufferSize = 1024 * 16; // 16K 一个块
private DownloadBean mBean;// 注意,这里是dwonloader的bean不是其subBean
private Context mContext;
private DownloadEngineCallback mCallback;
private EngineDBOperator mDBOper;
private int mDoneThreadCount = 0;// 完成的线程数
private int mState = EngineConstants.DOWNLOAD_STATE_INIT;// 下载器状态
private ArrayList<DownloadBean> mBeans = new ArrayList<DownloadBean>(
mThreadCount);
public Downloader(DownloadBean bean, Context context,
DownloadEngineCallback callback) throws DownloaderErrorException {
this.mBean = bean;
this.mContext = context;
this.mCallback = callback;
this.mDBOper = EngineDBOperator.getInstance(context);
if (this.mDBOper != null) {
if (this.mDBOper.isHasDownloadTaskByUrl(bean.url)) {// 如果此任务已经存放进数据库
getDownloaderInfoFromDB(bean);
} else {// 插入信息至数据库
addDownloaderInfoToDB(bean);
}
} else {
callBackError("Downloader错误,可能是EngineDBOperator为Null.");
throw new DownloaderErrorException(
"Downloader错误,可能是EngineDBOperator为Null.");
}
}
public DownloadBean getDownloaderInfo() {
return mBean;
}
public int getDownloaderState() {
return mState;
}
/**
* 请求初始化
*
* @param state
*/
protected void setDownloaderState(int state) {
mState = state;
if (state == EngineConstants.DOWNLOAD_STATE_INIT) {
mBean.currentPosition = 0;
}
}
/**
* 加入下载信息进入数据库,此方法用于刚刚初始化Downloader,且数据库中没有该任务的时候
*
* @param bean
* @throws DownloaderErrorException
*/
private void addDownloaderInfoToDB(DownloadBean bean)
throws DownloaderErrorException {
if (mState != EngineConstants.DOWNLOAD_STATE_INIT
&& mState != EngineConstants.DOWNLOAD_STATE_STOP
&& mState != EngineConstants.DOWNLOAD_STATE_ERROR) {
callBackError("这个任务已经加入到数据库中了");
throw new DownloaderErrorException("这个任务已经加入到数据库中了");
}
if (mDBOper != null) {
long fileSize = bean.fileSize;
if (mBeans.size() > 0) {
mBeans.clear();
}
try {
if (fileSize > 0) {// 判断传入的fileSize大小,如果大于0,就不用从网络中获取,直接初始化N个子下载器
if (!hasSpaceInSDCard()) {
return;
}
long range = fileSize / mThreadCount;// 文件分段值
for (int i = 0; i < mThreadCount - 1; i++) {
DownloadBean subBean = (DownloadBean) bean.clone();
subBean.threadId = i;
subBean.startPosition = i * range;
subBean.endPosition = (i + 1) * range - 1;
mBeans.add(subBean);
}
DownloadBean subBean = (DownloadBean) bean.clone();
subBean.threadId = mThreadCount - 1;
subBean.startPosition = (mThreadCount - 1) * range;
subBean.endPosition = fileSize - 1;
mBeans.add(subBean);
} else {// 如果等于0,就直接初始化N个0大小的子下载器
for (int n = 0; n < mThreadCount - 1; n++) {
DownloadBean subBean = (DownloadBean) bean.clone();
subBean.threadId = n;
mBeans.add(subBean);
}
DownloadBean subBean = (DownloadBean) bean.clone();
subBean.threadId = mThreadCount - 1;
mBeans.add(subBean);
}
mDBOper.addDownloadTask(mBeans);
if (bean.fileSize > 0) {// 如果文件大小已经获取就进入等待状态
mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下载器进入等待状态
} else {// 文件大小未获取就开启线程去获取文件大小并更新子下载器中的内容
new Thread(new Runnable() {
@Override
public void run() {
boolean flag = false;
synchronized (lock_getFileSize) {
flag = getFileSizeByNetwork(mBean);
}
if (flag) {
mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下载器进入等待状态
} else {
Log.e(TAG, "从网络中获取文件大小失败 1");
}
}
}).start();
}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
} else {
callBackError("addDownloaderInfoToDB错误,可能是EngineDBOperator为Null.");
throw new DownloaderErrorException(
"addDownloaderInfoToDB错误,可能是EngineDBOperator为Null.");
}
}
/**
* 从数据库中读取下载器信息
*
* @param bean
* @throws DownloaderErrorException
*/
private void getDownloaderInfoFromDB(DownloadBean bean)
throws DownloaderErrorException {
if (mDBOper != null) {
mBeans.clear();
mBeans = mDBOper.getDownloadTaskByUrl(bean.url);
mBean.currentPosition = 0;
mBean.fileSize = 0;
mThreadCount = mBeans.size();
for (DownloadBean subBean : mBeans) {
mBean.currentPosition += subBean.currentPosition;
if (subBean.fileSize > mBean.fileSize) {
mBean.fileSize = subBean.fileSize;
}
}
if (mBean.fileSize < 1) {
new Thread(new Runnable() {
@Override
public void run() {
boolean flag = false;
synchronized (lock_getFileSize) {
flag = getFileSizeByNetwork(mBean);
}
if (flag) {
mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下载器进入等待状态
} else {
Log.e(TAG, "从网络中获取文件大小失败 2");
}
}
}).start();
} else {
mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下载器进入等待状态
}
} else {
callBackError("getDownloaderInfoFromDB Error,May be EngineDBOperator is Null.");
throw new DownloaderErrorException(
"getDownloaderInfoFromDB Error,May be EngineDBOperator is Null.");
}
}
/**
* 从网络中获取文件大小,并更新listBeans
*/
private boolean getFileSizeByNetwork(DownloadBean bean) {
HttpURLConnection connection = null;
long fileSize = bean.fileSize;
try {
if (fileSize <= 0) {// 如果没有传入文件大小就从网络中获取
URL url = new URL(bean.url);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(8000);
if (android.os.Build.VERSION.SDK_INT > 10) {// 规避2.x上因为加入setRM导致连接超时的bug
connection.setRequestMethod("HEAD");// head
}
int resopnseCode = connection.getResponseCode();
if (resopnseCode != 200 && resopnseCode != 206) {
callBackError("http返回码不正确:" + resopnseCode);
return false;
}
// 获得文件大小
fileSize = connection.getContentLength();
mBean.fileSize = fileSize;
if (fileSize <= 0) {
callBackError("无法从服务器上获得文件大小" + fileSize);
return false;
}
// if (connection.getHeaderField("Content-Range") == null) {
// Log.e(TAG, "服务器不支持断点续传");
// mThreadCount = 1;
// }
// 如果没有存储空间了
if (!hasSpaceInSDCard()) {
return false;
}
long range = fileSize / mThreadCount;// 文件分段值
// 更新listBean
for (int i = 0; i < mThreadCount - 1; i++) {
DownloadBean subBean = mBeans.get(i);
subBean.fileSize = fileSize;
subBean.startPosition = i * range;
subBean.endPosition = (i + 1) * range - 1;
}
DownloadBean subBean = mBeans.get(mThreadCount - 1);
subBean.fileSize = fileSize;
subBean.startPosition = (mThreadCount - 1) * range;
subBean.endPosition = fileSize - 1;
// 更新数据库
if (mDBOper != null) {
mDBOper.updateTaskCompleteSize(mBeans, mBean.url);
} else {
callBackError("getFileSizeByNetwork错误,可能是EngineDBOperator is Null.");
throw new DownloaderErrorException(
"getFileSizeByNetwork错误,可能是EngineDBOperator is Null.");
}
return true;
} else {// 文件有大小就直接退出
return true;
}
} catch (Exception e) {
callBackError("从服务器获取文件大小超时");
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return false;
}
/**
* 开始下载,可能多次调用
*/
public void startDownloader() {
if (mState == EngineConstants.DOWNLOAD_STATE_DOWNLOADING) {// 如果正在下载就return
return;
}
if (mBean == null) {
callBackError("下载器没有初始化");
return;
}
File file = new File(mBean.savePath);
File parentDirectory = file.getParentFile();
if (!parentDirectory.exists()) {
parentDirectory.mkdirs();
}
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
if (mBeans.size() < 1) {// 防止由于发生错误导致清空了mBeans列表,但是又重新开始了任务,所有要再次初始化mBeans
try {
addDownloaderInfoToDB(mBean);
} catch (DownloaderErrorException e) {
e.printStackTrace();
return;
}
}
/**
* 只有获得文件大小后才会开始下载
*/
synchronized (lock_getFileSize) {
if (mState == EngineConstants.DOWNLOAD_STATE_INIT) {// 获取文件大小失败,重新获取
boolean flag = getFileSizeByNetwork(mBean);
if (!flag) {
callBackError("获取文件大小失败");
return;
}
}
}
mState = EngineConstants.DOWNLOAD_STATE_DOWNLOADING;
mDBOper.removePauseFileByUrl(mBean.url);// 从暂停列表中移除
mDoneThreadCount = 0;// 初始化完成线程数
for (DownloadBean bean : mBeans) {
if (bean.currentPosition < (bean.endPosition - bean.startPosition)) {// 如果该线程属于没有下载完成的
HamalThread hamalThread = new HamalThread(bean);
hamalThread.start();
} else {// 已经完成的线程不需要重新创建
mDoneThreadCount++;
}
}
if (mDoneThreadCount == mThreadCount) {// 下载完成
downloaderDone();
}
}
private class HamalThread extends Thread {
private int threadId;
private long startPos;
private long endPos;
private long compeleteSize;
private String urlstr;
public HamalThread(DownloadBean bean) {
this.threadId = bean.threadId;
this.startPos = bean.startPosition;
this.endPos = bean.endPosition;
this.compeleteSize = bean.currentPosition;
this.urlstr = bean.url;
}
@Override
public void run() {
HttpURLConnection connection = null;
RandomAccessFile randomAccessFile = null;
InputStream is = null;
try {
URL url = new URL(urlstr);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(8000);
connection.setRequestMethod("GET");
if (mThreadCount > 1) {// 多线程下载
// 设置范围,格式为Range:bytes x-y;
connection.setRequestProperty("Range", "bytes="
+ (startPos + compeleteSize) + "-" + endPos);
}
randomAccessFile = new RandomAccessFile(mBean.savePath, "rwd");
randomAccessFile.seek(startPos + compeleteSize);
// 将要下载的文件写到保存在保存路径下的文件中
is = connection.getInputStream();
byte[] buffer = new byte[bufferSize];
int length = -1;
EngineUtil eUtil = EngineUtil.getInstance();
if (EngineVariable.SUPPORT_NETWORK_TYPE == EngineConstants.DOWNLOAD_NETWORK_ONLYWIFI) {// 如果只能是3G下载
if (eUtil.getNetworkType() != EngineConstants.NETWORK_STATE_WIFI) {// 且当前网络不是Wifi
interruptDownloader();
return;
}
}
while ((length = is.read(buffer)) != -1) {
// 网络判断
if (EngineVariable.SUPPORT_NETWORK_TYPE == EngineConstants.DOWNLOAD_NETWORK_ONLYWIFI) {// 如果只能是3G下载
if (eUtil.getNetworkType() != EngineConstants.NETWORK_STATE_WIFI) {// 且当前网络不是Wifi
interruptDownloader();
return;
}
}
randomAccessFile.write(buffer, 0, length);
compeleteSize += length;
synchronized (lock_refresh_progress) {
mBean.currentPosition += length;
}
// 更新数据库中的下载信息
mDBOper.updateTaskCompleteSize(threadId, compeleteSize,
urlstr);
if (mState == EngineConstants.DOWNLOAD_STATE_PAUSE
|| mState == EngineConstants.DOWNLOAD_STATE_INTERRUPT
|| mState == EngineConstants.DOWNLOAD_STATE_STOP
|| mState == EngineConstants.DOWNLOAD_STATE_ERROR) {// 暂停
return;
}
}
// 该子线程下载完成
mDoneThreadCount++;
} catch (Exception e) {
Log.e(TAG, "下载途中断掉了连接...");
interruptDownloader();
e.printStackTrace();
} finally {
try {
is.close();
randomAccessFile.close();
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
if (mDoneThreadCount == mThreadCount) {
downloaderDone();
}
}
}
/**
* 获取下载进度
*
* @return
*/
public int getProgress() {
if (mBean.fileSize < 1) {
return 0;
}
return (int) (mBean.currentPosition * 100 / mBean.fileSize);
}
/**
* 暂停下载
*/
public void pauseDownloader() {
mState = EngineConstants.DOWNLOAD_STATE_PAUSE;
mDBOper.addPauseFile(mBean.url, mBean.packageName, mBean.fileId);
}
/**
* 中断下载(非人为的暂停)
*/
private void interruptDownloader() {
mState = EngineConstants.DOWNLOAD_STATE_INTERRUPT;
}
/**
* 结束下载
*/
public void stopDownloader() {
mState = EngineConstants.DOWNLOAD_STATE_STOP;
mBean.currentPosition = 0;
removeDownloaderInfo(mBean.url);
}
/**
* 清除下载的信息
*
* @param urlstr
*/
private void removeDownloaderInfo(String urlstr) {
mDBOper.deleteDownloadTaskByUrl(urlstr);
mDBOper.removePauseFileByUrl(urlstr);
mBeans.clear();
}
/**
* 下载完成
*/
private void downloaderDone() {
mState = EngineConstants.DOWNLOAD_STATE_DONE;
mBean.doneTime = System.currentTimeMillis();
mCallback.callbackWhenDownloadTaskListener(mState, mBean,
mBean.fileName + "下载完成");
removeDownloaderInfo(mBean.url);
mDBOper.addCompleteTask(mBean);// 将完成信息保存至数据库
}
/**
* 出现错误时候回调
*
* @param info
*/
private void callBackError(String info) {
mState = EngineConstants.DOWNLOAD_STATE_ERROR;
mCallback.callbackWhenDownloadTaskListener(mState, mBean, info);
removeDownloaderInfo(mBean.url);
}
/**
* 判断SD卡上是否留有足够的空间
*/
private boolean hasSpaceInSDCard() {
if (mBean.fileSize > EngineUtil.getInstance().getFreeSpaceAtDirectory(
Environment.getExternalStorageDirectory().getAbsolutePath())) {
callBackError("存储卡空间不够");
return false;
}
return true;
}
}
源码:http://download.csdn.net/detail/h3c4lenovo/5987789
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- LeetCode73|根据字符出现频率排序
- LeetCode72|前K个高频元素
- LeetCode71|数组中第K个最大元素
- LeetCode70|最小K个数
- LeetCode69|消失的数字
- LeetCode68|和为s的两个数字
- LeetCode78|存在重复元素
- LeetCode77|排序链表
- LeetCode76|两颗二叉搜索树中的所有元素
- LeetCode75|二叉搜索树的第k大节点
- LeetCode86|只出现一次的数字II
- LeetCode85|只出现一次的数字III
- LeetCode84|只出现一次的数字
- LeetCode83|排序矩阵查找
- LeetCode82|翻转字符串里的单词