1.使用httpurlconnection方式把数据提交到服务器
基于什么协议 http
get方式:组拼url地址把数据组拼到url上 有大小限制 1kb 4kb
post方式:post方式提交安全 没有大小限制
get方式提交数据,代码如下:
// 点击按钮 进行get方式提交数据
public void click1(View v) {
new Thread() {
public void run() {
try {
// 2.获取用户名和密码
String name = et_username.getText().toString().trim();
String pwd = et_password.getText().toString().trim();
// 2.1定义get方式要提交的路径 小细节 如果提交中文要对name和pwd进行一个urlencode编码
String path = "http://192.168.10.98:8080/login/LoginServlet?username="
+ URLEncoder.encode(name, "utf-8")
+ "&password="
+ URLEncoder.encode(pwd, "utf-8") + "";
// (1)创建一个url对象 参数就是网址
URL url = new URL(path);
// (2)获取hrrpURLConnection连接对象
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
// (3)设置参数 发送get请求
conn.setRequestMethod("GET");// 默认请求 就是get 要大写
// (4)设置网络连接的超时时间
conn.setConnectTimeout(5000);
// (5)获取服务器返回的状态码
int code = conn.getResponseCode();// 200 代表获取服务器资源全部成功
// 206请求部分资源成功
if (code == 200) {
// (6)获取服务器返回的数据 以流的形式返回
InputStream inputStream = conn.getInputStream();
// (6.1)把inputstream转换成string
String content = StreamTools.readStream(inputStream);
// (7)把服务器返回的数据展示到toast上 不能在子线程展示toast
showToast(content);
}
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
}
post方式和get区别
[1]路径不同 http://192.168.10.98:8080/login/LoginServlet
[2]请求方式post
[3]通过请求体的方式把数据写给服务器(以流的形式写给服务器)
post和get方式提交数据 区别 要多设置2个请求头信息
//设置头信息
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", data.length()+"");
post方式提交代码如下:
// [1]点击按钮,进行post方式提交数据
public void click2(View v) {
new Thread() {
public void run() {
try {
//[2]获取用户名和密码
String name=et_username.getText().toString().trim();
String pwd=et_password.getText().toString().trim();
//[2.1]定义post方式要提交的路径
String data="username="+URLEncoder.encode(name,"utf-8")+"&password="+URLEncoder.encode(pwd,"utf-8");//请求体的内容
//一 post和get方式提交数据 区别 路径不同
String path="http://192.168.10.98:8080/login/LoginServlet";
//(1)创建一个url对象 参数就是网址
URL url=new URL(path);
//(2)获取httpurlconnection对象
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
//二 post和get方式提交数据区别 设置请求方式是post
//(3)设置参数 发送post请求
conn.setRequestMethod("POST");//默认请求 就是get 要大写
//(4)设置网络超时时间
conn.setConnectTimeout(5000);
//三 post和get方式提交数据 区别 要多设置2个请求头信息
//设置头信息
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", data.length()+"");
//四 post和get方式提交数据 区别 把我们组好的数据提交给服务器 以流的形式提交
conn.setDoOutput(true);//设置一个标记 允许输出
conn.getOutputStream().write(data.getBytes());
//(5)获取服务器返回的状态码
int code=conn.getResponseCode();// 200 代表获取服务器资源全部成功 206请求部分资源成功
if(code==200){
//(6)获取服务器返回的数据 以流的形式返回
InputStream inputStream = conn.getInputStream();
//(6.1)把inputStream转换成string
String content=StreamTools.readStream(inputStream);
//(7)把服务器返回的数据展示到toast上 不能再子线程展示toast
showToast(content);
}
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
}
其中使用到的工具类:
public class StreamTools {
//把一个inputStream转换成一个String
public static String readStream(InputStream in) throws Exception{
//定义一个内存输出流
ByteArrayOutputStream baos=new ByteArrayOutputStream();
int len=-1;
byte[] buffer=new byte[1024];//1kb
while((len=in.read(buffer))!=-1){
baos.write(buffer,0,len);
}
in.close();
String content=new String(baos.toByteArray(),"utf-8");
return content;
}
}
2.乱码问题解决
编码与解码:
1.文字->(数字01代码):编码encode:就是把看得懂的内容,转换成看不懂的内容。
2.(数字01代码) -> 文字 : 解码decode:就是把看不懂的内容,转换成看懂的内容。
【注意】:url的中文要URLEncoder.encode(name, "utf-8"),只对中文编码
服务器与浏览器交互时对数据的处理方式
Html使用utf-8编码(变成流二进制)
---1--->Tomcat使用ISO-8859-1解码,编解码不一致
---2--->Serlvet直接使用,必然乱码
---3先编码--->使用ISO-8859-1编码(变成二进制)
---4后解码--->使用UTF-8解码
用这种:new String(name.getBytes("iso-8859-1"), "utf-8")
1.浏览器发送数据
html使用的编码格式是utf-8,所以浏览器中输入了某一数据,浏览器会先将这个数据以utf-8作为编码表将它进行编码,然后把编码后的01数据发送到服务器
2.tomcat服务器的特点:
而tomcat服务器默认使用的是ISO-8859-1的编码表,因此当服务器接收到浏览器发送的01数据时,服务器会默认使用ISO-8859-1的编码表将其进行解码
3.英文与标点不会乱码的原因:
ISO-8859-1与utf-8都是兼容ASCII码的,因此从浏览器发送到服务器中属于ASCII码的内容时不会乱码的,因为它们编解码都是按照ASCII码
4.汉字会出现乱码的原因:
ISO-8859-1不支持汉字,即使时GBK这种支持汉字的,通过他们编码再使用utf-8编码也是会出现乱码;因为ISO-8859-1不支持汉字,没有汉字对应的编码表,而GBK中同一个汉字对应的01代码与utf-8对应的01代码不一致,因为他们单个字符的占的字节数时不同的。
5.tomcat服务器接收数据:
当tomcat服务器接收到从服务器发来的01数据时,tomcat服务器会将它用ISO-8859-1编码表进行解码。因此这个01数据在浏览器中代表的时汉字时,那么tomcat用ISO-8859-1编码表解码后一定时一个乱码字符
6.解决服务端乱码问题:【new String(name.getBytes("iso-8859-1"), "utf-8")】
要想在服务器端获取的数据是一个表示正确的汉字,就需要把这个乱码的字符先用ISO-8859-1编码成01数据,然后再通过utf-8编码表解码成对应的字符,然后存入服务器中字符串一个变量内。这个字符串显示的就是与浏览器相同的正常的汉字。
7.解决浏览器端乱码问题:【或者用流输出utf-8编码后的数据(01数据、byte)】
如果把在服务器端显示正常中文字符串发送给浏览器显示,也是乱码的结果。因为这个汉字会先用ISO-8859-1编码表进行编码成01数据,之后发送给浏览器,浏览器会以utf-8编码表进行解码,那解码出来的肯定是一个乱码。
要想让浏览器显示出与服务器端一样的正常中文字符,需要在服务器端先把汉字字符串用utf-8的编码表进行编码,再用ISO-8859-1编码表进行解码得到一个乱码的字符串。然后服务器在发送数据时会将这个乱码字符串先用ISO-8859-1编码表进行编码后发送到浏览器。浏览器接收到这个01数据后使用utf-8进行解码,就可以得到一个正常的中文字符啦~~~
3.以httpclient方式把数据提交到服务器
//点击按钮 进行get方式提交数据
public void click1(View v){
new Thread(){
public void run() {
try {
String name=et_username.getText().toString().trim();
String pwd=et_password.getText().toString().trim();
//2.1定义get方式要提交的路径 小细节 如果提交中文要对name和pwd进行urlencode编码
String path="http://192.168.10.98:8080/login/LoginServlet?username="+
URLEncoder.encode(name,"utf-8")+"&password="+URLEncoder.encode(pwd,"utf-8")+"";
//3.获取httpclient的实例
DefaultHttpClient client=new DefaultHttpClient();
//3.1准备get请求 定义一个httpget实现
HttpGet get=new HttpGet(path);
//3.2执行一个get请求
HttpResponse response = client.execute(get);
//4.获取服务器返回的状态码
int code = response.getStatusLine().getStatusCode();
if(code==200){
//5.获取服务器返回的数据 以流的形式返回
InputStream inputStream = response.getEntity().getContent();
//6.把流转换成字符串
String content=StreamTools.readStream(inputStream);
//7.展示结果
showToast(content);
}
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
}
//1.点击按钮 进行post方式提交数据
public void click2(View v){
new Thread(){
public void run() {
try {
//2.获取用户名和密码
String name=et_username.getText().toString().trim();
String pwd=et_password.getText().toString().trim();
String path="http://192.168.10.98:8080/login/LoginServlet";
//3.以httpclient方式进行 post提交
DefaultHttpClient client=new DefaultHttpClient();
//3.1准备post请求
HttpPost post=new HttpPost(path);
//3.1.0准备parameters
List<NameValuePair> lists=new ArrayList<NameValuePair>();
//3.1.1准备NameValuePair 实际上就是我们要提交的用户名和密码 key是服务器key:username
NameValuePair nameValuePair=new BasicNameValuePair("username", name);
NameValuePair pwdValuePair=new BasicNameValuePair("password", pwd);
//3.1.2把nameValuePair和pwdValuePair加入到集合中
lists.add(nameValuePair);
lists.add(pwdValuePair);
//3.1.3准备entity
UrlEncodedFormEntity entity=new UrlEncodedFormEntity(lists, HTTP.UTF_8);
//3.2准备post方式提交的正文 以实体形式准备(键值对形式)
post.setEntity(entity);
HttpResponse response = client.execute(post);
//4.获取服务器返回的状态码
int code = response.getStatusLine().getStatusCode();
if(code==200){
//5.获取服务器返回的数据 以流的形式返回
InputStream inputStream = response.getEntity().getContent();
//6.把流转换成字符串
String content = StreamTools.readStream(inputStream);
//7.展示结果
showToast(content);
}
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
}
4.开源项目方式(asynchttpclient)把数据提交到服务器
先把com包 源码包拷贝到当前工程
开源项目get方式提交代码:
//点击按钮 进行get方式提交数据
public void click1(View v){
String name=et_username.getText().toString().trim();
String pwd=et_password.getText().toString().trim();
String path=null;
//2.1定义get方式要提交的路径 小细节 如果提交中文要对name和pwd进行一个urlencode编码
try {
path="http://192.168.10.98:8080/login/LoginServlet?username="+
URLEncoder.encode(name,"utf-8")+"&password="+URLEncoder.encode(pwd,"utf-8")+"";
} catch (Exception e) {
e.printStackTrace();
}
//3.使用开源项目进行get请求
//3.1创建asynchttpclient
AsyncHttpClient client=new AsyncHttpClient();
//3.2进行get请求
client.get(path, new AsyncHttpResponseHandler() {
//请求成功的回调方法
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
try {
Toast.makeText(getApplicationContext(), new String(responseBody,"utf-8"), 1).show();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
//请求失败
@Override
public void onFailure(int statusCode, Header[] headers,
byte[] responseBody, Throwable error) {
// TODO Auto-generated method stub
}
});
}
开源项目post方式提交代码:
//1.点击按钮 进行post方式提交数据
public void click2(View v){
//2.获取用户名和密码
String name=et_username.getText().toString().trim();
String pwd=et_password.getText().toString().trim();
String path="http://192.168.10.98:8080/login/LoginServlet";
//3.1.0创建asynchttpclient
AsyncHttpClient client=new AsyncHttpClient();
//3.1.1准备请求体的内容
RequestParams params=new RequestParams();
params.put("username", name);
params.put("password", pwd);
//3.2进行post请求 params 请求参数封装
client.post(path, params, new AsyncHttpResponseHandler() {
//请求成功 登录成功
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
try {
Toast.makeText(getApplicationContext(), new String(responseBody,"utf-8"), 1).show();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(int statusCode, Header[] headers,
byte[] responseBody, Throwable error) {
// TODO Auto-generated method stub
}
});
}
总结
[1]httpurlconnetion
[2]httpclient(了解 没有人用)
[3]开源项目(asynchttpclient)
5.javase多线程下载
设置请求服务器文件的位置
//设置一个请求头Range(作用告诉服务器每个线程下载的开始位置和结束位置)
conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
为什么多线程能够提高速度 (分块传输)
[1]获取文件大小
[2]在客户端创建一个大小和服务器一模一样的文件(为了提前申请空间)
[2.1]算出每个线程下载的开始位置和结束位置
[3]开多个线程去下载文件
[4]知道每个线程什么时候下载完毕了
每个线程下载的计算公式(最后一个线程的结束位置为length-1)
blockSize=length(10)/thread(3)=3
{0 1 2} {3 4 5} {6 7 8 9}
0 0~2 0*blockSize ~ 1*blockSize-1 0~2
1 3~5 1*blockSize ~ 2*blockSize-1 3~5
n n*blockSize ~ (n+1)*blockSize-1
m m*blockSize~length-1
6.断点续传实现
toy app 练习api 360市场 安卓市场 机锋市场 谷歌市场 4000多行 2年时间
[1]没有sd卡 判断一下sd卡状态是否可用
[2]也没有判断手机网络类型
[3]下载文件之前要先判断 文件是否是一个病毒文件
......
目的:RandomAccessFile
普通java项目,下载到项目下,代码如下:
public class MutilDownLoad {
//1.定义下载的路径
private static String path="http://192.168.10.98:8080/pic.jpg";
private static final int threadCount=3;//假设开三个线程
private static int runningThread;//代表当前正在运行的线程
public static void main(String[] args) {
//一 获取服务器文件的大小 要计算每个线程下载的开始位置和结束位置
try {
//(1)创建一个url对象 参数就是网址
URL url=new URL(path);
//(2)获取HttpURLConnection连接对象
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
//(3)设置参数 发送get请求
conn.setRequestMethod("GET");//默认请求 就是get 要大写
//(4)设置连接网络的超时时间
conn.setConnectTimeout(5000);
//(5)获取服务器返回的状态码
int code=conn.getResponseCode();//200 代表获取服务器资源全部成功 206请求部分资源
if(code==200){
//(6)获取服务器文件的大小
int length=conn.getContentLength();
//6.1把线程的数量赋值给正在运行的线程
runningThread=threadCount;
System.out.println("length:"+length);
//二 创建一个大小和服务器一模一样的文件 目的提前把空间申请出来
RandomAccessFile rafAccessFile=new RandomAccessFile("pic.jpg", "rw");
rafAccessFile.setLength(length);
//(7)算出每个线程下载的大小
int blockSize=length/threadCount;
//三 计算每个线程下载的开始位置和结束位置
for (int i = 0; i < threadCount; i++) {
int startIndex=i*blockSize;//每个线程下载的开始位置
int endIndex=(i+1)*blockSize-1;
//特殊情况就是最后一个线程
if(i==threadCount-1){
//说明是最后一个线程
endIndex=length-1;
}
System.out.println("线程id:"+i+"理论下载的位置:"+startIndex+"-----"+endIndex);
//四 开启线程去服务器下载文件
DownLoadThread downLoadThread=new DownLoadThread(startIndex, endIndex, i);
downLoadThread.start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//定义线程去服务器下载文件
private static class DownLoadThread extends Thread{
//通过构造方法把每个线程下载的开始位置和结束位置传递进来
private int startIndex;
private int endIndex;
private int threadId;
public DownLoadThread(int startIndex, int endIndex, int threadId) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
}
public void run() {
//四 实现去服务器下载文件的逻辑
try {
//(1)创建一个url对象 参数就是网址
URL url=new URL(path);
//(2)获取HttpURLConnection连接对象
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
//(3)设置参数 发送get请求
conn.setRequestMethod("GET");//默认请求 就是get 要大写
//(4)设置连接网络的超时时间
conn.setConnectTimeout(5000);
//(4.0)如果中间断过 继续上次的位置 继续下载 从文件钟读取上次下载的位置
File file=new File(getFilename(path)+threadId+".txt");
if(file.exists()&&file.length()>0){
FileInputStream fis=new FileInputStream(file);
BufferedReader bufr=new BufferedReader(new InputStreamReader(fis));
String lastPositionn = bufr.readLine();//读取出来的内容就是上一次下载的位置
int lastPosition=Integer.parseInt(lastPositionn);
//(4.0.1)要改变一下 startIndex位置
startIndex=lastPosition+1;
System.out.println("线程id:"+threadId+"真实下载的位置:"+startIndex+"----"+endIndex);
fis.close();//关闭流
}
//(4.1)设置一个请求头Range(作用告诉服务器每个线程下载的开始位置和结束位置)
conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
//(5)获取服务器返回的状态码
int code = conn.getResponseCode();//200代表获取服务器资源全部成功 206请求部分资源 成功
if(code==206){
//6.创建随机读写文件对象
RandomAccessFile raf=new RandomAccessFile("pic.jpg", "rw");
//7.每个线程要从自己的位置开始写
raf.seek(startIndex);
InputStream in=conn.getInputStream();
//8.把数据写到文件中
int len=-1;
byte[] buffer=new byte[1024*1024];//1Mb
int total=0;//代表当前线程下载的大小
while((len=in.read(buffer))!=-1){
raf.write(buffer,0,len);
total+=len;
//9.实现断点续传 就是把当前线程下载的位置 给存起来 下次再下载的时候 就是按照上次下载的位置继续下载 就可以了
int currentThreadPosition=startIndex+total;//比如就存到一个普通的.txt
//10.用来存当前线程下载的位置
RandomAccessFile raff=new RandomAccessFile(getFilename(path)+threadId+".txt", "rwd");
raff.write(String.valueOf(currentThreadPosition).getBytes());
raff.close();
}
raf.close();//关闭流 释放资源
System.out.println("线程id:"+threadId+"---下载完毕了");
//11.把.txt文件删除 每个线程具体什么时候下载完毕了 我们不知道
synchronized (DownLoadThread.class) {
runningThread--;
if(runningThread==0){
//说明所有的线程都执行完毕了 就把.txt文件删除
for (int i = 0; i < threadCount; i++) {
File deleteFile=new File(getFilename(path)+i+".txt");
deleteFile.delete();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//获取文件的名字 "http://192.168.11.73:8080/pic.jpg"
public static String getFilename(String path){
int start=path.lastIndexOf("/")+1;
return path.substring(start);
}
}
7.断点续传逻辑移植到Android上
[1]权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
[2]activity_main.xml代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<EditText
android:id="@+id/et_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="http://192.168.10.98:8080/pic.jpg"
android:hint="请输入下载路径"
/>
<EditText
android:id="@+id/et_threadCount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入线程的数量"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="click"
android:text="下载"
/>
<LinearLayout
android:id="@+id/ll_pb"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
></LinearLayout>
</LinearLayout>
[3]item.xml代码如下:
<?xml version="1.0" encoding="utf-8"?>
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
[4]MainActivity代码如下:
private EditText et_path;
private EditText et_threadCount;
private LinearLayout ll_pb_layout;
private static int runningThread;//代表当前正在运行的线程
private int threadCount;
private List<ProgressBar> pbLists;//用来存进度条的引用
private String path;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1.找到我们关心的控件
et_path = (EditText) findViewById(R.id.et_path);
et_threadCount = (EditText)findViewById(R.id.et_threadCount);
ll_pb_layout = (LinearLayout) findViewById(R.id.ll_pb);
//2.添加一个集合 用来存进度条的引用
pbLists = new ArrayList<ProgressBar>();
}
//点击按钮实现下载的逻辑
public void click(View v){
//2.获取下载的路径
path = et_path.getText().toString().trim();
//3.获取线程的数量
String threadCountt=et_threadCount.getText().toString().trim();
//3.0先移除进度条 再添加
ll_pb_layout.removeAllViews();
threadCount = Integer.parseInt(threadCountt);
pbLists.clear();
for (int i = 0; i < threadCount; i++) {
//3.1把定义的item布局转换成一个view对象
ProgressBar pbView = (ProgressBar) View.inflate(getApplicationContext(), R.layout.item, null);
//3.2把pbView添加到集合钟
pbLists.add(pbView);
//4.动态的添加进度条
ll_pb_layout.addView(pbView);
}
//5.开始移植 联网 获取文件长度
new Thread(){
public void run() {
//一 获取服务器文件的大小 要计算每个线程下载的开始位置和结束位置
try {
//(1)创建一个url对象 参数就是网址
URL url=new URL(path);
//(2)获取HttpURLConnection的连接对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//(3)设置参数 发送get请求
conn.setRequestMethod("GET");//默认请求 就是get 要大写
//(4)设置连接网络的超时时间
conn.setConnectTimeout(5000);
//(5)获取服务器返回的状态码
int code = conn.getResponseCode();//200 代表获取服务器资源全部成功 206请求部分资源
if(code==200){
//(6)获取服务器文件的大小
int length=conn.getContentLength();
//(6.1)把线程的数量赋值给正在运行的线程
runningThread=threadCount;
System.out.println("length:"+length);
//二 创建一个大小和服务器一模一样的文件 目的是提前把空间申请出来
RandomAccessFile rafAccessFile=new RandomAccessFile(getFilename(path), "rw");
rafAccessFile.setLength(length);
//(7)算出每个线程下载的大小
int blockSize=length/threadCount;
//三 计算每个线程下载的开始位置和结束位置
for (int i = 0; i < threadCount; i++) {
int startIndex=i*blockSize;//每个线程下载的开始位置
int endIndex=(i+1)*blockSize-1;
//特殊情况 就是最后一个线程
if(i==threadCount-1){
//说明是最后一个线程
endIndex=length-1;
}
System.out.println("线程id:"+i+"理论下载的位置:"+startIndex+"---"+endIndex);
//四 开启线程去服务器下载文件
DownLoadThread downLoadThread=new DownLoadThread(startIndex, endIndex, i);
downLoadThread.start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
}
//定义线程去服务器下载文件
private class DownLoadThread extends Thread{
//通过构造方法把每个线程下载的开始位置和结束位置传递进来
private int startIndex;
private int endIndex;
private int threadId;
private int PbMaxSize;//代表当前线程下载的最大值
//如果中断过 获取上次下载的位置
private int pblastPosition;
public DownLoadThread(int startIndex, int endIndex, int threadId) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
}
@Override
public void run() {
//四 实现去服务器下载文件的逻辑
try {
//(0)计算当前进度条的最大值
PbMaxSize=endIndex-startIndex;
//(1)创建一个url对象 参数就是网址
URL url=new URL(path);
//(2)获取httpurlconnection连接对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//(3)设置参数 发送get请求
conn.setRequestMethod("GET");//默认请求 就是get 要大写
//(4)设置连接网络的超时时间
conn.setConnectTimeout(5000);
//4.0如果中间断过 继续上次的位置 继续下载 从文件钟读取上次下载的位置
File file=new File(getFilename(path)+threadId+".txt");
if(file.exists()&&file.length()>0){
FileInputStream fis = new FileInputStream(file);
BufferedReader bufr = new BufferedReader(new InputStreamReader(fis));
String lastPositionn = bufr.readLine();//读取出来的内容就是上一次下载的位置
int lastPosition=Integer.parseInt(lastPositionn);
//4.0给我们定义的进度条位置 赋值
pblastPosition=lastPosition-startIndex;
//4.0.1 要改变一下 startIndex位置
startIndex=lastPosition+1;
System.out.println("线程id:"+threadId+"真实下载的位置:"+startIndex+"---"+endIndex);
fis.close();//关闭流
}
//4.1设置一个请求头Range(作用告诉服务器每个线程下载的开始位置和结束位置)
conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
//(5)获取服务器返回的状态码
int code = conn.getResponseCode();//200 代表获取服务器资源钱全部成功 206请求部分资源成功
if(code==206){
//(6)创建随机读取文件对象
RandomAccessFile raf=new RandomAccessFile(getFilename(path), "rw");
//(6)每个线程都要从自己的位置开始写
raf.seek(startIndex);
InputStream in = conn.getInputStream();
//(7)把数据写到文件中
int len=-1;
byte[] buffer=new byte[1024*1024];//1Mb
int total=0;//代表当前线程下载的大小
while((len=in.read(buffer))!=-1){
raf.write(buffer,0,len);
total+=len;
//8.实现断点续传 就是把当前线程下载的位置 给存起来 下载再下载的时候 就是按照上次下载的位置继续下载 就可以了
int currentThreadPosition=startIndex+total;//比如就存到一个普通的.txt文件中
//9.用来存当前线程下载的位置
RandomAccessFile raff=new RandomAccessFile(getFilename(path)+threadId+".txt", "rw");
raff.write(String.valueOf(currentThreadPosition).getBytes());
raff.close();
//10.设置一下当前进度条的最大值 和 当前进度
pbLists.get(threadId).setMax(PbMaxSize);//设置进度条的最大值
pbLists.get(threadId).setProgress(pblastPosition+total);//设置当前进度条的当前进度
}
raf.close();//关闭流 释放资源
System.out.println("线程id:"+threadId+"---下载完毕了");
//10.把.txt删除 每个线程具体什么时候下载完毕了 我们不知道
synchronized (DownLoadThread.class) {
runningThread--;
if(runningThread==0){
//说明所有的线程都执行完毕了 就把.txt文件删除
for (int i = 0; i < threadCount; i++) {
File deleteFile=new File(getFilename(path)+i+".txt");
deleteFile.delete();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//获取文件的名字 "http://192.168.10.98:8080/pic.jpg"
public String getFilename(String path){
int start=path.lastIndexOf("/")+1;
String substring=path.substring(start);
String fileName=Environment.getExternalStorageDirectory().getPath()+"/"+substring;
return fileName;
}
8.开源项目实现多线程下载(xutils)
先把com包 源码包拷贝到当前工程
//点击按钮实现断点续传下载逻辑
public void click(View v){
//[1]获取下载路径
String path = et_path.getText().toString().trim();
//[2]创建httputils对象
HttpUtils http=new HttpUtils();
//[3]实现断点下载 target下载文件的路径 autoResume是否支持断点续传的逻辑
http.download(path, "/mnt/sdcard/xpg.mp3", true, new RequestCallBack<File>() {
//下载成功
@Override
public void onSuccess(ResponseInfo<File> responseInfo) {
Toast.makeText(getApplicationContext(), "下载成功", 1).show();
}
@Override
public void onLoading(long total, long current, boolean isUploading) {
//total 代表总进度 current 代表当前进度
pb.setMax((int)total);
pb.setProgress((int)current);
}
//下载失败的回调
@Override
public void onFailure(HttpException error, String msg) {
// TODO Auto-generated method stub
}
});
}
9.今日总结
[1]httpurlconnection用于发送或者接收数据
get 组拼url地址把数据组拼到url
post 要自己组拼正文 请求头信息
[2]httpclient 了解
[3]asynchttpclient
[4]多线程下载
1.获取文件大小
2.在客户端创建一个大小和服务器一模一样的文件(为了提前申请空间)
3.算出每个线程下载的开始位置和结束位置
需要考虑 最后一个线程结束位置 文件长度-1
4.开启一个线程去实现下载逻辑 httpurlconnection
设置Range 还要注意服务器返回的状态码不是200,是206
5.断点的逻辑 就是把当前线程下载的位置给保存起来 RandomAccessFile 直接同步到底层设备
6.移植到Android 算出进度条的进度 集合存进度条的引用
7.与进度相关的都可以在子线程直接更新ui