您的位置:首页 > 移动开发 > Android开发

Android多线程断点续传下载

2016-07-03 22:26 387 查看
最近项目里用到了断点续传下载,所以在博客里做个记录,便于以后回顾。

背景知识

依赖于Http/206响应

首先你需要知道文件大小以及远程服务器是否支持HTTP 206请求.使用curl命令可以查看任意资源的HTTP头,使用下面的curl命令可以发送一个HEAD请求

$ curl -I http://avatar.csdn.net/D/0/9/1_wz249863091.jpg

这张图是我在CSDN博客的头像



这里只看2个属性

Accept-Ranges: bytes - 该响应头表明服务器支持Range请求,以及服务器所支持的单位是字节(这也是唯一可用的单位).我们还能知道:服务器支持断点续传,以及支持同时下载文件的多个部分,也就是说下载工具可以利用范围请求加速下载该文件.Accept-Ranges: none 响应头表示服务器不支持范围请求.

Content-Length: 30220 - Content-Length响应头表明了响应实体的大小,也就是真实的图片文件的大小是30220字节 (30K).

正常情况下,我们不指定range属性的时候,默认值都是0-length,也就是全量下载,返回的code就是200

如果我们指定了range,那么返回的code就是206

代码实现

根据上面的背景知识,可以写一个工具类,便于整个项目使用

package com.example.tony.myapplication;

import android.os.Environment;
import android.text.TextUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
* 断点续传工具类
*
* @author Tony.W
*/
public class DownloadUtil {
private static final String RANGE = "Range";
private static final String BYTE = "bytes=";
private static final String TO = "-";
private static final String RWS = "rws";
private static final String SLASH = "/";
private static final String UTF_8 = "utf-8";

//Http协议--部分下载的状态码
private static final int HTTP_PARTIAL_CODE = 206;
//Htpp协议--下载成功的状态码
private static final int HTPP_SUCCESS_CODE = 200;
//Http超时时间
private static final int TIME_OUT = 3000;
//默认下载线程数
private static final int DEFALUT_THREAD_COUNT = 3;

/**
* 断点续传下载
*
* @param path 资源路径
* @param threadCount 下载线程数
* */
public static void down(String path, int threadCount){
URL url = null;
HttpURLConnection conn = null;
RandomAccessFile raf = null;
try {
url = new URL(path);
conn = (HttpURLConnection) url.openConnection();
if (conn.getResponseCode() == HTPP_SUCCESS_CODE){
//文件长度
int fileLen = conn.getContentLength();
//缓存文件名字
String cacheFileName = path.substring(path.lastIndexOf(SLASH) + 1);
File file = new File(Environment.getExternalStorageDirectory(), cacheFileName);
raf = new RandomAccessFile(file, RWS);
raf.setLength(fileLen);
int partialLen = fileLen / threadCount;
for(int i = 0;i<threadCount;i++){
new DownloadThread(url, i, partialLen, file);
}
}

} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(raf != null){
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

/**
* 断点续传下载,默认开启3个线程下载
*
* @param path 资源路径
* */
public static void down(String path){
down(path, DEFALUT_THREAD_COUNT);
}

private static final class DownloadThread extends  Thread{
//下载资源的url
private URL url;
//下载的线程Id
private int threadId;
//每个线程需要下载的长度
private int particalLen;
//缓存文件地址
private File file;
//是否暂停下载
private boolean isPause = false;

/**
* 下载线程
* */
public DownloadThread(URL url, int threadId, int particalLen, File file){
this.file = file;
this.url = url;
this.particalLen = particalLen;
this.threadId = threadId;
}

/**
* 从文件读取数据
* */
public static String readFromFile(File file) throws IOException {
if (!file.exists() || file.isDirectory()) {
return null;
}
BufferedReader br = new BufferedReader(new FileReader(file));
String temp = null;
StringBuffer sb = new StringBuffer();
temp = br.readLine();
while (temp != null) {
sb.append(temp);
temp = br.readLine();
}
return sb.toString();
}

/**
* 向文件写入数据
* */
public static void writeToFile(File file, String data) throws IOException {
if (!file.exists()) {
file.createNewFile();
}
//重新写入,不是追加写入模式
FileOutputStream out = new FileOutputStream(file, false);
out.write(data.getBytes(UTF_8));
out.close();
}

@Override
public void run() {
downTask();
}

private void downTask(){
//缓存文件名字
String cacheFileName = Environment.getExternalStorageDirectory() + url.getPath().substring(url.getPath().lastIndexOf(SLASH) + 1 + threadId);
File cacheFile = new File(cacheFileName);
//读取缓存文件,判断是否之前已经有下载部分
float hasDownLen = 0;
//下载开始位置
int start = 0 + threadId * particalLen;
//下载结束为止
int end = (0 + threadId + 1) * particalLen -1;

String data = null;
try {
data = readFromFile(cacheFile);
} catch (IOException e) {
e.printStackTrace();
}

if (!TextUtils.isEmpty(data)) {
hasDownLen = Float.valueOf(data);
start += hasDownLen;
}

HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) url.openConnection();
} catch (IOException e) {
e.printStackTrace();
}
if(conn == null){
return;
}

conn.setReadTimeout(TIME_OUT);
//指定下载部分,如果超过服务器部分,以服务器为准
conn.setRequestProperty(RANGE, BYTE + start + TO + end);
RandomAccessFile raf = null;
InputStream in = null;
try {
if (conn.getResponseCode() == HTTP_PARTIAL_CODE) {
raf = new RandomAccessFile(file, RWS);
raf.seek(start);
in = conn.getInputStream();
//分段下载,每段大小为1024个字节
byte[] buf = new byte[1024];
int len = 0;
//如果读到最后,就跳出循环
while ((len = in.read(buf)) != -1) {
//将下载的数据存到文件
raf.write(buf);
//记录下载长度
hasDownLen += len;
writeToFile(cacheFile, String.valueOf(hasDownLen));
}
//如果完成该部分下载,删除缓存文件
cacheFile.delete();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (raf != null) {
raf.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}


这里只实现了多线程断点续传功能,如果要结合UI,可以加入一个Handler。

但是需要主要,由于都是static的静态方法,如果处理Handler的时候需要格外小心,不要造成内存泄漏

另外可以观察DDMS,在下载的时候,会产生N+1个文件,N就是你开启线程的数量,当下载全部完成后,只会留下一个数据文件,其余临时缓存文件都会被删除

附件已经上传,有需要的朋友可以下载

http://download.csdn.net/detail/wz249863091/9566461

如果有什么问题,请留言或者私信,第一时间会做出合理修改
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: