Android多线程断点续传下载
2016-07-03 22:26
387 查看
最近项目里用到了断点续传下载,所以在博客里做个记录,便于以后回顾。
首先你需要知道文件大小以及远程服务器是否支持HTTP 206请求.使用curl命令可以查看任意资源的HTTP头,使用下面的curl命令可以发送一个HEAD请求
$ curl -I http://avatar.csdn.net/D/0/9/1_wz249863091.jpg
这张图是我在CSDN博客的头像
![](https://img-blog.csdn.net/20160703221816272)
这里只看2个属性
Accept-Ranges: bytes - 该响应头表明服务器支持Range请求,以及服务器所支持的单位是字节(这也是唯一可用的单位).我们还能知道:服务器支持断点续传,以及支持同时下载文件的多个部分,也就是说下载工具可以利用范围请求加速下载该文件.Accept-Ranges: none 响应头表示服务器不支持范围请求.
Content-Length: 30220 - Content-Length响应头表明了响应实体的大小,也就是真实的图片文件的大小是30220字节 (30K).
正常情况下,我们不指定range属性的时候,默认值都是0-length,也就是全量下载,返回的code就是200
如果我们指定了range,那么返回的code就是206
这里只实现了多线程断点续传功能,如果要结合UI,可以加入一个Handler。
但是需要主要,由于都是static的静态方法,如果处理Handler的时候需要格外小心,不要造成内存泄漏
另外可以观察DDMS,在下载的时候,会产生N+1个文件,N就是你开启线程的数量,当下载全部完成后,只会留下一个数据文件,其余临时缓存文件都会被删除
附件已经上传,有需要的朋友可以下载
http://download.csdn.net/detail/wz249863091/9566461
如果有什么问题,请留言或者私信,第一时间会做出合理修改
背景知识
依赖于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
如果有什么问题,请留言或者私信,第一时间会做出合理修改
相关文章推荐
- Android 笔记整理
- Android初级教程XUtils实现“断点续传”下载
- Android初级教程XUtils实现“断点续传”下载
- Android学习笔记01——了解Android(系统框架、应用框架)
- Qt for Android 应用设置为中文名
- Android如何使用API
- android的生命周期
- Android四大组件之Service进阶
- Android平台Camera实时滤镜实现方法探讨(五)--GLSurfaceView实现Camera预览
- Android Data Binding 系列(一) -- 详细介绍与使用
- 如何在Fragment中获取Activity
- Android动画机制总结
- 【黑马Android】(16)XML/DTD/Sax解析/Schema语法简介
- 简单相机Demo
- Android四大组件之Service初涉
- android模拟器停在Waiting for HOME解决方案
- Android之片段Fragment的介绍(一)
- Android Studio官方文档之Android Studio代理配置篇
- Android中Adapter之BaseAdapter使用
- Android Studio启动异常