这篇是接着上一篇多线程下载文件(http://blog.csdn.net/wozuihaole/article/details/54892559)写的。我们下载文件中途如果断网了,文件只下载了一半,肯定是没法使用的,这时候我们就要用到断点下载,也就是下一次有网的时候继续下载完成文件。
原理:
1.为每个线程创建一个记录当前文件写入长度的临时文件;
2.每次线程请求服务器前先判断临时文件是否存在,如果存在,读出已写入的文件长度,使下载开始位置偏移到已写入长度的后面;
3.线程开始写入文件时,每次把buffer中的写入长度记录到临时文件中,保存起来;
4.当文件全部写入完成后,删除所有的临时文件。
主要代码:
1.每次请求服务器前,判断是否有临时文件存在:
if (positionFile.exists() && positionFile.length() >0) {//如果文件有内容,证明是断点继续下载 BufferedReader reader = new BufferedReader(new FileReader(positionFile)); int size = Integer.parseInt(reader.readLine()); total += size;//将total的值加上已经写入的大小,再接着写的时候,大小就会接着增加了 startIndex += size;//将下载的起始位置偏移到写过的内容后面 Log.d(TAG,"之前写入了"+size+"个字节的内容"); reader.close();//记得关哦 }
2.在每次写入下载文件的同时把写入的长度保存到临时文件,while循环里。
if (code == 206) {//分段下载的返回码是206不是200 InputStream is = con.getInputStream(); File f = new File(Environment.getExternalStorageDirectory(),"gg.exe"); RandomAccessFile file = new RandomAccessFile(f,"rw");//在本地创建文件 file.seek(startIndex); int len; byte[] buffer = new byte[512]; while ((len = is.read(buffer)) != -1) { RandomAccessFile positionF = new RandomAccessFile(positionFile,"rwd");//以rwd的方式写入文件,rwd为实时保存数据到硬盘 file.write(buffer,0,len); total += len; positionF.write(String.valueOf(total).getBytes()); positionF.close();//用完别忘了关闭 } is.close(); file.close(); Log.d(TAG,startIndex+"-"+endIndex+"部分文件下载完毕!"); }
3.在线程运行最后判断一下是否全部下载完成,也就是三个线程都运行完了,我是用的一个全局变量runThread记录当前运行的线程数,每运行完一个就减一,直到这个值为0,就是文件下载完成了,删除所有记录长度的临时文件。
finally { synchronized ("LOCK") { runThread--; Log.d(TAG,"线程"+threadId+"下载停止"); if (runThread == 0) { //下载完成后把咱们用来记录每次写入大小的临时文件删除掉 for (int i=1; i<= ThreadSize; i++) { File threadFile = new File(Environment.getExternalStorageDirectory(),i+".txt"); Log.d(TAG,threadId+".txt文件删除"+threadFile.delete()); } ToastOnScreen("下载完成"); } }
完整代码:
package com.example.multhread; import android.app.Activity; import android.os.Environment; import android.util.Log; import android.widget.Toast; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.InputStream; import java.io.RandomAccessFile; import java.io.Reader; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.List; /** * Created by pactera on 2017/2/4. */ public class DownloadManager { private static final String path = "http://10.5.92.12:8080/loginProject/ff.exe"; private static final String TAG = "DownloadManager"; private static final int ThreadSize = 3; private int runThread = ThreadSize; private Activity activity; private List<MyThread> threads; public DownloadManager(Activity activity) { this.activity = activity; threads = new ArrayList<MyThread>(); } public void StartDownload () { //1.在Android客户端首先创建一个和需要下载的文件相同大小的空白文件 long fileSize = getFileSize(); //2.开启3个线程去下载对应文件 if (fileSize > 0) { startMutileThread(fileSize); //3.如果所有线程都下载完毕,服务器的资源就下载完毕了 } else { ToastOnScreen("服务器访问异常,请稍后再试"); } } private long getFileSize() { try { URL url = new URL(path); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setConnectTimeout(5000); int code = con.getResponseCode(); if (code == 200) { long size = con.getContentLength();//获取文件长度 Log.d(TAG,"文件大小为:"+size); File f = new File(Environment.getExternalStorageDirectory(),"gg.exe"); RandomAccessFile file = new RandomAccessFile(f,"rwd");//在本地创建文件 file.setLength(size);//将要下载的文件长度,赋给本地文件 file.close(); return size; } } catch (Exception e) { e.printStackTrace(); } return 0; } private void startMutileThread(long fileSize) { long size = fileSize/ThreadSize;//获取每个线程需要下载文件的长度 for (int i=0; i<ThreadSize; i++) { long startIndex = i*size;//计算每个线程开始下载文件的位置 long endIndex = (i+1)*size-1;//停止下载文件的位置 if (i==ThreadSize-1) {//如果是最后一个线程,则直接下载完剩余的文件 endIndex = fileSize-1; } MyThread thread = new MyThread(startIndex,endIndex,(i+1));//开启线程 threads.add(thread); thread.start(); } } private class MyThread extends Thread { private long startIndex; private long endIndex; private int threadId; public MyThread(long startIndex, long endIndex, int threadId) { this.startIndex = startIndex;//开始下载的位置 this.endIndex = endIndex;//停止下载的位置 this.threadId = threadId; Log.d(TAG,startIndex+"-"+endIndex+"部分文件开始下载!"); } @Override public void run() { super.run(); try { int total = 0;//用来记录线程每次写入的数据长度 File positionFile = new File(Environment.getExternalStorageDirectory(),threadId + ".txt");//创建记录每次写入数据长度的文件 if (positionFile.exists() && positionFile.length() >0) {//如果文件有内容,证明是断点继续下载 BufferedReader reader = new BufferedReader(new FileReader(positionFile)); int size = Integer.parseInt(reader.readLine()); total += size;//将total的值加上已经写入的大小,再接着写的时候,大小就会接着增加了 startIndex += size;//将下载的起始位置偏移到写过的内容后面 Log.d(TAG,"之前写入了"+size+"个字节的内容"); reader.close();//记得关哦 } URL url = new URL(path); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setRequestProperty("Range","bytes="+startIndex+"-"+endIndex);//设置请求头,设置开始下载位置和停止位置 con.setConnectTimeout(5000); int code = con.getResponseCode(); if (code == 206) {//分段下载的返回码是206不是200 InputStream is = con.getInputStream(); File f = new File(Environment.getExternalStorageDirectory(),"gg.exe"); RandomAccessFile file = new RandomAccessFile(f,"rw");//在本地创建文件 file.seek(startIndex); int len; byte[] buffer = new byte[512]; while ((len = is.read(buffer)) != -1) { RandomAccessFile positionF = new RandomAccessFile(positionFile,"rwd");//以rwd的方式写入文件,rwd为实时保存数据到硬盘 file.write(buffer,0,len); total += len; positionF.write(String.valueOf(total).getBytes()); positionF.close();//用完别忘了关闭 } is.close(); file.close(); Log.d(TAG,startIndex+"-"+endIndex+"部分文件下载完毕!"); } } catch (Exception e) { e.printStackTrace(); } finally { synchronized ("LOCK") { runThread--; Log.d(TAG,"线程"+threadId+"下载停止"); if (runThread == 0) { //下载完成后把咱们用来记录每次写入大小的临时文件删除掉 for (int i=1; i<= ThreadSize; i++) { File threadFile = new File(Environment.getExternalStorageDirectory(),i+".txt"); Log.d(TAG,threadId+".txt文件删除"+threadFile.delete()); } ToastOnScreen("下载完成"); } } } } } private void ToastOnScreen(final String s) { activity.runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(activity, s, Toast.LENGTH_LONG).show(); } }); } }