多线程下载实例
一个简单的HttpURLConnection多线程下载实例,没有做断点续传,没有实现线程池,会根据网络状况自动分配线程数
/**
* 简单的多线程下载(不支持断点,无线程池)
*
* @Title MulThreadDownload
* @Description 简单的多线程下载
* @author Andy
*/
public class MulThreadDownload {
/** 下载的URL */
private URL downloadUrl;
/** 本地文件 */
private File localFile;
/** 每个线程下载的数据长度 */
private int block;
/**
*
* 多线程文件下载(根据网络状况自动分配线程数)
*
* @param path
* 下载地址
* @param locDirPath
* 本地目录
* @param downloadCallBack
* 回调
* @param fileTypes
* 指定文件类型(可选)
* @throws Exception
*/
public void download(Context context, String path, String locDirPath,
DownloadCallBack downloadCallBack, String... fileTypes)
throws Exception {
int threadCount = 3;
try {
ConnectivityManager connectivityManager = (ConnectivityManager) context
.getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if (info == null || !info.isConnectedOrConnecting()) {
threadCount = 3;
return;
}
switch (info.getType()) {
case ConnectivityManager.TYPE_WIFI:
case ConnectivityManager.TYPE_WIMAX:
case ConnectivityManager.TYPE_ETHERNET:
threadCount = 4;
break;
case ConnectivityManager.TYPE_MOBILE:
switch (info.getSubtype()) {
case TelephonyManager.NETWORK_TYPE_LTE: // 4G
case TelephonyManager.NETWORK_TYPE_HSPAP:
case TelephonyManager.NETWORK_TYPE_EHRPD:
threadCount = 3;
break;
case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
case TelephonyManager.NETWORK_TYPE_CDMA:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_EVDO_B:
threadCount = 2;
break;
case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
case TelephonyManager.NETWORK_TYPE_EDGE:
threadCount = 1;
break;
default:
threadCount = 3;
}
break;
default:
threadCount = 3;
}
} catch (Exception e) {
threadCount = 3;
}
download(path, locDirPath, threadCount, downloadCallBack, fileTypes);
}
/**
*
* 多线程文件下载
*
* @param path
* 下载地址
* @param locDirPath
* 本地目录
* @param threadCount
* 线程数
* @param downloadCallBack
* 回调
* @param fileTypes
* 指定文件类型(可选)
* @throws Exception
*/
public void download(final String path, final String locDirPath,
final int threadCount, final DownloadCallBack downloadCallBack,
final String... fileTypes) {
System.out.println("线程" + "主线程开始=" + path);
new Thread() {
public void run() {
try {
downloadThread(path, locDirPath, threadCount,
downloadCallBack, fileTypes);
} catch (Exception e) {
e.printStackTrace();
System.out.println("线程" + "子线程开始=" + e);
downloadCallBack.error();
}
};
}.start();
}
/**
*
* 多线程文件下载
*
* @param path
* 下载地址
* @param locDirPath
* 本地目录
* @param threadCount
* 线程数
* @param downloadCallBack
* 回调
* @param fileTypes
* 指定文件类型
* @throws Exception
*/
private void downloadThread(String path, String locDirPath,
final int threadCount, final DownloadCallBack downloadCallBack,
String... fileTypes) throws Exception {
downloadUrl = new URL(path);
HttpURLConnection conn = (HttpURLConnection) downloadUrl
.openConnection();
// 设置 GET 请求方式
conn.setRequestMethod("GET");
// 设置响应超时
conn.setConnectTimeout(5 * 1000);
// 获取下载文件大小
final int len = conn.getContentLength();
System.out.println("线程" + "len=" + len);
if (len <= 0) {
// 未获取到正确的文件大小
downloadCallBack.contentLengthError();
return;
}
File dir = new File(locDirPath);
if (!dir.exists()) {
dir.mkdirs();
}
/* 获取本地文件完整路径 */
final String locPath = getLocPath(path, locDirPath, fileTypes);
/* 创建本地目标文件,并设置其大小为准备下载文件的总大小 */
localFile = new File(locPath + ".tmp");
RandomAccessFile accessFile = new RandomAccessFile(localFile, "rwd");
accessFile.setLength(len);
accessFile.close();
/* 计算每条线程要下载的数据大小 */
block = len % threadCount == 0 ? len / threadCount : len / threadCount
+ 1;
downloadCallBack.totalSize(len);
/* 启动线程下载文件 */
for (int i = 0; i < threadCount; i++) {
int threadId = i;
long start = i * block;
long end = start + block - 1;
if (end >= len) {
end = len - 1;
}
System.out.println("线程" + threadId + "开始");
new DownloadThread(threadId, start, end, new Callback() {
@Override
public void complete(int threadId) {
completeNum++;
System.out.println("线程" + threadId + "完成"+completeNum+":"+threadCount);
if (completeNum == threadCount) {
// 全部下载完成
localFile.renameTo(new File(locPath));
downloadCallBack.complete(locPath);
}
}
@Override
public void progress(int threadId, long size) {
downloadSize += size;
downloadCallBack.progress(len, downloadSize);
}
@Override
public void error(int threadId) {
System.out.println("线程" + threadId + "error");
downloadCallBack.error();
}
}).start();
}
}
public interface DownloadCallBack {
/** 异常,结束下载,ContentLength<=0 */
public void contentLengthError();
public void totalSize(long total);
public void complete(String path);
public void progress(long total, long size);
public void error();
}
int completeNum = 0;
long downloadSize = 0;
private String getLocPath(String path, String locDirPath,
String... fileTypes) {
String locPath;
// 获取文件名
String locName = OSUtil.toMD5(path);
// 文件类型
String fileType;
if (fileTypes != null && fileTypes.length > 0) {
fileType = fileTypes[0];
} else {
// 获取path中的文件格式后缀
String[] s = path.split("\\.");
fileType = s[s.length - 1];
}
if (fileType != null && fileType.trim().length() > 0) {
// 如果path带有文件后缀,则使用其作为后缀
locPath = locDirPath + File.separator + locName + "." + fileType;
} else {
locPath = locDirPath + File.separator + locName;
}
return locPath;
}
/**
* 内部类: 文件下载线程类
*/
private final class DownloadThread extends Thread {
/* 线程 id */
private int threadId;
/* 线程下载结果回调 */
private Callback callback;
/* 开始下载的位置 */
private long startPosition;
/* 结束下载的位置 */
private long endPosition;
/**
* 新建一个下载线程
*
* @param threadid
* 线程 id
*/
public DownloadThread(int threadId, long start, long end,
Callback callback) {
this.threadId = threadId;
this.callback = callback;
startPosition = start;
endPosition = end;
}
@Override
public void run() {
RandomAccessFile accessFile = null;
try {
/* 设置从本地文件的什么位置开始写入数据 ,"rwd" 表示对文件具有读写删权限 */
accessFile = new RandomAccessFile(localFile, "rwd");
accessFile.seek(startPosition);
HttpURLConnection conn = (HttpURLConnection) downloadUrl
.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(5 * 1000);
/* 为 HTTP 设置 Range 属性,可以指定服务器返回数据的范围 */
conn.setRequestProperty("Range", "bytes=" + startPosition + "-"
+ endPosition);
/* 将数据写往本地文件 */
long size = writeTo(accessFile, conn);
if (size >= endPosition - startPosition) {
callback.complete(threadId);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (accessFile != null) {
accessFile.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
/**
* 将下载数据写往本地文件
*/
private long writeTo(RandomAccessFile accessFile, HttpURLConnection conn) {
InputStream is = null;
long size = 0;
try {
is = conn.getInputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = is.read(buffer)) != -1) {
accessFile.write(buffer, 0, len);
size += len;
callback.progress(threadId, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
return size;
}
}
interface Callback {
public void complete(int threadId);
public void progress(int threadId, long size);
public void error(int threadId);
}
}