Java 断点续传
(1)原理
在下载行为出现中断的时候,记录下中断的位置信息,然后在下次行为开始的时候,直接从记录的这个位置开始下载内容,而不再从头开始。
分为两步:
当“上传(下载)的行为”出现中断,我们需要记录本次上传(下载)的位置(position)。
当“续”这一行为开始,我们直接跳转到postion处继续上传(下载)的行为。
(2)代码
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
public class Test {
// step1:首先,我们定义了一个变量position,记录在发生中断的时候,已完成读写的位置。(这是为了方便,实际来说肯定应该讲这个值存到文件或者数据库等进行持久化)
private static int position = -1;
public static void main(String[] args) {
// 源文件与目标文件
File sourceFile = new File("D:/", "test.txt");
File targetFile = new File("E:/", "test.txt");
// 输入输出流
FileInputStream fis = null;
FileOutputStream fos = null;
// 数据缓冲区
byte[] buf = new byte[1];
try {
fis = new FileInputStream(sourceFile);
fos = new FileOutputStream(targetFile);
// 数据读写
while (fis.read(buf) != -1) {
fos.write(buf);
// step2:然后在文件读写的while循环中,我们去模拟一个中断行为的发生。这里是当targetFile的文件长度为3个字节则模拟抛出一个我们自定义的异常。(我们可以想象为实际下载中,已经上传(下载)了”x”个字节的内容,这个时候网络中断了,那么我们就在网络中断抛出的异常中将”x”记录下来)。
if (targetFile.length() == 3) {
position = 3;
throw new FileAccessException();
}
}
} catch (FileAccessException e) {
//step3:开启”续传“行为,即keepGoing方法.
keepGoing(sourceFile,targetFile, position);
} catch (FileNotFoundException e) {
System.out.println("指定文件不存在");
} catch (IOException e) {
// TODO: handle exception
} finally {
try {
// 关闭输入输出流
if (fis != null)
fis.close();
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void keepGoing(File source,File target, int position) {
// step3.1:我们起头让线程休眠10秒钟,这正是为了让我们运行程序看到效果。
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// step3.2:在“续传”行为开始后,通过RandomAccessFile类来包装我们的文件,然后通过seek将指针指定到之前发生中断的位置进行读写就搞定了。
(实际的文件下载上传,我们当然需要将保存的中断值上传给服务器,这个方式通常为
try {
RandomAccessFile readFile = new RandomAccessFile(source, "rw");
RandomAccessFile writeFile = new RandomAccessFile(target, "rw");
readFile.seek(position);
writeFile.seek(position);
// 数据缓冲区
byte[] buf = new byte[1];
// 数据读写
while (readFile.read(buf) != -1) {
writeFile.write(buf);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class FileAccessException extends Exception {
}
(3)实现结果
运行程序,那么文件就会开启“由D盘上传到E盘的过程”,我们首先点开E盘,会发现的确多了一个test.txt文件,打开它发现内容如下:
这个时候我们发现内容只有“abc”。这是在我们预料以内的,因为我们的程序模拟在文件上传了3个字节的时候发生了中断。
等待10秒钟过去,然后再点开该文件,发现内容的确已经变成了“abc”,由此也就完成了续传。
android中的实现
import...;
2
3 public class MainActivity extends AppCompatActivity {
4
5 private EditText et_path;
6 private EditText et_threadCount;
7 private LinearLayout ll_pb;
8 private String path;
9
10 private static int runningThread;// 代表正在运行的线程
11 private int threadCount;
12 private List pbList;//集合存储进度条的引用
13
14 @Override
15 protected void onCreate(Bundle savedInstanceState) {
16 super.onCreate(savedInstanceState);
17 setContentView(R.layout.activity_main);
18
19 et_path = findViewById(R.id.et_path);
20 et_threadCount = findViewById(R.id.et_threadCount);
21 ll_pb = findViewById(R.id.ll_pb);
22 //添加一个进度条的引用
23 pbList = new ArrayList();
24 }
25
26 //点击按钮实现下载逻辑
27 public void click(View view) {
28 //获取下载路径
29 path = et_path.getText().toString().trim();
30 //获取线程数量
31 String threadCounts = et_threadCount.getText().toString().trim();
32 //移除以前的进度条添加新的进度条
33 ll_pb.removeAllViews();
34 threadCount = Integer.parseInt(threadCounts);
35 pbList.clear();
36 for (int i = 0; i < threadCount; i++) {
37 ProgressBar v = (ProgressBar) View.inflate(getApplicationContext(), R.layout.layout, null);
38
39 //把v添加到几何中
40 pbList.add(v);
41
42 //动态获取进度条
43 ll_pb.addView(v);
44 }
45
46 //java逻辑移植
47 new Thread() {
48 @Override
49 public void run() {
50 /*************/
51 System.out.println("你好");
52 try {
53 URL url = new URL(path);
54 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
55 conn.setRequestMethod("GET");
56 conn.setConnectTimeout(5000);
57 int code = conn.getResponseCode();
58 if (code == 200) {
59 int length = conn.getContentLength();
60 // 把运行线程的数量赋值给runningThread
61 runningThread = threadCount;
62
63 System.out.println("length=" + length);
64 // 创建一个和服务器的文件一样大小的文件,提前申请空间
65 RandomAccessFile randomAccessFile = new RandomAccessFile(getFileName(path), "rw");
66 randomAccessFile.setLength(length);
67 // 算出每个线程下载的大小
68 int blockSize = length / threadCount;
69 // 计算每个线程下载的开始位置和结束位置
70 for (int i = 0; i < length; i++) {
71 int startIndex = i * blockSize;// 开始位置
72 int endIndex = (i + 1) * blockSize;// 结束位置
73 // 特殊情况就是最后一个线程
74 if (i == threadCount - 1) {
75 // 说明是最后一个线程
76 endIndex = length - 1;
77 }
78 // 开启线程去服务器下载
79 DownLoadThread downLoadThread = new DownLoadThread(startIndex, endIndex, i);
80 downLoadThread.start();
81
82 }
83
84 }
85 } catch (MalformedURLException e) {
86 // TODO Auto-generated catch block
87 e.printStackTrace();
88 } catch (IOException e) {
89 // TODO Auto-generated catch block
90 e.printStackTrace();
91 }
92 /*************/
93 }
94 }.start();
95
96 }
97
98 private class DownLoadThread extends Thread {
99 // 通过构造方法吧每个线程的开始位置和结束位置传进来
100 private int startIndex;
101 private int endIndex;
102 private int threadID;
103 private int PbMaxSize;//代表当前下载(进度条)的最大值
104 private int pblastPosition;//如果中断过,这是进度条上次的位置
105
106 public DownLoadThread(int startIndex, int endIndex, int threadID) {
107 this.startIndex = startIndex;
108 this.endIndex = endIndex;
109 this.threadID = threadID;
110
111 }
112
113 @Override
114 public void run() {
115 // 实现去服务器下载文件
116 try {
117 //计算进度条最大值
118 PbMaxSize = endIndex - startIndex;
119 URL url = new URL(path);
120 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
121 conn.setRequestMethod("GET");
122 conn.setConnectTimeout(5000);
123 // 如果中间断过,接着上次的位置继续下载,聪慧文件中读取上次下载的位置
124 File file = new File(getFileName(path) + threadID + ".txt");
125 if (file.exists() && file.length() > 0) {
126 FileInputStream fis = new FileInputStream(file);
127 BufferedReader bufr = new BufferedReader(new InputStreamReader(fis));
128 String lastPosition = bufr.readLine();
129 int lastPosition1 = Integer.parseInt(lastPosition);
130
131 //赋值给进度条位置
132 pblastPosition = lastPosition1 - startIndex;
133 // 改变一下startIndex的值
134 startIndex = lastPosition1 + 1;
135 System.out.println("线程id:" + threadID + "真实下载的位置:" + lastPosition + "-------" + endIndex);
136
137 bufr.close();
138 fis.close();
139
140 }
141
142 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
143 int code = conn.getResponseCode();
144 if (code == 206) {
145 // 随机读写文件对象
146 RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
147 // 每个线程从自己的位置开始写
148
149 raf.seek(startIndex);
150 InputStream in = conn.getInputStream();
151 // 把数据写到文件中
152 int len = -1;
153 byte[] buffer = new byte[1024];
154 int totle = 0;// 代表当前线程下载的大小
155 while ((len = in.read(buffer)) != -1) {
156 raf.write(buffer, 0, len);
157 totle += len;
158
159 // 实现断点续传就是把当前线程下载的位置保存起来,下次再下载的时候按照上次下载的位置继续下载
160 int currentThreadPosition = startIndex + totle;// 存到一个txt文本中
161 // 用来存储当前线程当前下载的位置
162 RandomAccessFile raff = new RandomAccessFile(getFileName(path) + threadID + ".txt", "rwd");
163 raff.write(String.valueOf(currentThreadPosition).getBytes());
164 raff.close();
165
166 //设置进度条当前的进度
167 pbList.get(threadID).setMax(PbMaxSize);
168 pbList.get(threadID).setProgress(pblastPosition + totle);
169 }
170 raf.close();
171 System.out.println("线程ID:" + threadID + "下载完成");
172 // 将产生的txt文件删除,每个线程下载完成的具体时间不知道
173 synchronized (DownLoadThread.class) {
174 runningThread--;
175 if (runningThread == 0) {
176 //说明线程执行完毕
177 for (int i = 0; i < threadCount; i++) {
178
179 File filedel = new File(getFileName(path) + i + ".txt");
180 filedel.delete();
181 }
182
183 }
184
185 }
186
187 }
188 } catch (MalformedURLException e) {
189 // TODO Auto-generated catch block
190 e.printStackTrace();
191 } catch (IOException e) {
192 // TODO Auto-generated catch block
193 e.printStackTrace();
194 }
195
196 }
197 }
198
199 public String getFileName(String path) {
200 int start = path.lastIndexOf("/") + 1;
201 String subString = path.substring(start);
202 String fileName = "/data/data/com.lgqrlchinese.heima76android_11_mutildownload/" + subString;
203 return fileName;
204
205 }
206 }