Android 断点续传的原理剖析与实例讲解
本文所要讲的是Android断点续传的内容,以实例的形式进行了详细介绍。
一、断点续传的原理
其实断点续传的原理很简单,就是在http的请求上和一般的下载有所不同而已。
打个比方,浏览器请求服务器上的一个文时,所发出的请求如下:
假设服务器域名为www.jizhuomi.com/android,文件名为down.zip。
get/down.ziphttp/1.1
accept:image/gif,image/x-xbitmap,image/jpeg,image/pjpeg,application/vnd.ms-
excel,application/msword,application/vnd.ms-powerpoint,*/*
accept-language:zh-cn
accept-encoding:gzip,deflate
user-agent:mozilla/4.0(compatible;msie5.01;windowsnt5.0)
connection:keep-alive
服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下:
200
content-length=106786028
accept-ranges=bytes
date=mon,30apr200112:56:11gmt
etag=w/"02ca57e173c11:95b"
content-type=application/octet-stream
server=microsoft-iis/5.0
last-modified=mon,30apr200112:56:11gmt
所谓断点续传,也就是要从文件已经下载的地方开始继续下载。所以在客户端浏览器传给web服务器的时候要多加一条信息--从哪里开始。
下面是用自己编的一个“浏览器”来传递请求信息给web服务器,要求从2000070字节开始。
get/down.ziphttp/1.0
user-agent:netfox
range:bytes=2000070-
accept:text/html,image/gif,image/jpeg,*;q=.2,*/*;q=.2
仔细看一下就会发现多了一行range:bytes=2000070-
这一行的意思就是告诉服务器down.zip这个文件从2000070字节开始传,前面的字节不用传了。
服务器收到这个请求以后,返回的信息如下:
206
content-length=106786028
content-range=bytes2000070-106786027/106786028
date=mon,30apr200112:55:20gmt
etag=w/"02ca57e173c11:95b"
content-type=application/octet-stream
server=microsoft-iis/5.0
last-modified=mon,30apr200112:55:20gmt
和前面服务器返回的信息比较一下,就会发现增加了一行:
content-range=bytes2000070-106786027/106786028
返回的代码也改为206了,而不再是200了。
知道了以上原理,就可以进行断点续传的编程了。
二、java实现断点续传的关键几点
用什么方法实现提交range:bytes=2000070-?
当然用最原始的socket是肯定能完成的,不过那样太费事了,其实java的net包中提供了这种功能。代码如下:
Java代码
urlurl=newurl("http://www.jizhuomi.com/android/down.zip");
httpurlconnectionhttpconnection=(httpurlconnection)url.openconnection();
//设置user-agent
httpconnection.setrequestproperty("user-agent","netfox");
//设置断点续传的开始位置
httpconnection.setrequestproperty("range","bytes=2000070");
//获得输入流
inputstreaminput=httpconnection.getinputstream();
从输入流中取出的字节流就是down.zip文件从2000070开始的字节流。
大家看,其实断点续传用java实现起来还是很简单的吧。
接下来要做的事就是怎么保存获得的流到文件中去了。
保存文件采用的方法:我采用的是io包中的randaccessfile类。
操作相当简单,假设从2000070处开始保存文件,代码如下:
Java代码
randomaccessosavedfile=newrandomaccessfile("down.zip","rw");
longnpos=2000070;
//定位文件指针到npos位置
osavedfile.seek(npos);
byte[]b=newbyte[1024];
intnread;
//从输入流中读入字节流,然后写到文件中
while((nread=input.read(b,0,1024))>0)
{
osavedfile.write(b,0,nread);
}
怎么样,也很简单吧。
接下来要做的就是整合成一个完整的程序了。包括一系列的线程控制等等。
三、断点续传内核的实现
主要用了6个类,包括一个测试类。
sitefilefetch.java负责整个文件的抓取,控制内部线程(filesplitterfetch类)。
filesplitterfetch.java负责部分文件的抓取。
fileaccess.java负责文件的存储。
siteinfobean.java要抓取的文件的信息,如文件保存的目录,名字,抓取文件的url等。
utility.java工具类,放一些简单的方法。
testmethod.java测试类。
四、实例源码
下面是源程序:
Java代码
/*
**sitefilefetch.java
*/
packagenetfox;
importjava.io.*;
importjava.net.*;
publicclasssitefilefetchextendsthread{
siteinfobeansiteinfobean=null;//文件信息bean
long[]nstartpos;//开始位置
long[]nendpos;//结束位置
filesplitterfetch[]filesplitterfetch;//子线程对象
longnfilelength;//文件长度
booleanbfirst=true;//是否第一次取文件
booleanbstop=false;//停止标志
filetmpfile;//文件下载的临时信息
dataoutputstreamoutput;//输出到文件的输出流
publicsitefilefetch(siteinfobeanbean)throwsioexception
{
siteinfobean=bean;
//tmpfile=file.createtempfile("zhong","1111",newfile(bean.getsfilepath()));
tmpfile=newfile(bean.getsfilepath()+file.separator+bean.getsfilename()+".info");
if(tmpfile.exists())
{
bfirst=false;
read_npos();
}
else
{
nstartpos=newlong[bean.getnsplitter()];
nendpos=newlong[bean.getnsplitter()];
}
}
publicvoidrun()
{
//获得文件长度
//分割文件
//实例filesplitterfetch
//启动filesplitterfetch线程
//等待子线程返回
try{
if(bfirst)
{
nfilelength=getfilesize();
if(nfilelength==-1)
{
system.err.println("filelengthisnotknown!");
}
elseif(nfilelength==-2)
{
system.err.println("fileisnotaccess!");
}
else
{
for(inti=0;i<nstartpos.length;i++)
{
nstartpos=(long)(i*(nfilelength/nstartpos.length));
}
for(inti=0;i<nendpos.length-1;i++)
{
nendpos=nstartpos[i+1];
}
nendpos[nendpos.length-1]=nfilelength;
}
}
//启动子线程
filesplitterfetch=newfilesplitterfetch[nstartpos.length];
for(inti=0;i<nstartpos.length;i++)
{
filesplitterfetch=newfilesplitterfetch(siteinfobean.getssiteurl(),
siteinfobean.getsfilepath()+file.separator+siteinfobean.getsfilename(),
nstartpos,nendpos,i);
utility.log("thread"+i+",nstartpos="+nstartpos+",nendpos="+nendpos);
filesplitterfetch.start();
}
//filesplitterfetch[npos.length-1]=newfilesplitterfetch(siteinfobean.getssiteurl(),
siteinfobean.getsfilepath()+file.separator+siteinfobean.getsfilename(),npos[npos.length-1],nfilelength,npos.length-1);
//utility.log("thread"+(npos.length-1)+",nstartpos="+npos[npos.length-1]+",
nendpos="+nfilelength);
//filesplitterfetch[npos.length-1].start();
//等待子线程结束
//intcount=0;
//是否结束while循环
booleanbreakwhile=false;
while(!bstop)
{
write_npos();
utility.sleep(500);
breakwhile=true;
for(inti=0;i<nstartpos.length;i++)
{
if(!filesplitterfetch.bdownover)
{
breakwhile=false;
break;
}
}
if(breakwhile)
break;
//count++;
//if(count>4)
//sitestop();
}
system.err.println("文件下载结束!");
}
catch(exceptione){e.printstacktrace();}
}
//获得文件长度
publiclonggetfilesize()
{
intnfilelength=-1;
try{
urlurl=newurl(siteinfobean.getssiteurl());
httpurlconnectionhttpconnection=(httpurlconnection)url.openconnection();
httpconnection.setrequestproperty("user-agent","netfox");
intresponsecode=httpconnection.getresponsecode();
if(responsecode>=400)
{
processerrorcode(responsecode);
return-2;//-2representaccessiserror
}
stringsheader;
for(inti=1;;i++)
{
//datainputstreamin=newdatainputstream(httpconnection.getinputstream());
//utility.log(in.readline());
sheader=httpconnection.getheaderfieldkey(i);
if(sheader!=null)
{
if(sheader.equals("content-length"))
{
nfilelength=integer.parseint(httpconnection.getheaderfield(sheader));
break;
}
}
else
break;
}
}
catch(ioexceptione){e.printstacktrace();}
catch(exceptione){e.printstacktrace();}
utility.log(nfilelength);
returnnfilelength;
}
//保存下载信息(文件指针位置)
privatevoidwrite_npos()
{
try{
output=newdataoutputstream(newfileoutputstream(tmpfile));
output.writeint(nstartpos.length);
for(inti=0;i<nstartpos.length;i++)
{
//output.writelong(npos);
output.writelong(filesplitterfetch.nstartpos);
output.writelong(filesplitterfetch.nendpos);
}
output.close();
}
catch(ioexceptione){e.printstacktrace();}
catch(exceptione){e.printstacktrace();}
}
//读取保存的下载信息(文件指针位置)
privatevoidread_npos()
{
try{
datainputstreaminput=newdatainputstream(newfileinputstream(tmpfile));
intncount=input.readint();
nstartpos=newlong[ncount];
nendpos=newlong[ncount];
for(inti=0;i<nstartpos.length;i++)
{
nstartpos=input.readlong();
nendpos=input.readlong();
}
input.close();
}
catch(ioexceptione){e.printstacktrace();}
catch(exceptione){e.printstacktrace();}
}
privatevoidprocesserrorcode(intnerrorcode)
{
system.err.println("errorcode:"+nerrorcode);
}
//停止文件下载
publicvoidsitestop()
{
bstop=true;
for(inti=0;i<nstartpos.length;i++)
filesplitterfetch.splitterstop();
}
}
/*
**filesplitterfetch.java
*/
packagenetfox;
importjava.io.*;
importjava.net.*;
publicclassfilesplitterfetchextendsthread{
stringsurl;//fileurl
longnstartpos;//filesnippetstartposition
longnendpos;//filesnippetendposition
intnthreadid;//threadsid
booleanbdownover=false;//downingisover
booleanbstop=false;//stopidentical
fileaccessifileaccessi=null;//fileaccessinterface
publicfilesplitterfetch(stringsurl,stringsname,longnstart,longnend,intid)throwsioexception
{
this.surl=surl;
this.nstartpos=nstart;
this.nendpos=nend;
nthreadid=id;
fileaccessi=newfileaccessi(sname,nstartpos);
}
publicvoidrun()
{
while(nstartpos<nendpos&&!bstop)
{
try{
urlurl=newurl(surl);
httpurlconnectionhttpconnection=(httpurlconnection)url.openconnection();
httpconnection.setrequestproperty("user-agent","netfox");
stringsproperty="bytes="+nstartpos+"-";
httpconnection.setrequestproperty("range",sproperty);
utility.log(sproperty);
inputstreaminput=httpconnection.getinputstream();
//logresponsehead(httpconnection);
byte[]b=newbyte[1024];
intnread;
while((nread=input.read(b,0,1024))>0&&nstartpos<nendpos&&!bstop)
{
nstartpos+=fileaccessi.write(b,0,nread);
//if(nthreadid==1)
//utility.log("nstartpos="+nstartpos+",nendpos="+nendpos);
}
utility.log("thread"+nthreadid+"isover!");
bdownover=true;
//npos=fileaccessi.write(b,0,nread);
}
catch(exceptione){e.printstacktrace();}
}
}
//打印回应的头信息
publicvoidlogresponsehead(httpurlconnectioncon)
{
for(inti=1;;i++)
{
stringheader=con.getheaderfieldkey(i);
if(header!=null)
//responseheaders.put(header,httpconnection.getheaderfield(header));
utility.log(header+":"+con.getheaderfield(header));
else
break;
}
}
publicvoidsplitterstop()
{
bstop=true;
}
}
/*
**fileaccess.java
*/
packagenetfox;
importjava.io.*;
publicclassfileaccessiimplementsserializable{
randomaccessfileosavedfile;
longnpos;
publicfileaccessi()throwsioexception
{
this("",0);
}
publicfileaccessi(stringsname,longnpos)throwsioexception
{
osavedfile=newrandomaccessfile(sname,"rw");
this.npos=npos;
osavedfile.seek(npos);
}
publicsynchronizedintwrite(byte[]b,intnstart,intnlen)
{
intn=-1;
try{
osavedfile.write(b,nstart,nlen);
n=nlen;
}
catch(ioexceptione)
{
e.printstacktrace();
}
returnn;
}
}
/*
**siteinfobean.java
*/
packagenetfox;
publicclasssiteinfobean{
privatestringssiteurl;//sitesurl
privatestringsfilepath;//savedfilespath
privatestringsfilename;//savedfilesname
privateintnsplitter;//countofspliteddownloadingfile
publicsiteinfobean()
{
//defaultvalueofnsplitteris5
this("","","",5);
}
publicsiteinfobean(stringsurl,stringspath,stringsname,intnspiltter)
{
ssiteurl=surl;
sfilepath=spath;
sfilename=sname;
this.nsplitter=nspiltter;
}
publicstringgetssiteurl()
{
returnssiteurl;
}
publicvoidsetssiteurl(stringvalue)
{
ssiteurl=value;
}
publicstringgetsfilepath()
{
returnsfilepath;
}
publicvoidsetsfilepath(stringvalue)
{
sfilepath=value;
}
publicstringgetsfilename()
{
returnsfilename;
}
publicvoidsetsfilename(stringvalue)
{
sfilename=value;
}
publicintgetnsplitter()
{
returnnsplitter;
}
publicvoidsetnsplitter(intncount)
{
nsplitter=ncount;
}
}
/*
**utility.java
*/
packagenetfox;
publicclassutility{
publicutility()
{
}
publicstaticvoidsleep(intnsecond)
{
try{
thread.sleep(nsecond);
}
catch(exceptione)
{
e.printstacktrace();
}
}
publicstaticvoidlog(stringsmsg)
{
system.err.println(smsg);
}
publicstaticvoidlog(intsmsg)
{
system.err.println(smsg);
}
}
/*
**testmethod.java
*/
packagenetfox;
publicclasstestmethod{
publictestmethod()
{///xx/weblogic60b2_win.exe
try{
siteinfobeanbean=newsiteinfobean("http://localhost/xx/weblogic60b2_win.exe","l:\\temp","weblogic60b2_win.exe",5);
//siteinfobeanbean=newsiteinfobean("http://localhost:8080/down.zip","l:\\temp","weblogic60b2_win.exe",5);
sitefilefetchfilefetch=newsitefilefetch(bean);
filefetch.start();
}
catch(exceptione){e.printstacktrace();}
}
publicstaticvoidmain(string[]args)
{
newtestmethod();
}
}
以上就是对Android断点续传的资料整理,后续继续补充相关资料,谢谢大家对本站的支持!