文件下载之多线程断点续传技术底层实现
通过HttpURLConnection连接
断点续传核心步骤:
1.UI设计
2.获取服务器文件的大小,通过连接的一个方法getContentLength()来得到。
3.在客户端创建一个和将要下载的文件的同样大小的同名文件。
4.计算每个线程的起始位置和结束位置
5.开启多个线程,采用RandomAccessFile类,根据计算得到的起始位置和结束位置来随机的读取服务
器资源的输出流,并且实时的采用RandomAccessFile类保存线程读取到的字节位置。
6.判断线程结束和文件上传成功。
断点续传具体实现:
1.UI设计
下载主界面设计
进度条单独存放在一个布局文件中,注意进度条要采用以下的样式的进度条。
2.代码逻辑
多线程断点续传的技术可以说是目前学Android课程以来最难、代码最长的一个案例,那么如何
有章有序的将代码写出来呢?
思路整理:
1)网络访问权限和SD卡访问权限的添加
2)初始化动作及下载点击事件的书写
3)随机读取文件线程类的定义
具体代码:// 注册点击事件
public void click(View v) {
path = et_path.getText().toString().trim();
String threadcount_str = et_threadcount.getText().toString().trim();
final int threadcount = Integer.parseInt(threadcount_str);
if (TextUtils.isEmpty(path) || threadcount
Toast.makeText(getApplicationContext(), "输入错误", 0).show();
return;
}
list_pbBars = new ArrayList();
// 先清空进度条布局
ll_layout.removeAllViews();
// 添加进度条
for (int i = 0; i
//得到进度条的View对象
View item_pb_View = View.inflate(getApplicationContext(),R.layout.item_progress, null);
//通过View再得到进度条
ProgressBar pb_bar = (ProgressBar) item_pb_View.findViewById(R.id.item_pb);
list_pbBars.add(pb_bar);
ll_layout.addView(item_pb_View);
}
try {
//将服务器文件路径用File包装一下,以便获得它的文件名。
sourceFile = new File(path);
//访问URL得到文件的大小 ,通过Content-Length得到。
new Thread(){
public void run()
{
System.out.println("xxx"+Thread.currentThread().getName());
try {
//创建HttpURLConnection
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
//获取状态码
int responseCode = conn.getResponseCode();
if(responseCode == 200)
{
//获取资源文件的长度
sourceFileLength = conn.getContentLength();
sourceFileName = sourceFile.getName();
clientFile = new RandomAccessFile(Environment.getExternalStorageDirectory() + "/"
+ sourceFileName, "rw");
clientFile.setLength(sourceFileLength);
//正在运行的线程
runningThreadCount = threadcount;
// 第3步: 计算每个线程读取文件的起始和结束位置
int blockSize = sourceFileLength / threadcount;
for (int i = 0; i
//得到每个线程读取的起始和结束位置
int start_index = i * blockSize;
int end_index = (i + 1) * blockSize - 1;
int last_index = -1;
if(i == threadcount - 1)
{
end_index = sourceFileLength -1;
}
//------如果采用了断点续传,线程的起始位置就是上次记录位置。
File recordIndexFile = new File(Environment.getExternalStorageDirectory().getPath() + "/" +i + ".txt");
//如果有断点文件,就得到上次记录的读取位置。
if(recordIndexFile.exists() && recordIndexFile.length() > 0)
{
BufferedReader bufr = new BufferedReader(new FileReader(recordIndexFile));
last_index = Integer.parseInt(bufr.readLine());
//last_index++;
bufr.close();
}
else //否则上次读取位置就是开始位置。
{
last_index = start_index;
}
//设置进度条的总长度
list_pbBars.get(i).setMax((int)(end_index - start_index));
System.out.println("线程"+i +"::"+ last_index + "----" + end_index);
// 第4步:启动线程
new DownloadThread(i, start_index, end_index,last_index).start();
}
}
else
{
showToast("请求不到资源 ");
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
// 第5步:判断线程结束
} catch (Exception e) {
e.printStackTrace();
}
}
// 定义下载的线程类
class DownloadThread extends Thread {
private int threadID;
private int start_index, end_index,last_index;
private int total = 0;//记录已经读写的总的字节数
public DownloadThread(int threadID, int start_index, int end_index,int last_index) {
this.threadID = threadID;
this.start_index = start_index;
this.end_index = end_index;
this.last_index = last_index;
this.setName("线程" + threadID);
}
public void run() {
try {
//创建HttpURLConnection
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
//conn.setReadTimeout(5);
//设置一个头信息Range,表示要读取的流的字节范围。
conn.setRequestProperty("Range", "bytes=" + last_index + "-" + end_index);
//获取状态码
int responseCode = conn.getResponseCode();
//注意多线程下载状态得用206来判断
if(responseCode == 206)
{
//得到服务器的输出流
InputStream serverIn = conn.getInputStream();
//得到客户端的文件输出流
RandomAccessFile myClientFile = new RandomAccessFile(
Environment.getExternalStorageDirectory() + "/"
+ sourceFileName, "rwd");
myClientFile.seek(last_index);
//读取服务器文件写入到客户端文件中
//断点续传文件
int len = 0;
byte[] buf = new byte[1024*1024];
while((len = serverIn.read(buf))!= -1)
{
myClientFile.write(buf,0,len);
total += len;
//实时存储当前读取到的文件位置。
int current_index = last_index + total;
//因为此处实时的存储读取文件的位置,将缓冲区buf稍微弄大点。
// 改
/***注意读取文件位置的流一定要用RandomAccessFile,因为它可以直接同步到缓存(磁盘上)。***/
RandomAccessFile raffAccessFile = new RandomAccessFile(Environment.getExternalStorageDirectory().getPath()+"/"+threadID+".txt", "rwd");
raffAccessFile.write(String.valueOf(current_index).getBytes());
raffAccessFile.close();
//设置进度条的当前进度
list_pbBars.get(threadID).setProgress((int)(total + last_index - start_index));
}
myClientFile.close();
serverIn.close();
//操作共享资源,注意加锁。
synchronized (DownloadThread.class) {
runningThreadCount--;
if(runningThreadCount == 0)
{
Toast.makeText(getApplicationContext(), "文件下载完毕", 0).show();
}
}
}
else
{
showToast("服务器忙");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}