通常情况下,从服务器下载文件时,服务器都是同时与多个用户连接,用户之间共享带宽。如果N个用户的优先级都相同,那么每个用户连接到该服务器上的实际带宽就是服务器带宽的N分之一。可以想象,如果用户数目较多,则每个用户只能占有可怜的一点带宽,下载将会是个漫长的过程。
如果使用多线程:
具体实现过程中会出现很多问题:
1.怎么在一个文件里面写数据的时候按照指定的位置写(因为每个线程的下载区间需要不一样,不然数据会覆盖,导致文件下不全)
2.如何去获取要下载的文件大小(因为怕下载中途需要下载其他东西,导致本次需要下载的文件内存不足,所以需要先预留一个和要下载的文件大小一样大的空间)
3.计算每个子线程的下载区间(因为每个线程的下载区间肯定不一样,不然怎么加快速度呢)
第一个问题的解决办法:
借助RandomAccessFile 随机文件访问类的 seek(long offset)方法,这个方法可以把文件的写入位置移动至offset。
第二个问题的解决办法:
我们可以使用HttpURLConnection 对象的 getContentLength() 方法得到你当前请求文件的大小。
第三个问题的解决办法:
假设下载的文件大小为10B(0-9,数组下标从0开始),线程数为3,那么
线程0的下载区间应该是: 0—2
线程1的下载区间应该是: 3—5
线程2的下载区间应该是: 6—9
每个线程下载文件的大小 = 文件长度 / 线程数 (最后一个线程除外,因为可能不能均分)
那么i线程的下载开始位置: i*每个线程下载文件的大小
i线程的下载结束位置: (i+1)*每个线程下载文件的大小 - 1
最后一个线程的结束位置为:文件长度 - 1
实现断点续传的思路:
可以把每个线程下载的进度都存在一个文件里,等来电时我们先去检索有没有进度文件,如果有,说明上次下载过,但没下完,就将次进度取出来继续下载。不过线程下载的开始位置应该是 原来的开始位置+上次的进度,为了用户体验,我们应该在线程全部下载完成之后将保存的下载进度文件删除(因为这个文件对用户也没什么用)。
下面我们来理一下思路:
①请求网络得到需要下载的文件的大小,并生成一个和原文件一样大小的文件(先占空间)(响应码为200)
②确定每个线程的下载区间(最后一个线程的结束位置应该单独考虑)
③先查看有没有进度文件,有则从上次进度开始下载,没有则请求网络获取需要下载区间的数据,并生成下载进度文件以便断点续传。(记住请求的数据不是所有数据,而是各个线程它需要下载的那部分区间,响应码为206)
注:不是所有的服务器都支持断点续传,这取决于服务器那边。
④待各个线程全部下载完成,将进度文件删掉。
⑤开启线程下载
具体代码:
public class MainActivity extends Activity {
//进度条
private ProgressBar pb;
//显示进度(百分比)
private TextView tv;
//记录当前进度条的下载进度
private int currentProgress;
//进行下载的线程数量
public static final int THREADCOUNT = 3;
//下载完成的线程数量
public int finishedThread = 0;
//下载完成生成的文件名
public String fileName = "navicat.pdf";
//请求的文件下载地址(本地文件)
public String path = "http://192.168.1.100:8089/" + fileName;
//请求的文件下载地址(网络文件)
// public String path =
// "thunder://QUFodHRwOi8vZGw0NS44MHMuaW06OTIwLzE2MDUv6LaF6ISRNDjlsI/ml7Zb5Zu96K+tRFZE54mIXS/otoXohJE0OOWwj+aXtlvlm73or61EVkTniYhdX2JkLm1wNFpa";
private Handler mHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
if (msg.what == 0x1) {
tv.setText(pb.getProgress()*100/pb.getMax() + "%");
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
/**
* 初始化组件
*/
private void initView() {
pb = (ProgressBar) findViewById(R.id.pb);
tv = (TextView) findViewById(R.id.tv);
}
/**
* 点击下载的事件
* @param view
*/
public void download(View view) {
new Thread() {
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(3000);
conn.setReadTimeout(8000);
//请求成功时的响应码为200(注意响应码为200)
if (conn.getResponseCode() == 200) {
// 拿到需要下载的文件的大小
int length = conn.getContentLength();
// 先占个位置,生成临时文件
File file = new File(Environment.getExternalStorageDirectory(),fileName);
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.setLength(length);
//设置进度条的最大进度为文件的长度
pb.setMax(length);
raf.close();
//每个线程应该下载的长度(最后一个线程除外,因为不一定能够平分)
int size = length / THREADCOUNT;
for (int i = 0; i < THREADCOUNT; i++) {
// 1.确定每个线程的下载区间
// 2.开启对应的子线程
int startIndex = i * size; //开始位置
int endIndex = (i + 1) * size - 1; //结束位置
// 最后一个线程
if (i == THREADCOUNT - 1) {
endIndex = length - 1;
}
System.out.println("第" + (i + 1) + "个线程的下载区间为:"
+ startIndex + "-" + endIndex);
new DownloadThread(startIndex, endIndex, path, i)
.start();
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
};
}.start();
}
class DownloadThread extends Thread{
private int lastProgress;
private int startIndex,endIndex,threadId;
private String path;
public DownloadThread(int startIndex,int endIndex,String path,int threadId) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.path = path;
this.threadId = threadId;
}
@Override
public void run() {
try {
//建立进度临时文件,其实这时还没有创建。当往文件里写东西的时候才创建。
File progressFile = new File(Environment.getExternalStorageDirectory(), threadId+".txt");
//判断临时文件是否存在,存在表示已下载过,没下完而已
if (progressFile.exists()) {
FileInputStream fis = new FileInputStream(progressFile);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
//从进度临时文件中读取出上一次下载的总进度,然后与原本的开始位置相加,得到新的开始位置
lastProgress = Integer.parseInt(br.readLine());
startIndex += lastProgress;
//断点续传,更新上次下载的进度条
currentProgress += lastProgress;
pb.setProgress(currentProgress);
Message msg = Message.obtain();
msg.what = 0x1;
mHandler.sendMessage(msg);
br.close();
fis.close();
}
//真正请求数据
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(3000);
conn.setReadTimeout(8000);
//设置本次http请求所请求的数据的区间(这是需要服务器那边支持断点),格式需要这样写,不能写错
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
//请求部分数据,响应码是206(注意响应码是206)
if (conn.getResponseCode() == 206) {
//此时流中只有1/3原数据
InputStream is = conn.getInputStream();
File file = new File(Environment.getExternalStorageDirectory(),fileName);
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//把文件的写入位置移动至startIndex
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);
//生成一个专门用来记录下载进度的临时文件
RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");
//每次读取流里数据之后,同步把当前线程下载的总进度写入进度临时文件中
progressRaf.write((total + "").getBytes());
progressRaf.close();
//下载时更新进度条
currentProgress += len;
pb.setProgress(currentProgress);
Message msg = Message.obtain();
msg.what = 0x1;
mHandler.sendMessage(msg);
}
System.out.println("线程" + threadId + "下载完成");
raf.close();
//每完成一个线程就+1
finishedThread ++;
//等标志位等于线程数的时候就说明线程全部完成了
if (finishedThread == THREADCOUNT) {
for (int i = 0; i < finishedThread; i++) {
//将生成的进度临时文件删除
File f = new File(Environment.getExternalStorageDirectory(),i + ".txt");
f.delete();
}
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
<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="com.example.multithreaddownload.MainActivity" >
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="download"
android:text="多线程下载及断点续传" />
<ProgressBar
android:id="@+id/pb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@android:style/Widget.ProgressBar.Horizontal"/>
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
参考:http://blog.csdn.net/qq_22063697/article/details/51568066