多线程下载之断点续传android版本
第一个页面布局
<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"
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" >
<EditText
android:id="@+id/en_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="http://192.168.80.212:8080/feiqiu.exe"
android:hint="请输入下载路径" />
<EditText
android:id="@+id/en_threadCount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="55dp"
android:hint="请输入下载线程数量" >
</EditText>
<Button
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_margin="90dp"
android:onClick="click"
android:text="下载" />
<!-- 写一个线性布局,动态的添加布局 -->
<LinearLayout
android:id="@+id/ll_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="200dp"
android:orientation="vertical" >
</LinearLayout>
</RelativeLayout>
第二个页面布局
<?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" >
</ProgressBar>
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 java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
public class MainActivity extends Activity {
private EditText en_path;
private EditText en_threadCount;
private List<ProgressBar> pbs;
private int runningThread ;
private int threadCount;
private LinearLayout ll_layout;
private String downThreadCount;
private String downloadPath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取我们关心的控件
en_path = (EditText) findViewById(R.id.en_path);
en_threadCount = (EditText) findViewById(R.id.en_threadCount);
ll_layout = (LinearLayout) findViewById(R.id.ll_layout);
pbs = new ArrayList<ProgressBar>();
}
public void click(View v) {
downloadPath = en_path.getText().toString().trim();
downThreadCount = en_threadCount.getText().toString().trim();
//将从控件获取到的下载数量转换为int型
threadCount = Integer.parseInt(downThreadCount);
//清除集合和进度条
ll_layout.removeAllViews(); //清除集合中的进度条
pbs.clear();
//有几个线程就添加几个进度条
for (int i = 0; i < threadCount; i++) {
ProgressBar pb = (ProgressBar) View.inflate(getApplication(), R.layout.item, null);
pbs.add(pb); //将进度条添加到集合
ll_layout.addView(pb); //将进度条添加到布局
}
//开启子线程
new Thread() {
public void run() {
try {
URL url = new URL(downloadPath);
HttpURLConnection con = (HttpURLConnection) url
.openConnection();
con.setRequestMethod("GET");
con.setConnectTimeout(5000);
int code = con.getResponseCode();
if (code == 200) {
//从服务器获取文件的长度
int length = con.getContentLength();
//将线程数赋值
runningThread = threadCount;
//将文件写到内存
RandomAccessFile rafAccessFile = new RandomAccessFile(getFileName(downloadPath), "rw");
rafAccessFile.setLength(length); //设置该文件的大小
int blockSize = length / threadCount; //得到每个线程下载的总大小
for (int i = 0; i < threadCount; i++) {
int startIndex = i * blockSize; //计算每个线程出开始的位置
int endIndex = (i + 1) * blockSize; //计算每个线程的结束位置
if(i == threadCount - 1){ //计算最后一个线程的位置
endIndex = length - 1;
}
new DownLoadThread(i,startIndex,endIndex).start(); //开启线程下载
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
//下载文件
class DownLoadThread extends Thread{
private int threadid; //线程id
private int startIndex; //线程的开始位置
private int endIndex; //线程的结束位置
private int pbMax; //进度条的最大值
private int pbLastPosition; //进度条的当前位置
public DownLoadThread(int threadid,int startIndex,int endIndex){
this.threadid = threadid;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run() {
try {
pbMax = endIndex - startIndex; //计算出进度条的最大值
URL url = new URL((downloadPath));
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setConnectTimeout(5000);
//如果线程下载过程中中断过,则走下面的 if语句,没有则跳过该语句继续执行
File file = new File(getFileName(downloadPath) + threadid + ".txt"); //存储每个线程的txt文件
if(file.exists() && file.length() > 0){
//用高效的字符流关联该文件
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
String lastPosition = br.readLine();
pbLastPosition = Integer.parseInt(lastPosition);
startIndex = Integer.parseInt(lastPosition); //将读取出来的数据作为当前线程的开始位置
br.close();
}
//设置Range头,指定下载的开始位置和结束位置
con.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
int code = con.getResponseCode();
if (code == 206) { //请求资源部分求成功
InputStream in = con.getInputStream();
RandomAccessFile raf = new RandomAccessFile(getFileName(downloadPath), "rw");
raf.seek(startIndex); //设置文件的开始位置
int len;
int total = 0;
byte[] arr = new byte[1024*1024];
while((len = in.read(arr)) != -1){
total += len; //获取到正在下载的长度并累加
int currentThreadPosition = startIndex + total; //将当前下载的长度和开始位置相加得到线程当前的位置
//创建一个当前线程的txt文件,用于存储中断时的位置
RandomAccessFile accessFile = new RandomAccessFile(getFileName(downloadPath) + threadid + ".txt", "rwd");
//将此位置写入到文件中
accessFile.write(String.valueOf(currentThreadPosition).getBytes());
accessFile.close();
//下载数据(将数据写到文件中,此文件为.exe(以飞秋为例)形式)
raf.write(arr, 0, len);
//设置进度条的大小
pbs.get(threadid).setMax(pbMax); //进度条的最大值
//设置进度条的当前位置
pbs.get(threadid).setProgress(pbLastPosition + total - startIndex);
}
//关流
in.close();
raf.close();
//为什么不用threadCount--,而是给它赋值为runningThread在操作呢?
//个人理解:如果直接操作threadCount,那么当threadCount出现错误时全局都会受到影响
//但是如果操作runningThread出现错误时,并不会对全局造成影响,并且代码容易维护
synchronized ((DownLoadThread.class) ) {//同步:是为了在当前线程能够完全执行,其他线程不中断当前线程
runningThread --;
if(runningThread == 0){ //如果正在运行的线程等于0,则将生成的临时文件删除
for (int i = 0; i < threadCount; i++) {
File txtfile = new File(getFileName(downloadPath) + i + ".txt");
txtfile.delete();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static String getFileName(String path){
int start = path.lastIndexOf("/") + 1;
String sdCard = Environment.getExternalStorageDirectory().getPath();
return sdCard + "/" + path.substring(start);
}
}