单线程下载HTTP文件是一件非常简单的事。那么,多线程断点需要什么功能?
1.多线程下载;
2.支持断点;
思路:第一次下载某个文件的时候,创建一个下载线程开始从网络下载文件,把已经下载的部分保存到磁盘文件,当用户通过UI主动暂停下载的时候,把线程停止销毁。
下次下载同一个url的文件的时候,重新创建一个下载线程开始从网络下载文件,如果发现磁盘上保存过该文件,则通过 HttpURLConnection.setRequestProperty("Range", "bytes=本地缓存的文件的字节数-完整文件的大小");
从上次中断的地方请求继续下载。
一、多线程
使用多线程的好处:使用多线程下载会提升文件下载的速度。那么多线程下载文件的过程是:
(1)首先获得下载文件的长度,然后设置本地文件的长度。
HttpURLConnection.getContentLength();//获取下载文件的长度
RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","rwd");
file.setLength(filesize);//设置本地文件的长度
(2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置。
如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如下图所示。
(3)使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,
比如要下载的文件是1000个字节(编号0-999),分两次下载
HttpURLConnection.setRequestProperty("Range", "bytes=0-499"); 下载文件的前500个字节,0代表第一个字节。
HttpURLConnection.setRequestProperty("Range", "bytes=500-999");
(4)保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。
RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd");
threadfile.seek(2097152);//从文件的什么位置开始写入数据
二、断点续传
写了一个断点续传的组件
public
class
SuspendableDownloader {
private
static
final
String SDPATH =
"//sdcard//"
;
private
static
final
String TAG =
"SuspendableDownloader"
;
public
boolean
isStopDownload =
false
;
public
void
startDownload() {
isStopDownload =
false
;
}
public
void
stopDownload(){
isStopDownload =
true
;
}
public
interface
CallBack{
public
boolean
notfiyProgress(
int
percent);
public
void
onDownLoadCancel();
}
private
CallBack mCallBack =
null
;
public
void
setCallBack(CallBack callback){
this
.mCallBack = callback;
}
public
String downLoadFile(String httpUrl)
throws
IOException {
File tmpFile =
new
File(SDPATH);
if
(!tmpFile.exists()) {
tmpFile.mkdir();
}
String fileName = httpUrl.split(
"/"
)[httpUrl.split(
"/"
).length-
1
];
final
RandomAccessFile file =
new
RandomAccessFile(SDPATH + fileName,
"rwd"
);
//第一次下载
if
(file.length() ==
0
) {
URL url =
new
URL(httpUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
int
length = conn.getContentLength();
InputStream is = conn.getInputStream();
//FileOutputStream fos = new FileOutputStream(file);
byte
[] buf =
new
byte
[
256
*
2
];
conn.connect();
double
count =
0
;
if
(conn.getResponseCode() >=
400
) {
Log.i(TAG,
"time exceed"
);
}
else
{
while
(count <=
100
&& !isStopDownload) {
if
(is !=
null
) {
int
numRead = is.read(buf);
if
(numRead <=
0
) {
break
;
}
else
{
file.write(buf,
0
, numRead);
mCallBack.notfiyProgress((
int
)(file.length()*
100
/length));
Log.d(TAG,
"notifyprogress="
+(
int
)(file.length()*
100
/length));
}
}
else
{
break
;
}
}
if
(isStopDownload) {
mCallBack.onDownLoadCancel();
}
}
conn.disconnect();
file.close();
is.close();
}
else
{
//不是第一次下载,之前有过下载
Log.d(TAG,
"continue file.length() ="
+file.length() );
// file.seek(file.length());
URL url =
new
URL(httpUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
int
length = conn.getContentLength();
HttpURLConnection conn2 = (HttpURLConnection) url.openConnection();
conn2.setRequestProperty(
"Range"
,
"bytes="
+file.length()+
"-"
+(length-
1
));
//如果文件已经下载完成,即从 1234-1234 会报告FileNotFound Exception
//java.lang.IllegalStateException: Cannot set request property after connection is made
// int length = conn.getContentLength();http://www.eoeandroid.com/thread-154241-1-1.html
if
(file.length() == length) {
return
SDPATH + fileName;
}
// file.setLength(length);
InputStream is = conn.getInputStream();
//FileOutputStream fos = new FileOutputStream(file);
byte
[] buf =
new
byte
[
256
*
2
];
conn.connect();
double
count =
0
;
if
(conn.getResponseCode() >=
400
) {
Log.i(TAG,
"time exceed"
);
}
else
{
while
(count <=
100
&& !isStopDownload) {
if
(is !=
null
) {
int
numRead = is.read(buf);
if
(numRead <=
0
) {
break
;
}
else
{
file.write(buf,
0
, numRead);
mCallBack.notfiyProgress((
int
)(file.length()*
100
/length));
Log.d(TAG,
"notifyprogress="
+(
int
)(file.length()*
100
/length));
}
}
else
{
break
;
}
}
if
(isStopDownload) {
mCallBack.onDownLoadCancel();
}
}
conn.disconnect();
file.close();
is.close();
}
return
SDPATH + fileName;
}
}
|
注意一些地方:
1.错误 java.lang.IllegalStateException: Cannot set request property after connection is made
由于要通过HttpURLConnection getContentLength获取下载文件的长度,之后才能设置Range请求头,但是getContentLength已经与server建立了connection,导致了这个错误。解决方法就是通过两个HttpURLConnection实例,第一次获取下载文件长度,第二次获取文件
2. Thread.stop 已经废弃,建议使用调用interrupt , 但是有些操作是不可打断的。包括进入synchronized段以及Lock.lock(),inputSteam.read()等。解决方法是同一个变量来控制isStopDownload来控制下载线程的退出。