可以突破服务器段对单个线程的速度限制。
不可能超过带宽。
服务端支持con.setRequestProPerty("Range","bytes="+startIndex+"-"+endIndex);
客户端RandomAccessFile 支持随意读写文件的任意位置
模式为rws 表示每次写入相应文件,都直接更新文件,而不是像一般的File,每次写入数据之前,还一定先把数据放在缓存里,然后才把缓存更新到硬盘上。
seek(long pos) ; 这个对象中有一个文件指针,可以用来控制读写文件的位置。
读的时候,若是服务器成功返回数据,相应码是206(正好是我宿舍号),不是200
public class RangeTest {
/**
* @param args
*/
private static String path="http://127.0.0.1:8080/test.avi";
private static int threadcount=3;
public static void main(String[] args) throws Exception {
URL url=new URL(path);
HttpURLConnection connection=(HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
int code=connection.getResponseCode();
if(code==200)
{
int leng=connection.getContentLength();
RandomAccessFile file=new RandomAccessFile("test.avi", "rw");
file.setLength(leng);
for(int i=0;i<threadcount;++i)
{
int start=leng/threadcount*i;
int end=leng/threadcount*(i+1)-1;
if(i==threadcount-1){
end=leng-1;
}
new DownLoadPart(start, end, i+1).start();
}
}
}
private static class DownLoadPart extends Thread{
private int start;
private int end;
private int threadid;
public DownLoadPart(int start, int end, int threadid) {
super();
this.start = start;
this.end = end;
this.threadid = threadid;
}
@Override
public void run() {
URL url;
try {
url = new URL(path);
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(10000);
//设置请求头,表示开始下载的起始位置和终止位置。
conn.setRequestProperty("Range", "bytes="+start+"-"+end);
if(conn.getResponseCode()==206){
System.out.println("线程"+threadid+"开始下载");
InputStream is=conn.getInputStream();
byte[]buf=new byte[1024];
RandomAccessFile file=new RandomAccessFile("test.avi", "rw");
file.seek(start);
int len;
while((len=is.read(buf))>0){
file.write(buf,0,len);
}
is.close();
System.out.println("线程"+threadid+"下载完毕");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
断点续传只做了几处修改:
1.新建几个日志文件,每个日志文件对应一个线程。
2.确定下载区间范围前,先考虑是否存在相应的日志文件,若存在,则读取文件并转为int(long更好点),作为起始区间。
3.下载文件时,每进行一次读写操作,则修改本线程的日志文件,记录当前读到的位置。
完善的:
public class RangeTest {
/**
* @param args
*/
private static String path="http://127.0.0.1:8080/test.avi";
private static int threadcount=3;
public static void main(String[] args) throws Exception {
URL url=new URL(path);
HttpURLConnection connection=(HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
int code=connection.getResponseCode();
if(code==200)
{
int leng=connection.getContentLength();
RandomAccessFile file=new RandomAccessFile("test.avi", "rw");
file.setLength(leng);
for(int i=0;i<threadcount;++i)
{
int start=leng/threadcount*i;
int end=leng/threadcount*(i+1)-1;
File log=new File("test"+(i+1)+".log");
if(log!=null&&log.length()>0){
FileInputStream fis=new FileInputStream(log);
BufferedReader br=new BufferedReader(new InputStreamReader(fis));
String st=br.readLine();
start=Integer.parseInt(st);
}
if(i==threadcount-1){
end=leng-1;
}
new DownLoadPart(start, end, i+1).start();
}
}
}
private static class DownLoadPart extends Thread{
private int start;
private int end;
private int threadid;
public DownLoadPart(int start, int end, int threadid) {
super();
this.start = start;
this.end = end;
this.threadid = threadid;
}
@Override
public void run() {
URL url;
try {
url = new URL(path);
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(10000);
//设置请求头,表示开始下载的起始位置和终止位置。
conn.setRequestProperty("Range", "bytes="+start+"-"+end);
if(conn.getResponseCode()==206){
System.out.println("线程"+threadid+"开始下载"+start+"-"+end);
InputStream is=conn.getInputStream();
byte[]buf=new byte[1024];
RandomAccessFile file=new RandomAccessFile("test.avi", "rw");
file.seek(start);
int len;
int coun=0;
while((len=is.read(buf))>0){
file.write(buf,0,len);
coun+=len;
int position=coun+start;
RandomAccessFile temp=new RandomAccessFile("test"+threadid+".log", "rwd");
temp.write(String.valueOf(position).getBytes());
}
is.close();
System.out.println("线程"+threadid+"下载完毕");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
最后将其应用到Android上:
注意:与进度相关的控件都可以在子线程,直接更新,因为他们已经封装好了,ProgressBar,SeekBar,ProgressDialog(进度条对话框)
ProgressBar
setMax设置最大值
setProgress设置当前进度
ViewGroup(LinearLayout,RelativeLayout ,FrameLayout)
View.inflate(context,R.layout.**,viewgroup);
如果最后一个参数传入了ViewGroup,则从xml转化来的view对象就会作为这个viewgroup的子view,添加到这个viewgroup中,
ViewGroup.removeAllViews();删除所有子控件
getChildAt(index); 可以根据索引找到子控件。
Toast与View.inflate等都会用到,注意:getApplicationContext() 获取应用的上下文,Application这个类生命周期最长。与ServletContext类似。
若是使用MainActivity.this 有时候不是很安全,比如说Toast.makeText(MainActivity.this, "XXX",Toast.LONG).show(); 展示的过程中,你点击返回,垃圾回收机制应当回收MainActivity,但MainActivity还在被Toast引用,所以无法回收,时间久了便会造成内存泄漏。
只要 程序还在跑getApplicationContext()一定是存在的,因此这个资源大部分情况下都会被回收,所以getApplication()在绝大部分情况下,都是安全的,只有当其在操作对话框时,不太安全,需要使用MainActivity。
核心代码:
public class MainActivity extends Activity implements OnClickListener{
private EditText et_url;
private EditText et_count;
private Button bt_download;
private LinearLayout ll_progressbar;
private String path="http://10.0.2.2:8080/test.avi";
private int threadcount;
private String savePath;
private int partSize;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_url=(EditText) findViewById(R.id.et_url);
et_count=(EditText) findViewById(R.id.et_count);
bt_download=(Button) findViewById(R.id.bt_download);
ll_progressbar=(LinearLayout) findViewById(R.id.ll_progresses);
bt_download.setOnClickListener(this);
savePath = this.getCacheDir().getAbsolutePath()+"/test.avi";
}
public void onClick(View v) {
ll_progressbar.removeAllViews();
String num=et_count.getText().toString().trim();
threadcount=Integer.parseInt(num);
for(int i=0;i<threadcount;++i){
View.inflate(MainActivity.this, R.layout.progress, ll_progressbar);
}
new Thread(){
@Override
public void run() {
try{
URL url=new URL(path);
HttpURLConnection connection=(HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
int code=connection.getResponseCode();
if(code==200)
{
int leng=connection.getContentLength();
RandomAccessFile file=new RandomAccessFile(savePath, "rw");
file.setLength(leng);
partSize=leng/threadcount;
for(int i=0;i<threadcount;++i)
{
int start=leng/threadcount*i;
int end=leng/threadcount*(i+1)-1;
if(i==threadcount-1){
end=leng-1;
}
new DownLoadPart(start, end, i+1).start();
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}.start();
}
private class DownLoadPart extends Thread{
private int start;
private int end;
private int threadid;
private ProgressBar progressbar;
public DownLoadPart(int start, int end, int threadid) {
super();
this.start = start;
this.end = end;
this.threadid = threadid;
//参数不是threadid,往往有at或Index等字样表明下标从0开始
progressbar=(ProgressBar) ll_progressbar.getChildAt(threadid-1);
//这里的start表示这一个线程的最开始下载的位置,而不是将要下载的位置。
progressbar.setMax(end-start);
}
@Override
public void run() {
try {
File log=new File(MainActivity.this.getCacheDir().getAbsolutePath()+"/test"+threadid+".log");
if(log!=null&&log.length()>0){
FileInputStream fis=new FileInputStream(log);
BufferedReader br=new BufferedReader(new InputStreamReader(fis));
String st=br.readLine();
//这里的start表示将要下载的位置。
start=Integer.parseInt(st);
}
URL url;
url = new URL(path);
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(10000);
//设置请求头,表示开始下载的起始位置和终止位置。
conn.setRequestProperty("Range", "bytes="+start+"-"+end);
if(conn.getResponseCode()==206){
System.out.println("线程"+threadid+"开始下载"+start+"-"+end);
InputStream is=conn.getInputStream();
byte[]buf=new byte[1024];
RandomAccessFile file=new RandomAccessFile(savePath, "rw");
file.seek(start);
int len;
int coun=0;
while((len=is.read(buf))>0){
file.write(buf,0,len);
coun+=len;
int position=coun+start;
RandomAccessFile temp=new RandomAccessFile(MainActivity.this.getCacheDir().getAbsolutePath()+"/test"+threadid+".log", "rwd");
temp.write(String.valueOf(position).getBytes());
//这里不可以写coun,coun表示这一次运行开始到现在的下载量
//这里表示当前下载的位置减最开始下载的位置
progressbar.setProgress(position-partSize*(threadid-1));
}
log.delete();
is.close();
System.out.println("线程"+threadid+"下载完毕");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
使用开源项目:
这是最常用的方法
导入 xUtils-2.6.14.jar
public class MainActivity extends Activity implements OnClickListener{
private EditText et_url;
private ProgressBar bar;
private Button bt_download;
private String path="http://10.0.2.2:8080/test.avi";
private String savePath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_url=(EditText) findViewById(R.id.et_url);
bt_download=(Button) findViewById(R.id.bt_download);
bar=(ProgressBar) findViewById(R.id.pb_progress);
bt_download.setOnClickListener(this);
savePath = this.getCacheDir().getAbsolutePath()+"/test.avi";
}
public void onClick(View v) {
HttpUtils utils =new HttpUtils();
utils.download(path, savePath, true, new RequestCallBack<File>() {
@Override
public void onLoading(long total, long current, boolean isUploading) {
bar.setMax((int) total);
bar.setProgress((int) current);
}
//表示下载完成
@Override
public void onSuccess(ResponseInfo<File> arg0) {
}
@Override
public void onFailure(HttpException arg0, String arg1) {
}
});
}
}
download方法 第一个参数表示url路径,第二个参数表示保存路径,第三个参数表示是否支持断点续传,第四个参数表示是否下载完成重命名