有时候 会遇到这样的需求:实现多线程的断点续传。今天就我平时的一些实战的经验 来给大家写一个多线程断点续传的例子,希望对大家有所帮助。
一、多线程下载的原理
多线程下载:
多线程下载的基本原理就是由一个完整的文件分成几个不同部分,然后分别由不同的线程去下载这几个部分,把它下载到本地之后,我们将其进行合并,合并完成之后就能得到完整的文件
断点续传:
第一次下载时我们下载这么多,假如遇到了网络状态不太好或者用户手动中止了下载,这时,我们的下载不能顺利的进行下去,于是我们需要进行第二次下载。
在传统的下载过程中我们第二次下载和第一次下载一样 需要从新开始进行下载,但是有了断点续传 ,我们记录第一次下载的位置,第二次下载的时候从断点处开始下载。
1.如何从服务器获取部分文件
断点续传我们要解决的是如何告诉服务器我们需要的文件的位置(从哪里到哪里),这个就涉及到了http协议的问题,在向服务器发送请求的过程中,要用一个 http header,这个会制定我们的请求的要求,在这里 我们要使用的是range ,用来制定我们需要的位置(Range : bytes=0-500).
2.文件的合并
还有个一我们需要解决的问题就是我们下载下来的这几部分如何合并,这里给出两种方案:
一、RandomAccessFile
他可以让我们从文件的某一个位置开始读写
二、先下载到几个文件中,最后合并
本次 我们使用RandomAcceeFile
首先,我们先把布局文件写出来
如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/FileUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ProgressBar
style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_down"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
好了,写好布局之后 我们开始要写重要的一些逻辑了(重点)
public class MultiDownActivity extends Activity{
private int total=0;
private boolean donwnloading=false;
private URL url;
private File file;
private Button btn_down;
private EditText et_fileUrl;
private ProgressBar progressBar;
private int length;
Handler handler=new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what==0){
progressBar.setProgress(msg.arg1);
if (msg.arg1==length){
Toast.makeText(MultiDownActivity.this, "下载完成!", Toast.LENGTH_SHORT).show();
total = 0;
}
}
return false;
}
});
private List<HashMap<String,Integer>> threadList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multidown);
et_fileUrl= (EditText) findViewById(R.id.FileUrl);
btn_down= (Button) findViewById(R.id.btn_down);
progressBar = (ProgressBar) findViewById(R.id.progress);
threadList=new ArrayList<>();
btn_down.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (donwnloading){
donwnloading=false;
btn_down.setText("下载");
return;
}
donwnloading=true;
btn_down.setText("暂停");
if (threadList.size()==0){
new Thread(new Runnable() {
@Override
public void run() {
try {
url=new URL(et_fileUrl.getText().toString());
HttpURLConnection conn= (HttpURLConnection) url.openConnection();
//请求方式
conn.setRequestMethod("GET");
//设置连接超时时间
conn.setReadTimeout(5000);
//请求的代理(伪装成浏览器)
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727)");
length=conn.getContentLength();
progressBar.setMax(length);
progressBar.setProgress(0);
if (length<0){
Toast.makeText(MultiDownActivity.this,"文件不存在",Toast.LENGTH_SHORT).show();
return;
}
file=new File(Environment.getExternalStorageDirectory(),getFileName(et_fileUrl.getText().toString()));
RandomAccessFile randomAccessFile=new RandomAccessFile(file,"rw");
randomAccessFile.setLength(length);
int blockSize=length/3;
for (int i=0;i<3;i++){
int begin=i*blockSize;
int end=(i+1)*blockSize;
if (i==2){
end=length;
}
HashMap<String,Integer> map=new HashMap<String, Integer>();
map.put("begin",begin);
map.put("end",end);
map.put("finished",0);
threadList.add(map);
//创建新的线程,下载
Thread t=new Thread(new DownLoadRunnable(begin,end,file,url,i));
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}else {
//恢复下载
for (int i = 0; i < threadList.size(); i++) {
HashMap<String, Integer> map = threadList.get(i);
int begin = map.get("begin");
int end = map.get("end");
int finished = map.get("finished");
Thread t = new Thread(new DownLoadRunnable( begin + finished, end, file, url,i));
t.start();
}
}
}
});
}
private String getFileName(String url){
int index=url.lastIndexOf("/")+1;
return url.substring(index);
}
class DownLoadRunnable implements Runnable{
private int begin;
private int end;
private File file;
private URL url;
private int id;
public DownLoadRunnable(int begin, int end, File file, URL url, int id) {
this.begin = begin;
this.end = end;
this.file = file;
this.url = url;
this.id = id;
}
@Override
public void run() {
try {
HttpURLConnection conn= (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
//请求的代理(伪装成浏览器)
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727)");
conn.setRequestProperty("Range","bytes="+begin+"-"+end);
InputStream is=conn.getInputStream();
byte [] buf=new byte[1024*1024];
RandomAccessFile randomAccessFile=new RandomAccessFile(file,"rw");
randomAccessFile.seek(begin);
int len=0;
HashMap<String,Integer> map=threadList.get(id);
while ((len=is.read(buf))!=-1&&donwnloading){
randomAccessFile.write(buf, 0, len);
updataProgress(len);
map.put("finished",map.get("finished")+len);
}
is.close();
randomAccessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
synchronized private void updataProgress(int len){
total+=len;
handler.obtainMessage(0, total, 0).sendToTarget();
}
}
基本的思路是这样的:
首先我们要确定文件的大小,以便下载和分割。
然后我们将其分为三段,分别进行下载
好了 到这里 多线程的下载我们就实现了,接下来就是断点续传的实现。
我们定义一个布尔类型的变量来控制我们的下载和 暂停 (如上图)我们在建一个list用来记录每一段的下载进度,当list的大小不为0时,我们接着从未下载完的地方继续下载
最后就是和progressbar的一些同步工作,这里我用到的是handler来实现的(这里就涉及到了非主线程刷新ui的原理哦)
好了 一个简单的多线程续传实现了!
作者:leiliang568