通过安卓的项目向服务端提交参数。用了三种方式,一种是httpUrlConnection,一种是httpClient,还有一种是使用开源项目去提交参数。掌握多线程下载和断点续传的原理。多线程下载可以把一个文件分成多份去下载。使用多线程下载如果你的带宽比较大,可能会突破对线程流量的限制。在服务端可以对每一个下载的线程的流量/网速/带宽设置一个限制。比如一个线程最多就是100KB/s,如果只是一个线程下载就是100KB/S,你要是开5个线程就是500KB/S.当然了服务端肯定也可以对一个ip连接过来的线程数进行限制。所以如果你开的线程过多不见得就一定会起到比较好的效果。百度网盘、迅雷下载都搞会员,用会员下载的速度就比较快。非会员下载速度就会慢一些。所以实际上就是是否是会员对线程的速度限制是不一样。所以你是一个非会员即使你开了多个线程最终对你的线程的个数是有限制的,连100个可能只有5个线程有流量,其他的线程压根就没有数据。
多线程下载什么时候能够起到提高速度的作用呢?第一个就是不能突破你自己的带宽,你本身就是一个小吸水管,你开再多的线程也不可能突破你自己带宽的限制。你是1M的带宽开再多的线程也是1M的带宽。多线程下载也是受服务端的限制的,比如服务端要求一个ip只能连5个线程,那你开再多的线程也没有用。
多线程下载原理:多个线程下载同一个文件怎么实现这个事情呢?
服务端有这么一个文件。正常情况下一个线程下载就相当于把服务端的文件Ctrl+C Ctrl+V复制到客户端。现在要换一种做法了,服务端得支持多线程读,客户端得支持多线程写同一个文件。服务端得支持多线程读取同一个文件,就是你这个文件可以分份。如果这个东西不能分份那你就不能多线程下载了。服务端想能够多个线程,相当于多个人干一个活,首先任务得支持分份,它能拆分。破镜不能重圆,但是文件可以支持分份。把文件分成几份,一个线程拿一份。多线程读,多线程写,首先服务端得支持多线程请求数据,然后客户端数据拿下来了去写。之前写数据保存一个文件咱们用的API是File。比如我现在这一个文件用File,怎么去让它分成好几份去写成同一个东西?File没有这方法。File只能是给它起一个名字,起一个名字这就是一个File对象。所以客户端这边用File还是不行。
服务端有一个请求头叫Range,Range接收这么两个参数,一个叫做开始的索引,一个叫做结束的索引。比如bytes=10-20,就是把当前这个文件的第10个byte-第20个byte这段内容给它拿下来,这个是服务端。首先服务端它得支持Range头。服务端支持Range,通过Range这个方法把数据给它请求下来。所以可以到服务端获取一下整个文件的长度。如果是三个线程接着下载,可以算一下每一个线程究竟下载多大的数据。从0-9一共是10个byte。所以说虽说不能均分,但是呢它的这个量是很少的。不管你这文件多大,最终三个线程一除,顶多就是每一个线程之间差一个byte,差一个字节。差一个字节这个量很小啊,那如果是五个线程顶多也就不会超过5个byte。所以说虽说不一定会均分,但是实际上由于这个byte单位是很小的,所以差不多可以做到咱们每一个线程下载的量是一样的。现在的网速动辄就是几兆或者是10几兆,对于这一个byte来讲就是一眨眼的事情。所以说这个倒影响不大。
只要服务端支持Range头就可以把文件分份。继而我开启多个线程,每个线程我请求的数据范围不同。三个线程各干各的,分别去下载。下载下来之后得在客户端把它们三拼成一个文件。拼的时候用File是不行的。在客户端要使用API RandomAccessFile.RandomAccessFile 支持随机读写文件的任意位置。seek(long pos)到具体的这么一个索引位置上,可以从这个位置开始对文件进行读取和写入。
首先服务端必须得支持Range头,服务端如果不支持Range头你是无法进行多线程下载的。客户端写数据保存文件的时候通过RandomAccessFile把这个内容给它多线程地保存成同一份文件。所以这个就是最基本的多线程下载的原理。
Range头的用法。先在java写,然后移植到安卓。
package com.itheima.multiThreadDownload;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
//import java.net.MalformedURLException;
import java.net.URL;
import com.sun.org.apache.xerces.internal.util.URI.MalformedURIException;
//import java.net.URLConnection;
public class RangeTest {
public static void main(String[] args) {
String path = "http://127.0.0.1:8080/RELEASE-NOTES.txt";
try {
URL url = new URL(path);//在java的代码里联网没有必要开线程了
HttpURLConnection openConnection = (HttpURLConnection) url.openConnection();
openConnection.setRequestMethod("GET");
openConnection.setConnectTimeout(10000);
openConnection.setRequestProperty("Range", "bytes=10-200");//Range头,只读取0-20字节这一部分的数据
if(openConnection.getResponseCode()==206){//加上Range头之后服务端成功返回的响应码是206
//可以指定Range头的开始索引和结束索引
//通过Range头可以下载不同的数据的部分
InputStream inputStream = openConnection.getInputStream();
String stringFromStream = getStringFromStream(inputStream);
System.out.println(stringFromStream);
}
} catch (Exception e) {
//}catch(MalformedURIException e){
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static String getStringFromStream(InputStream inputStream) {
// TODO Auto-generated method stub
//把流转化为字符串
ByteArrayOutputStream baso = new ByteArrayOutputStream();
int len = -1;
byte[] buffer = new byte[1024];
try {
while((len=inputStream.read(buffer))!=-1){
baso.write(buffer, 0, len);
}
inputStream.close();
byte[] byteArray = baso.toByteArray();
return new String(byteArray);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}