网络编程
1. 使用HttpClient发送get请求
HttpClient是Apache开发的第三方框架,Google把它封装到了Android API中,用于发送HTTP请求。
在Android.jar包中,可以看到有很多java的API,这些都是被Android改写的API,也可以看到Android封装了大量的Apache API。
示例:res\layout\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"
tools:context=".MainActivity"
android:orientation="vertical">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/et_pass"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="get登陆"
android:onClick="click1"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="post登陆"
android:onClick="click2"
/>
</LinearLayout>
src/cn.itcast.getmethod/MainActivity.java
package cn.itcast.getmethod;
import java.io.InputStream;
import java.net.URLEncoder;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import cn.itcast.getmethod.tool.Tools;
public class MainActivity extends Activity {
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Toast.makeText(MainActivity.this, (String)msg.obj, 0).show();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click1(View v){
EditText et_name = (EditText)findViewById(R.id.et_name);
EditText et_pass = (EditText)findViewById(R.id.et_pass);
String name = et_name.getText().toString();
String pass = et_pass.getText().toString();
final String path = "http://192.168.1.100:8080/Web/servlet/Login?name=" + URLEncoder.encode(name) + "&pass=" + pass;
Thread t = new Thread(){
@Override
public void run() {
//1. 创建客户端对象
//HttpClient是一个接口,不要new一个HttpClient对象,否则要实现很多的方法
HttpClient client = new DefaultHttpClient();
//2. 创建Http GET请求对象
HttpGet get = new HttpGet(path);
try {
//3. 使用客户端发送get请求
HttpResponse response = client.execute(get);
//获取状态行
StatusLine line = response.getStatusLine();
//从状态行中拿到状态码
if(line.getStatusCode() == 200){
//获取实体,实体里存放的是服务器返回的数据的相关信息
HttpEntity entity = response.getEntity();
//获取服务器返回的输入流
InputStream is = entity.getContent();
String text = Tools.getTextFromStream(is);
//发送消息,让主线程刷新UI
Message msg = handler.obtainMessage();
msg.obj = text;
handler.sendMessage(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
t.start();
}
}
添加权限:
点击get登陆按钮,运行结果:
2. 使用HttpClient发送post请求
src/cn.itcast.postmethod/MainActivity.java
package cn.itcast.postmethod;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import cn.itcast.getmethod.R;
import cn.itcast.getmethod.tool.Tools;
public class MainActivity extends Activity {
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Toast.makeText(MainActivity.this, (String)msg.obj, 0).show();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click2(View v){
EditText et_name = (EditText)findViewById(R.id.et_name);
EditText et_pass = (EditText)findViewById(R.id.et_pass);
final String name = et_name.getText().toString();
final String pass = et_pass.getText().toString();
final String path = "http://:8080/Web/servlet/Login";
Thread t = new Thread(){
@Override
public void run() {
//1. 创建客户端对象
HttpClient client = new DefaultHttpClient();
//2. 创建Http POST请求对象
HttpPost post = new HttpPost(path);
try{
//通过此集合封装要提交的数据
List<NameValuePair> parameters = new ArrayList<NameValuePair>();
//集合的泛型是BasicNameValuePair类型,那么由此可以推算出,要提交的数据是封装在BasicNameValuePair对象中
BasicNameValuePair bvp1 = new BasicNameValuePair("name", name);
BasicNameValuePair bvp2 = new BasicNameValuePair("pass", pass);
parameters.add(bvp1);
parameters.add(bvp2);
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters,"utf-8");
//把实体类封装至post请求中,提交post请求时,实体中的数据就会用输出流写给服务器
post.setEntity(entity);
//客户端发送post请求
HttpResponse response = client.execute(post);
//获取状态行
StatusLine line = response.getStatusLine();
//从状态行中拿到状态码
if(line.getStatusCode() == 200){
//获取实体,实体里存放的是服务器返回的数据的相关信息
HttpEntity et = response.getEntity();
//获取服务器返回的输入流
InputStream is = et.getContent();
String text = Tools.getTextFromStream(is);
//发送消息,让主线程刷新UI
Message msg = handler.obtainMessage();
msg.obj = text;
handler.sendMessage(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
t.start();
}
}
点击post登陆按钮,运行结果:
3. 异步HttpClient框架
从github上下载android-async-http-master开源jar包,拷贝library/src/main/java目录下的内容到我们自己的项目中。
拷贝后,发现有错误,这是由于Base64.java中的BuildConfig类导包问题,Ctrl+Shift+O自动导包即可修复。
使用异步HttpClient框架实现上面示例中的功能,activity.xml与上面的示例相同,修改MainActivity.java代码。
src/cn.itcast.asynchttpclient/MainActivity.java
package cn.itcast.asynchttpclient;
import org.apache.http.Header;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.RequestParams;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click1(View v){
EditText et_name = (EditText)findViewById(R.id.et_name);
EditText et_pass = (EditText)findViewById(R.id.et_pass);
final String name = et_name.getText().toString();
final String pass = et_pass.getText().toString();
final String path = "http://192.168.1.100:8080/Web/servlet/Login";
//使用异步HttpClient发送get请求
AsyncHttpClient client = new AsyncHttpClient();
//定义一个请求参数对象,封装要提交的数据
RequestParams rp = new RequestParams();
rp.add("name", name);
rp.add("pass", pass);
//发送get请求
client.get(path, rp, new MyResponseHandler());
}
public void click2(View v){
EditText et_name = (EditText)findViewById(R.id.et_name);
EditText et_pass = (EditText)findViewById(R.id.et_pass);
final String name = et_name.getText().toString();
final String pass = et_pass.getText().toString();
final String path = "http://192.168.1.100:8080/Web/servlet/Login";
AsyncHttpClient client = new AsyncHttpClient();
RequestParams rp = new RequestParams();
rp.add("name", name);
rp.add("pass", pass);
//发送post请求
client.post(path, rp, new MyResponseHandler());
}
class MyResponseHandler extends AsyncHttpResponseHandler{
//请求成功时(响应码为200开头),此方法调用
//登陆成功或者登录失败,只要请求成功,都会调用onSuccess方法
@Override
public void onSuccess(int statusCode, Header[] headers,
byte[] responseBody) {
Toast.makeText(MainActivity.this, new String(responseBody), 0).show();
}
//请求失败时(响应码非200开头)调用
@Override
public void onFailure(int statusCode, Header[] headers,
byte[] responseBody, Throwable error) {
//请求不成功,也显示登录失败
Toast.makeText(MainActivity.this, "登陆失败", 0).show();
}
}
}
添加权限:
运行结果:分别点击“get登陆”和“post登陆”按钮。
4. 多线程下载的原理和过程
断点续传:上次下载到哪,这次就从哪开始下。
多线程:下载速度更快。
原理:抢占服务器资源。例如:带宽为20M/s,3个人去下载同一部电影,那么每人分别占6.66M/s带宽。如果有一人A开了3个线程同时下载,那么5个线程,各占4M/s带宽,那么A所占带宽就是4*3=12M/s,其他两人各占4M/s带宽。也就是说A抢占了更多的服务器资源。
多线程下载示例说明:
例如有一个10KB的文件,分成010,3个线程去下载,第0个线程下载02,也就是3KB数据,第1个线程下载35,也就是3KB数据,余下的69,4KB的数据由最后一个线程下载。
总结出公式就是:
每个线程下载的数据开始点:threadId*size,结束点:(threadId + 1) * size -1。
最后一个线程除外,下载结束点:length - 1。
计算每条线程的下载区间
多线程断点续传的API全部都是Java API,Java项目测试比较容易,所以,我们先创建一个Java项目。
将待下载的资源放入Tomcat服务器中。
src/cn.itcast.MultiDownLoad/Main.java
package cn.itcast.MultiDownLoad;
import java.net.HttpURLConnection;
import java.net.URL;
public class Main {
static int threadCount = 3;
static String path = "http://localhost:8080/QQPlayer.exe";
public static void main(String[] args) {
URL url;
try {
url = new URL(path);
//打开连接对象,做初始化设置
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
if(conn.getResponseCode() == 200){
//获取要下载的目标文件的总长度
int length = conn.getContentLength();
//计算每条线程要下载的长度
int size = length / threadCount;
System.out.println("size:" + size);
//计算每条线程下载的开始位置和结束位置
for(int threadId = 0; threadId < threadCount; threadId++){
int startIndex = threadId * size;
int endIndex = (threadId + 1) * size - 1;
//如果是最后一条线程,那么需要把余数也一块下载
if(threadId == threadCount - 1){
endIndex = length - 1;
}
System.out.println("线程" + threadId + ",下载区间为:" + startIndex + "-" + endIndex);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
创建临时文件
src/cn.itcast.MultiDownLoad/Main.java
package cn.itcast.MultiDownLoad;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class Main {
static int threadCount = 3;
static String path = "http://localhost:8080/QQPlayer.exe";
public static void main(String[] args) {
URL url;
try {
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
if(conn.getResponseCode() == 200){
int length = conn.getContentLength();
int size = length / threadCount;
System.out.println("size:" + size);
//创建一个与目标文件大小一致的临时文件
File file = new File(getFileNameFromPath(path));
//打开文件的访问模式设置为rwd,表示除了读取和写入,还要求对文件内容的每个更新都同步写入到底层存储设备。
//设计到下载的程序,文件访问模式一定要使用rwd,不经过缓冲区,直接写入硬盘。
//如果下载到的数据让写入到缓冲区,一旦断电,缓冲区数据丢失,并且下次服务器断点续传也不会再传输这部分数据,那么下载的文件就不能用了
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//设置临时文件的大小
raf.setLength(length);
for(int threadId = 0; threadId < threadCount; threadId++){
int startIndex = threadId * size;
int endIndex = (threadId + 1) * size - 1;
if(threadId == threadCount - 1){
endIndex = length - 1;
}
System.out.println("线程" + threadId + ",下载区间为:" + startIndex + "-" + endIndex);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getFileNameFromPath(String path){
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
}
开启多个线程下载文件
src/cn.itcast.MultiDownLoad/Main.java
package cn.itcast.MultiDownLoad;
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class Main {
static int threadCount = 3;
static String path = "http://localhost:8080/QQPlayer.exe";
public static void main(String[] args) {
URL url;
try {
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
if(conn.getResponseCode() == 200){
int length = conn.getContentLength();
int size = length / threadCount;
System.out.println("size:" + size);
for(int threadId = 0; threadId < threadCount; threadId++){
int startIndex = threadId * size;
int endIndex = (threadId + 1) * size - 1;
if(threadId == threadCount - 1){
endIndex = length - 1;
}
System.out.println("线程" + threadId + ",下载区间为:" + startIndex + "-" + endIndex);
DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);
dt.start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getFileNameFromPath(String path){
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
}
class DownLoadThread extends Thread{
int threadId;
int startIndex;
int endIndex;
public DownLoadThread(int threadId, int startIndex, int endIndex) {
super();
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public void run(){
URL url;
try{
url = new URL(Main.path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
//Range表示指定请求的数据区间
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
//请求部分数据,返回的是206
if(conn.getResponseCode() == 206){
InputStream is = conn.getInputStream();
//打开临时文件的IO流
File file = new File(Main.getFileNameFromPath(Main.path));
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//修改写入临时文件的开始位置
raf.seek(startIndex);
byte[] b = new byte[1024];
int len = 0;
//当前线程下载的总进度
int total = 0;
while((len = is.read(b)) != -1){
//把读取到的字节写入临时文件中
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载的进度为:" + total);
}
raf.close();
}
System.out.println("线程" + threadId + "下载完毕---------------------");
}catch(Exception e){
e.printStackTrace();
}
}
}
运行结果:刷新,即可看到文件已经下载好了
创建进度临时文件保存下载进度
src/cn.itcast.MultiDownLoad/Main.java
package cn.itcast.MultiDownLoad;
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class Main {
static int threadCount = 3;
static String path = "http://localhost:8080/QQPlayer.exe";
public static void main(String[] args) {
URL url;
try {
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
if(conn.getResponseCode() == 200){
int length = conn.getContentLength();
int size = length / threadCount;
System.out.println("size:" + size);
for(int threadId = 0; threadId < threadCount; threadId++){
int startIndex = threadId * size;
int endIndex = (threadId + 1) * size - 1;
if(threadId == threadCount - 1){
endIndex = length - 1;
}
System.out.println("线程" + threadId + ",下载区间为:" + startIndex + "-" + endIndex);
DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);
dt.start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getFileNameFromPath(String path){
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
}
class DownLoadThread extends Thread{
int threadId;
int startIndex;
int endIndex;
public DownLoadThread(int threadId, int startIndex, int endIndex) {
super();
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public void run(){
URL url;
try{
url = new URL(Main.path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
if(conn.getResponseCode() == 206){
InputStream is = conn.getInputStream();
File file = new File(Main.getFileNameFromPath(Main.path));
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.seek(startIndex);
byte[] b = new byte[1024];
int len = 0;
int total = 0;
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载的进度为:" + total);
//创建一个进度临时文件,保存下载进度
File fileProgress = new File(threadId + ".txt");
RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");
rafProgress.write((total + "").getBytes());
rafProgress.close();
}
raf.close();
}
System.out.println("线程" + threadId + "下载完毕---------------------");
}catch(Exception e){
e.printStackTrace();
}
}
}
运行结果:执行程序,然后,在没下载完成时,就点击右上角的停止按钮。
刷新,可以看到记录文件已经产生。
完成断点续传下载
src/cn.itcast.MultiDownLoad/Main.java
package cn.itcast.MultiDownLoad;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class Main {
static int threadCount = 3;
static String path = "http://localhost:8080/QQPlayer.exe";
public static void main(String[] args) {
URL url;
try {
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
if(conn.getResponseCode() == 200){
int length = conn.getContentLength();
int size = length / threadCount;
System.out.println("size:" + size);
for(int threadId = 0; threadId < threadCount; threadId++){
int startIndex = threadId * size;
int endIndex = (threadId + 1) * size - 1;
if(threadId == threadCount - 1){
endIndex = length - 1;
}
DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);
dt.start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getFileNameFromPath(String path){
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
}
class DownLoadThread extends Thread{
int threadId;
int startIndex;
int endIndex;
public DownLoadThread(int threadId, int startIndex, int endIndex) {
super();
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public void run(){
URL url;
try{
int lastProgress = 0;
//下载之前,先判断进度临时文件是否存在
File fileProgress1 = new File(threadId + ".txt");
if(fileProgress1.exists()){
FileInputStream fis = new FileInputStream(fileProgress1);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
//读取进度临时文件中的值
lastProgress = Integer.parseInt(br.readLine());
//把上一次下载的进度加到下载开始位置
startIndex += lastProgress;
fis.close();
}
System.out.println("线程" + threadId + ",下载区间为:" + startIndex + "-" + endIndex);
url = new URL(Main.path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
if(conn.getResponseCode() == 206){
InputStream is = conn.getInputStream();
File file = new File(Main.getFileNameFromPath(Main.path));
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.seek(startIndex);
byte[] b = new byte[1024];
int len = 0;
//从之前下载的地方开始下载
int total = lastProgress;
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载的进度为:" + total);
File fileProgress = new File(threadId + ".txt");
RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");
rafProgress.write((total + "").getBytes());
rafProgress.close();
}
raf.close();
System.out.println("线程" + threadId + "下载完毕---------------------");
}
}catch(Exception e){
e.printStackTrace();
}
}
}
运行结果:
执行Main.java程序,然后,在还没有下载完,停止。然后,再次执行Main.java,可以看到如下显示,也就是实现了断点续传。
下载后删除进度临时文件
src/cn.itcast.MultiDownLoad/Main.java
package cn.itcast.MultiDownLoad;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class Main {
//记录当前已经下载完成的线程的数量
static int finishedThreadCount = 0;
static int threadCount = 3;
static String path = "http://localhost:8080/QQPlayer.exe";
public static void main(String[] args) {
URL url;
try {
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
if(conn.getResponseCode() == 200){
int length = conn.getContentLength();
int size = length / threadCount;
System.out.println("size:" + size);
for(int threadId = 0; threadId < threadCount; threadId++){
int startIndex = threadId * size;
int endIndex = (threadId + 1) * size - 1;
if(threadId == threadCount - 1){
endIndex = length - 1;
}
DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);
dt.start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getFileNameFromPath(String path){
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
}
class DownLoadThread extends Thread{
int threadId;
int startIndex;
int endIndex;
public DownLoadThread(int threadId, int startIndex, int endIndex) {
super();
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public void run(){
URL url;
try{
int lastProgress = 0;
File fileProgress1 = new File(threadId + ".txt");
if(fileProgress1.exists()){
FileInputStream fis = new FileInputStream(fileProgress1);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
lastProgress = Integer.parseInt(br.readLine());
startIndex += lastProgress;
fis.close();
}
System.out.println("线程" + threadId + ",下载区间为:" + startIndex + "-" + endIndex);
url = new URL(Main.path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
if(conn.getResponseCode() == 206){
InputStream is = conn.getInputStream();
File file = new File(Main.getFileNameFromPath(Main.path));
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.seek(startIndex);
byte[] b = new byte[1024];
int len = 0;
int total = lastProgress;
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载的进度为:" + total);
File fileProgress = new File(threadId + ".txt");
RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");
rafProgress.write((total + "").getBytes());
rafProgress.close();
}
raf.close();
System.out.println("线程" + threadId + "下载完毕---------------------");
//在所有线程都下载完毕后,一起删除所有进度临时文件
//有一个线程完成下载,已经下载完成的线程的数量+1
Main.finishedThreadCount++;
synchronized(Main.path){
if(Main.finishedThreadCount == 3){
//删除所有进度临时文件
for(int i = 0; i < Main.finishedThreadCount; i++){
File f = new File(i + ".txt");
f.delete();
}
//为了防止所有线程都执行到上面的Main.finishedThreadCount++;,然后三个线程都执行删除所有临时文件的代码。
//所以,一方面使用同步代码块,另一方面将Main.finishedThreadCount设置为0。
Main.finishedThreadCount = 0;
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
运行结果:运行Main.java,断点续传完成后,刷新。可以看到,临时文件已经被删除。
5. Android版多线程断点续传下载
res/layout/activity.xml
<RelativeLayout 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"
tools:context=".MainActivity" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载"
android:onClick="click" />
</RelativeLayout>
src/cn.itcast.androidmultidownload/MainActivity.xml
package cn.itcast.androidmultidownload;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
public class MainActivity extends Activity {
int finishedThreadCount = 0;
int threadCount = 3;
String path = "http://192.168.1.100:8080/QQPlayer.exe";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v){
Thread t = new Thread(){
@Override
public void run() {
URL url;
try {
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
if(conn.getResponseCode() == 200){
int length = conn.getContentLength();
int size = length / threadCount;
System.out.println("size:" + size);
for(int threadId = 0; threadId < threadCount; threadId++){
int startIndex = threadId * size;
int endIndex = (threadId + 1) * size - 1;
if(threadId == threadCount - 1){
endIndex = length - 1;
}
DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);
dt.start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
t.start();
}
public String getFileNameFromPath(String path){
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
class DownLoadThread extends Thread{
int threadId;
int startIndex;
int endIndex;
public DownLoadThread(int threadId, int startIndex, int endIndex) {
super();
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public void run(){
URL url;
try{
int lastProgress = 0;
//修改文件路径,存在外部存储器中
File fileProgress1 = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");
if(fileProgress1.exists()){
FileInputStream fis = new FileInputStream(fileProgress1);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
lastProgress = Integer.parseInt(br.readLine());
startIndex += lastProgress;
fis.close();
}
System.out.println("线程" + threadId + ",下载区间为:" + startIndex + "-" + endIndex);
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
if(conn.getResponseCode() == 206){
InputStream is = conn.getInputStream();
File file = new File(Environment.getExternalStorageDirectory(), getFileNameFromPath(path));
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.seek(startIndex);
byte[] b = new byte[1024];
int len = 0;
int total = lastProgress;
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载的进度为:" + total);
File fileProgress = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");
RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");
rafProgress.write((total + "").getBytes());
rafProgress.close();
}
raf.close();
System.out.println("线程" + threadId + "下载完毕---------------------");
finishedThreadCount++;
synchronized(path){
if(finishedThreadCount == 3){
for(int i = 0; i < finishedThreadCount; i++){
File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");
f.delete();
}
finishedThreadCount = 0;
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
添加权限:
运行结果:点击“下载”按钮,在下载完成之前,杀死线程。
可以看到临时文件生成。
再次运行应用程序,点击“下载”按钮,接着下载,断点续传成功实现。
下载完成后,可以看到临时文件删除成功。
添加进度条反应下载进度,res/layout/activity.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"
tools:context=".MainActivity"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载"
android:onClick="click" />
<ProgressBar
android:id="@+id/pb"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
src/cn.itcast.androidmultidownload/MainActivity.xml
package cn.itcast.androidmultidownload;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.ProgressBar;
public class MainActivity extends Activity {
int finishedThreadCount = 0;
int threadCount = 3;
String path = "http://192.168.1.100:8080/QQPlayer.exe";
private ProgressBar pb;
//记录进度条的当前进度
int currentProgress = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//进度条
pb = (ProgressBar) findViewById(R.id.pb);
}
public void click(View v){
Thread t = new Thread(){
@Override
public void run() {
URL url;
try {
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
if(conn.getResponseCode() == 200){
int length = conn.getContentLength();
//设定进度条的最大值
pb.setMax(length);
int size = length / threadCount;
System.out.println("size:" + size);
for(int threadId = 0; threadId < threadCount; threadId++){
int startIndex = threadId * size;
int endIndex = (threadId + 1) * size - 1;
if(threadId == threadCount - 1){
endIndex = length - 1;
}
DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);
dt.start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
t.start();
}
public String getFileNameFromPath(String path){
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
class DownLoadThread extends Thread{
int threadId;
int startIndex;
int endIndex;
public DownLoadThread(int threadId, int startIndex, int endIndex) {
super();
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public void run(){
URL url;
try{
int lastProgress = 0;
File fileProgress1 = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");
if(fileProgress1.exists()){
FileInputStream fis = new FileInputStream(fileProgress1);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
lastProgress = Integer.parseInt(br.readLine());
startIndex += lastProgress;
//如果开始位置大于或等于endIndex,说明上一次下载中,此线程就已经下载完了
if(startIndex >= endIndex){
finishedThreadCount++;
}
//如果上一次下载过,把上次的进度加到当前进度中
currentProgress += lastProgress;
pb.setProgress(currentProgress);
fis.close();
}
System.out.println("线程" + threadId + ",下载区间为:" + startIndex + "-" + endIndex);
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
if(conn.getResponseCode() == 206){
InputStream is = conn.getInputStream();
File file = new File(Environment.getExternalStorageDirectory(), getFileNameFromPath(path));
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.seek(startIndex);
byte[] b = new byte[1024];
int len = 0;
int total = lastProgress;
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载的进度为:" + total);
File fileProgress = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");
RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");
rafProgress.write((total + "").getBytes());
rafProgress.close();
//每一条线程下载的数据,都应该加到全局进度里
currentProgress += len;
//设置进度条当前进度
//进度条内部也是通过handler让主线程刷新UI的
pb.setProgress(currentProgress);
}
raf.close();
System.out.println("线程" + threadId + "下载完毕---------------------");
finishedThreadCount++;
synchronized(path){
if(finishedThreadCount == 3){
for(int i = 0; i < finishedThreadCount; i++){
File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");
f.delete();
}
finishedThreadCount = 0;
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
运行结果:点击“下载”按钮,可以通过进度条看到下载进度。然后,杀死进程,再重新运行应用程序,点击“下载”按钮,可以看到进度条在原有的基础上继续向前移动,也就是实现了断点续传的进度条实现。
添加文本进度,res/layout/activity.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"
tools:context=".MainActivity"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载"
android:onClick="click" />
<ProgressBar
android:id="@+id/pb"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0%"
android:layout_gravity="right"
/>
</LinearLayout>
src/cn.itcast.androidmultidownload/MainActivity.xml
package cn.itcast.androidmultidownload;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
int finishedThreadCount = 0;
int threadCount = 3;
String path = "http://192.168.1.100:8080/QQPlayer.exe";
private ProgressBar pb;
int currentProgress = 0;
private TextView tv;
//刷新TextView
Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
//当前进度除以最大进度,得到下载进度的百分比
tv.setText(pb.getProgress() * 100 /pb.getMax() + "%");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pb = (ProgressBar) findViewById(R.id.pb);
tv = (TextView)findViewById(R.id.tv);
}
public void click(View v){
Thread t = new Thread(){
@Override
public void run() {
URL url;
try {
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
if(conn.getResponseCode() == 200){
int length = conn.getContentLength();
pb.setMax(length);
int size = length / threadCount;
System.out.println("size:" + size);
for(int threadId = 0; threadId < threadCount; threadId++){
int startIndex = threadId * size;
int endIndex = (threadId + 1) * size - 1;
if(threadId == threadCount - 1){
endIndex = length - 1;
}
DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);
dt.start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
t.start();
}
public String getFileNameFromPath(String path){
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
class DownLoadThread extends Thread{
int threadId;
int startIndex;
int endIndex;
public DownLoadThread(int threadId, int startIndex, int endIndex) {
super();
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public void run(){
URL url;
try{
int lastProgress = 0;
File fileProgress1 = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");
if(fileProgress1.exists()){
FileInputStream fis = new FileInputStream(fileProgress1);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
lastProgress = Integer.parseInt(br.readLine());
startIndex += lastProgress;
if(startIndex >= endIndex){
finishedThreadCount++;
}
currentProgress += lastProgress;
pb.setProgress(currentProgress);
//发送消息,让主线程刷新文本进度
handler.sendEmptyMessage(1);
fis.close();
}
System.out.println("线程" + threadId + ",下载区间为:" + startIndex + "-" + endIndex);
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
if(conn.getResponseCode() == 206){
InputStream is = conn.getInputStream();
File file = new File(Environment.getExternalStorageDirectory(), getFileNameFromPath(path));
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.seek(startIndex);
byte[] b = new byte[1024];
int len = 0;
int total = lastProgress;
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载的进度为:" + total);
File fileProgress = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");
RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");
rafProgress.write((total + "").getBytes());
rafProgress.close();
currentProgress += len;
pb.setProgress(currentProgress);
//发送消息,让主线程刷新文本进度
handler.sendEmptyMessage(1);
}
raf.close();
System.out.println("线程" + threadId + "下载完毕---------------------");
finishedThreadCount++;
synchronized(path){
if(finishedThreadCount == 3){
for(int i = 0; i < finishedThreadCount; i++){
File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");
f.delete();
}
finishedThreadCount = 0;
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
运行结果:
文本进度计算的bug:当文件较大时,就会出现bug,文本进度计算数据变成了负数。
这是因为文件大小超出了int所能表示的最大范围。
只需要修改代码如下即可。
如果最终显示为99%,那么只需要在下载完成之后,直接在程序中写死为100%即可。
6. xUtils多线程断点续传下载
从github上下载xUtils,将xUtils的jar复制到libs目录下。
如果无法关联源码,可以通过在libs目录下新建一个properties文件解决。
properties文件的内容为"src=源码目录",即可成功关联源码。
res/layout/activity.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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity"
android:orientation="vertical"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载"
android:onClick="click" />
<TextView
android:id="@+id/tv_success"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/tv_failure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ff0000"
/>
<ProgressBar
android:id="@+id/tv_pb"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
src/cn.itcast.androidmultidownload/MainActivity.xml
package cn.itcast.xutils;
import java.io.File;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.HttpHandler;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
public class MainActivity extends Activity {
String path = "http://192.168.1.100:8080/QQPlayer.exe";
private TextView tv;
private ProgressBar pb;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pb = (ProgressBar)findViewById(R.id.tv_pb);
tv = (TextView)findViewById(R.id.tv_progress);
}
public void click(View v){
HttpUtils utils = new HttpUtils();
HttpHandler handler = utils.download(path, //请求的网址
"sdcard/QQPlayer.exe", //文件保存的路径及文件名
true, // 是否支持断点续传
true, // 如果相应头中包含文件名,那么下载完毕后,自动以该名字重命名文件
new RequestCallBack<File>() {
//下载完成调用
@Override
public void onSuccess(ResponseInfo<File> responseInfo) {
TextView tv = (TextView)findViewById(R.id.tv_success);
tv.setText(responseInfo.result.getPath());
}
//下载失败后调用
@Override
public void onFailure(HttpException error, String msg) {
TextView tv = (TextView)findViewById(R.id.tv_success);
tv.setText(msg);
}
//下载过程中不断调用
@Override
public void onLoading(long total, long current,
boolean isUploading) {
pb.setMax((int)total);
pb.setProgress((int)current);
tv.setText((current * 100)/ total + "%");
}
});
}
}
添加权限:
运行结果: