Android 断点续传的原理剖析与实例讲解

本文所要讲的是Android断点续传的内容,以实例的形式进行了详细介绍。

  一、断点续传的原理

其实断点续传的原理很简单,就是在http的请求上和一般的下载有所不同而已。

打个比方,浏览器请求服务器上的一个文时,所发出的请求如下:

假设服务器域名为www.jizhuomi.com/android,文件名为down.zip。

get /down.zip http/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; msie 5.01; windows nt 5.0)
connection: keep-alive

服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下:

200
content-length=106786028
accept-ranges=bytes
date=mon, 30 apr 2001 12:56:11 gmt
etag=w/"02ca57e173c11:95b"
content-type=application/octet-stream
server=microsoft-iis/5.0
last-modified=mon, 30 apr 2001 12:56:11 gmt

所谓断点续传,也就是要从文件已经下载的地方开始继续下载。所以在客户端浏览器传给web服务器的时候要多加一条信息--从哪里开始。

下面是用自己编的一个“浏览器”来传递请求信息给web服务器,要求从2000070字节开始。

get /down.zip http/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=bytes 2000070-106786027/106786028
date=mon, 30 apr 2001 12:55:20 gmt
etag=w/"02ca57e173c11:95b"
content-type=application/octet-stream
server=microsoft-iis/5.0
last-modified=mon, 30 apr 2001 12:55:20 gmt

和前面服务器返回的信息比较一下,就会发现增加了一行:

content-range=bytes 2000070-106786027/106786028

返回的代码也改为206了,而不再是200了。

知道了以上原理,就可以进行断点续传的编程了。

       二、java实现断点续传的关键几点

       用什么方法实现提交range: bytes=2000070-?

当然用最原始的socket是肯定能完成的,不过那样太费事了,其实java的net包中提供了这种功能。代码如下:

Java代码

url url = new url("http://www.jizhuomi.com/android/down.zip");
httpurlconnection httpconnection = (httpurlconnection)url.openconnection();
//设置user-agent
httpconnection.setrequestproperty("user-agent","netfox");
//设置断点续传的开始位置
httpconnection.setrequestproperty("range","bytes=2000070");
//获得输入流
inputstream input = httpconnection.getinputstream();
    从输入流中取出的字节流就是down.zip文件从2000070开始的字节流。

    大家看,其实断点续传用java实现起来还是很简单的吧。

    接下来要做的事就是怎么保存获得的流到文件中去了。

    保存文件采用的方法:我采用的是io包中的randaccessfile类。

    操作相当简单,假设从2000070处开始保存文件,代码如下:

Java代码
randomaccess osavedfile = new randomaccessfile("down.zip","rw");
long npos = 2000070;
//定位文件指针到npos位置
osavedfile.seek(npos);
byte[] b = new byte[1024];
int nread;
//从输入流中读入字节流,然后写到文件中
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
*/
package netfox;
import java.io.*;
import java.net.*;  

