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断点续传的资料整理,后续继续补充相关资料,谢谢大家对本站的支持!