public class sitefilefetch extends thread {  

siteinfobean siteinfobean = null; //文件信息bean
long[] nstartpos; //开始位置
long[] nendpos; //结束位置
filesplitterfetch[] filesplitterfetch; //子线程对象
long nfilelength; //文件长度
boolean bfirst = true; //是否第一次取文件
boolean bstop = false; //停止标志
file tmpfile; //文件下载的临时信息
dataoutputstream output; //输出到文件的输出流  

public sitefilefetch(siteinfobean bean) throws ioexception
{
siteinfobean = bean;
//tmpfile = file.createtempfile ("zhong","1111",new file(bean.getsfilepath()));
tmpfile = new file(bean.getsfilepath()+file.separator + bean.getsfilename()+".info");
if(tmpfile.exists ())
{
bfirst = false;
read_npos();
}
else
{
nstartpos = new long[bean.getnsplitter()];
nendpos = new long[bean.getnsplitter()];
}
}  

public void run()
{
//获得文件长度
//分割文件
//实例filesplitterfetch
//启动filesplitterfetch线程
//等待子线程返回
try{
if(bfirst)
{
nfilelength = getfilesize();
if(nfilelength == -1)
{
system.err.println("file length is not known!");
}
else if(nfilelength == -2)
{
system.err.println("file is not access!");
}
else
{
for(int i=0;i<nstartpos.length;i++)
{
nstartpos = (long)(i*(nfilelength/nstartpos.length));
}
for(int i=0;i<nendpos.length-1;i++)
{
nendpos = nstartpos[i+1];
}
nendpos[nendpos.length-1] = nfilelength;
}
}
//启动子线程
filesplitterfetch = new filesplitterfetch[nstartpos.length];
for(int i=0;i<nstartpos.length;i++)
{
filesplitterfetch = new filesplitterfetch(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] = new filesplitterfetch(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();  

//等待子线程结束
//int count = 0;
//是否结束while循环
boolean breakwhile = false;  

while(!bstop)
{
write_npos();
utility.sleep(500);
breakwhile = true;  

for(int i=0;i<nstartpos.length;i++)
{
if(!filesplitterfetch.bdownover)
{
breakwhile = false;
break;
}
}
if(breakwhile)
break;  

//count++;
//if(count>4)
// sitestop();
}  

system.err.println("文件下载结束!");
}
catch(exception e){e.printstacktrace ();}
}  

//获得文件长度
public long getfilesize()
{
int nfilelength = -1;
try{
url url = new url(siteinfobean.getssiteurl());
httpurlconnection httpconnection = (httpurlconnection)url.openconnection ();
httpconnection.setrequestproperty("user-agent","netfox");  

int responsecode=httpconnection.getresponsecode();
if(responsecode>=400)
{
processerrorcode(responsecode);
return -2; //-2 represent access is error
}  

string sheader;  

for(int i=1;;i++)
{
//datainputstream in = new datainputstream(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(ioexception e){e.printstacktrace ();}
catch(exception e){e.printstacktrace ();}  

utility.log(nfilelength);  

return nfilelength;
}  

//保存下载信息(文件指针位置)
private void write_npos()
{
try{
output = new dataoutputstream(new fileoutputstream(tmpfile));
output.writeint(nstartpos.length);
for(int i=0;i<nstartpos.length;i++)
{
// output.writelong(npos);
output.writelong(filesplitterfetch.nstartpos);
output.writelong(filesplitterfetch.nendpos);
}
output.close();
}
catch(ioexception e){e.printstacktrace ();}
catch(exception e){e.printstacktrace ();}
}  

//读取保存的下载信息(文件指针位置)
private void read_npos()
{
try{
datainputstream input = new datainputstream(new fileinputstream(tmpfile));
int ncount = input.readint();
nstartpos = new long[ncount];
nendpos = new long[ncount];
for(int i=0;i<nstartpos.length;i++)
{
nstartpos = input.readlong();
nendpos = input.readlong();
}
input.close();
}
catch(ioexception e){e.printstacktrace ();}
catch(exception e){e.printstacktrace ();}
}  

private void processerrorcode(int nerrorcode)
{
system.err.println("error code : " + nerrorcode);
}  

//停止文件下载
public void sitestop()
{
bstop = true;
for(int i=0;i<nstartpos.length;i++)
filesplitterfetch.splitterstop();
}
}
/*
**filesplitterfetch.java
*/
package netfox;  

import java.io.*;
import java.net.*;  

public class filesplitterfetch extends thread {  

string surl; //file url
long nstartpos; //file snippet start position
long nendpos; //file snippet end position
int nthreadid; //threads id
boolean bdownover = false; //downing is over
boolean bstop = false; //stop identical
fileaccessi fileaccessi = null; //file access interface  

public filesplitterfetch(string surl,string sname,long nstart,long nend,int id) throws ioexception
{
this.surl = surl;
this.nstartpos = nstart;
this.nendpos = nend;
nthreadid = id;
fileaccessi = new fileaccessi(sname,nstartpos);
}  

public void run()
{
while(nstartpos < nendpos && !bstop)
{
try{
url url = new url(surl);
httpurlconnection httpconnection = (httpurlconnection)url.openconnection ();
httpconnection.setrequestproperty("user-agent","netfox");
string sproperty = "bytes="+nstartpos+"-";
httpconnection.setrequestproperty("range",sproperty);
utility.log(sproperty);  

inputstream input = httpconnection.getinputstream();
//logresponsehead(httpconnection);  

byte[] b = new byte[1024];
int nread;
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 + " is over!");
bdownover = true;
//npos = fileaccessi.write (b,0,nread);
}
catch(exception e){e.printstacktrace ();}
}
}  

//打印回应的头信息
public void logresponsehead(httpurlconnection con)
{
for(int i=1;;i++)
{
string header=con.getheaderfieldkey(i);
if(header!=null)
//responseheaders.put(header,httpconnection.getheaderfield(header));
utility.log(header+" : "+con.getheaderfield(header));
else
break;
}
}  

public void splitterstop()
{
bstop = true;
}
}  

/*
**fileaccess.java
*/
package netfox;
import java.io.*;  

public class fileaccessi implements serializable{  

randomaccessfile osavedfile;
long npos;  

public fileaccessi() throws ioexception
{
this("",0);
}  

public fileaccessi(string sname,long npos) throws ioexception
{
osavedfile = new randomaccessfile(sname,"rw");
this.npos = npos;
osavedfile.seek(npos);
}  

public synchronized int write(byte[] b,int nstart,int nlen)
{
int n = -1;
try{
osavedfile.write(b,nstart,nlen);
n = nlen;
}
catch(ioexception e)
{
e.printstacktrace ();
}  

return n;
}
}  

/*
**siteinfobean.java
*/
package netfox;  

public class siteinfobean {  

private string ssiteurl; //sites url
private string sfilepath; //saved files path
private string sfilename; //saved files name
private int nsplitter; //count of splited downloading file  

public siteinfobean()
{
//default value of nsplitter is 5
this("","","",5);
}  

public siteinfobean(string surl,string spath,string sname,int nspiltter)
{
ssiteurl= surl;
sfilepath = spath;
sfilename = sname;
this.nsplitter = nspiltter;
}  

public string getssiteurl()
{
return ssiteurl;
}  

public void setssiteurl(string value)
{
ssiteurl = value;
}  

public string getsfilepath()
{
return sfilepath;
}  

public void setsfilepath(string value)
{
sfilepath = value;
}  

public string getsfilename()
{
return sfilename;
}  

public void setsfilename(string value)
{
sfilename = value;
}  

public int getnsplitter()
{
return nsplitter;
}  

public void setnsplitter(int ncount)
{
nsplitter = ncount;
}
}   

/*
**utility.java
*/
package netfox;  

public class utility {  

public utility()
{
}  

public static void sleep(int nsecond)
{
try{
thread.sleep(nsecond);
}
catch(exception e)
{
e.printstacktrace ();
}
}  

public static void log(string smsg)
{
system.err.println(smsg);
}  

public static void log(int smsg)
{
system.err.println(smsg);
}
}  

/*
**testmethod.java
*/
package netfox;  

public class testmethod {  

public testmethod()
{ ///xx/weblogic60b2_win.exe
try{
siteinfobean bean = new siteinfobean("http://localhost/xx/weblogic60b2_win.exe","l:\\temp","weblogic60b2_win.exe",5);
//siteinfobean bean = new siteinfobean("http://localhost:8080/down.zip","l:\\temp","weblogic60b2_win.exe",5);
sitefilefetch filefetch = new sitefilefetch(bean);
filefetch.start();
}
catch(exception e){e.printstacktrace ();}
}  

public static void main(string[] args)
{
new testmethod();
}
}

以上就是对Android 断点续传的资料整理,后续继续补充相关资料,谢谢大家对本站的支持!

(0)

相关推荐

  • Android多线程断点续传下载功能实现代码

    原理 其实断点续传的原理很简单,从字面上理解,所谓断点续传就是从停止的地方重新下载. 断点:线程停止的位置. 续传:从停止的位置重新下载. 用代码解析就是: 断点:当前线程已经下载完成的数据长度. 续传:向服务器请求上次线程停止位置之后的数据. 原理知道了,功能实现起来也简单.每当线程停止时就把已下载的数据长度写入记录文件,当重新下载时,从记录文件读取已经下载了的长度.而这个长度就是所需要的断点. 续传的实现也简单,可以通过设置网络请求参数,请求服务器从指定的位置开始读取数据. 而要实现这两个功

  • Android 断点续传原理以及实现

    Android 断点续传原理以及实现 0.  前言 在Android开发中,断点续传听起来挺容易,在下载一个文件时点击暂停任务暂停,点击开始会继续下载文件.但是真正实现起来知识点还是蛮多的,因此今天有时间实现了一下,并进行记录. 1.  断点续传原理 在本地下载过程中要使用数据库实时存储到底存储到文件的哪个位置了,这样点击开始继续传递时,才能通过HTTP的GET请求中的setRequestProperty()方法可以告诉服务器,数据从哪里开始,到哪里结束.同时在本地的文件写入时,RandomAc

  • Android实现网络多线程断点续传下载功能

    我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多线程断点需要什么功能? 1.多线程下载 2.支持断点 使用多线程的好处:使用多线程下载会提升文件下载的速度 原理 多线程下载的原理就是将要下载的文件分成若干份,其中每份都使用一个单独的线程进行下载,这样对于文件的下载速度自然就提高了许多. 既然要分成若干部分分工下载,自然要知道各个线程自己要下载的起始位置,与要下载的大小.所以我们要解决线程的分配与各个线程定位到下载

  • 详解Android使用OKHttp3实现下载(断点续传、显示进度)

    OKHttp3是如今非常流行的Android网络请求框架,那么如何利用Android实现断点续传呢,今天写了个Demo尝试了一下,感觉还是有点意思 准备阶段 我们会用到OKHttp3来做网络请求,使用RxJava来实现线程的切换,并且开启Java8来启用Lambda表达式,毕竟RxJava实现线程切换非常方便,而且数据流的形式也非常舒服,同时Lambda和RxJava配合食用味道更佳 打开我们的app Module下的build.gradle,代码如下 apply plugin: 'com.an

  • Android通过HTTP协议实现断点续传下载实例

    整理文档,搜刮出一个Android通过HTTP协议实现断点续传下载的代码,稍微整理精简一下做下分享. FileDownloader.java package cn.itcast.net.download; import java.io.File; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.LinkedHashMap; impor

  • Android实现网络多线程断点续传下载实例

    我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多线程断点需要什么功能? 1.多线程下载, 2.支持断点. 使用多线程的好处:使用多线程下载会提升文件下载的速度.那么多线程下载文件的过程是: (1)首先获得下载文件的长度,然后设置本地文件的长度. HttpURLConnection.getContentLength();//获取下载文件的长度 RandomAccessFile file = new RandomAc

  • android实现多线程下载文件(支持暂停、取消、断点续传)

    多线程下载文件(支持暂停.取消.断点续传) 多线程同时下载文件即:在同一时间内通过多个线程对同一个请求地址发起多个请求,将需要下载的数据分割成多个部分,同时下载,每个线程只负责下载其中的一部分,最后将每一个线程下载的部分组装起来即可. 涉及的知识及问题 请求的数据如何分段 分段完成后如何下载和下载完成后如何组装到一起 暂停下载和继续下载的实现(wait().notifyAll().synchronized的使用) 取消下载和断点续传的实现 一.请求的数据如何分段 首先通过HttpURLConne

  • Android编程开发实现多线程断点续传下载器实例

    本文实例讲述了Android编程开发实现多线程断点续传下载器.分享给大家供大家参考,具体如下: 使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线.电量不足等情况下,这就需要使用到断点续传功能,下次启动时从记录位置继续下载,可避免重复部分的下载.这里采用数据库来记录下载的进度. 效果图:   断点续传 1.断点续传需要在下载过程中记录每条线程的下载进度 2.每次下载开始之前先读取数据库

  • PC版与Android手机版带断点续传的多线程下载

    一.多线程下载 多线程下载就是抢占服务器资源 原理:服务器CPU 分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源. 1.设置开启线程数,发送http请求到下载地址,获取下载文件的总长度           然后创建一个长度一致的临时文件,避免下载到一半存储空间不够了,并计算每个线程下载多少数据              2.计算每个线程下载数据的开始和结束位置           再次发送请求,用 Range 头请求开始位置和结束位

  • android使用OkHttp实现下载的进度监听和断点续传

    1. 导入依赖包 // retrofit, 基于Okhttp,考虑到项目中经常会用到retrofit,就导入这个了. compile 'com.squareup.retrofit2:retrofit:2.1.0' // ButterKnife compile 'com.jakewharton:butterknife:7.0.1' // rxjava 本例中线程切换要用到,代替handler compile 'io.reactivex:rxjava:1.1.6' compile 'io.react

随机推荐