0.  前言

在Android开发中,断点续传听起来挺容易,在下载一个文件时点击暂停任务暂停,点击开始会继续下载文件。但是真正实现起来知识点还是蛮多的,因此今天有时间实现了一下,并进行记录。本文原创,转载请注明出处为SEU_Calvin的博客

1.  断点续传原理

在本地下载过程中要使用数据库实时存储到底存储到文件的哪个位置了,这样点击开始继续传递时,才能通过HTTP的GET请求中的setRequestProperty()方法可以告诉服务器,数据从哪里开始,到哪里结束。同时在本地的文件写入时,RandomAccessFile的seek()方法也支持在文件中的任意位置进行写入操作。同时通过广播将子线程的进度告诉Activity的ProcessBar。

 

2.  Activity的按钮响应

当点击开始按钮时,将url写在了FileInfo类的对象info中并通过Intent从Activity传递到了Service中。这里使用setAction()来区分是开始按钮还是暂停按钮。

public class FileInfo implements Serializable{
private String url; //URL
private int length; //长度或结束位置
private int start; //开始位置
private int now;//当前进度
//构造方法,set/get略
}
//开始按钮逻辑,停止逻辑大致相同
strat.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,DownLoadService.class);
intent.setAction(DownLoadService.ACTION_START);
intent.putExtra("fileUrl",info);
startService(intent);
}
});


3.  在Service中的子线程中获取文件大小

在Service中的onStartCommand()中,将FileInfo对象从Intent中取出,如果是开始命令,则开启一个线程,根据该url去获得要下载文件的大小,将该大小写入对象并通过Handler传回Service,同时在本地创建一个相同大小的本地文件。暂停命令最后会讲到。

 public void run() {
HttpURLConnection urlConnection = null;
RandomAccessFile randomFile = null;
try {
URL url = new URL(fileInfo.getUrl());
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(3000);
urlConnection.setRequestMethod("GET");
int length = -1;
if (urlConnection.getResponseCode() == HttpStatus.SC_OK) {
//获得文件长度
length = urlConnection.getContentLength();
}
if (length <= 0) {
return;
}
//创建相同大小的本地文件
File dir = new File(DOWNLOAD_PATH);
if (!dir.exists()) {
dir.mkdir();
}
File file = new File(dir, FILE_NAME);
randomFile = new RandomAccessFile(file, "rwd");
randomFile.setLength(length);
//长度给fileInfo对象
fileInfo.setLength(length);
//通过Handler将对象传递给Service
mHandle.obtainMessage(0, fileInfo).sendToTarget();
} catch (Exception e) {
e.printStackTrace();
} finally { //流的回收逻辑略
}
}
}

4.  数据库操作封装

在Service的handleMessage()方法中拿到有length属性的FileInfo对象,并使用自定义的DownLoadUtil类进行具体的文件下载逻辑。这里传入上下文,因为数据库处理操作需要用到。

downLoadUtil = new DownLoadUtil(DownLoadService.this,info);
downLoadUtil.download();

这里有一个数据库操作的接口ThreadDAO,内部有增删改查等逻辑,用于记录下载任务的信息。自定义一个ThreadDAOImpl类将这里的逻辑实现,内部数据库创建关于继承SQLiteOpenHelper的自定义类的逻辑就不贴了,比较简单,该类会在ThreadDAOImpl类的构造方法中创建实例。完成底层数据库操作的封装。

public interface ThreadDAO {
//插入一条数据
public void insert(FileInfo info);
//根据URL删除一条数据
public void delete(String url);
//根据URL更新一条进度
public void update(String url,int finished);
//根据URL找到一条数据
public List<FileInfo> get(String url);
//是否存在
public boolean isExits(String url);
}

5.  具体的文件下载逻辑

public class DownLoadUtil {
//构造方法略
public void download(){
List<FileInfo> lists = threadDAO.get(fileInfo.getUrl());
FileInfo info = null;
if(lists.size() == 0){
//第一次下载,创建子线程下载
new MyThread(fileInfo).start();
}else{
//中间开始的
info = lists.get(0);
new MyThread(info).start();
}
} class MyThread extends Thread{
private FileInfo info = null;
public MyThread(FileInfo threadInfo) {
this.info = threadInfo;
}
@Override
public void run() {
//向数据库添加线程信息
if(!threadDAO.isExits(info.getUrl())){
threadDAO.insert(info);
}
HttpURLConnection urlConnection = null;
RandomAccessFile randomFile =null;
InputStream inputStream = null;
try {
URL url = new URL(info.getUrl());
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(3000);
urlConnection.setRequestMethod("GET");
//设置下载位置
int start = info.getStart() + info.getNow();
urlConnection.setRequestProperty("Range","bytes=" + start + "-" + info.getLength()); //设置文件写入位置
File file = new File(DOWNLOAD_PATH,FILE_NAME);
randomFile = new RandomAccessFile(file, "rwd");
randomFile.seek(start); //向Activity发广播
Intent intent = new Intent(ACTION_UPDATE);
finished += info.getNow(); if (urlConnection.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
//获得文件流
inputStream = urlConnection.getInputStream();
byte[] buffer = new byte[512];
int len = -1;
long time = System.currentTimeMillis();
while ((len = inputStream.read(buffer))!= -1){
//写入文件
randomFile.write(buffer,0,len);
//把进度发送给Activity
finished += len;
//看时间间隔,时间间隔大于500ms再发
if(System.currentTimeMillis() - time >500){
time = System.currentTimeMillis();
intent.putExtra("now",finished *100 /fileInfo.getLength());
context.sendBroadcast(intent);
}
//判断是否是暂停状态
if(isPause){
threadDAO.update(info.getUrl(),finished);
return; //结束循环
}
}
//删除线程信息
threadDAO.delete(info.getUrl());
}
}catch (Exception e){
e.printStackTrace();
}finally {//回收工作略
}
}
}
}

上面也讲到使用自定义的DownLoadUtil类进行具体的文件下载逻辑,这也是最关键的部分了,在该类的构造方法中进行ThreadDAOImpl实例的创建。并在download()中通过数据库查询的操作,判断是否是第一次开始下载任务,如果是,则开启一个子线程MyThread进行下载任务,否则将进度信息从数据库中取出,并将该信息传递给MyThread。

在MyThread中,通过info.getStart() + info.getNow()设置开始下载的位置,如果是第一次下载两个数将都是0,如果是暂停后再下载,则info.getNow()会取出非0值,该值来自数据库存储。使用setRequestProperty告知服务器从哪里开始传递数据,传递到哪里结束,本地使用RandomAccessFile的seek()方法进行数据的本地存储。使用广播将进度的百分比传递给Activity,Activity再改变ProcessBar进行UI调整。

这里很关键的一点是在用户点击暂停后会在Service中调用downLoadUtil.isPause = true,因此上面while循环会结束,停止下载并通过数据库的update()保存进度值。从而在续传时取出该值,重新对服务器发起文件起始点的下载任务请求,同时也在本地文件的相应位置继续写入操作。本文原创,转载请注明出处为SEU_Calvin的博客

6.  效果如下所示

Android开发——断点续传原理以及实现的更多相关文章

  1. Android开发学习笔记(二)——编译和运行原理(1)

    http://www.cnblogs.com/Pickuper/archive/2011/06/14/2078969.html 接着上一篇的内容,继续从全局了解Android.在清楚了Android的 ...

  2. Android开发进阶——自定义View的使用及其原理探索

    在Android开发中,系统提供给我们的UI控件是有限的,当我们需要使用一些特殊的控件的时候,只靠系统提供的控件,可能无法达到我们想要的效果,这时,就需要我们自定义一些控件,来完成我们想要的效果了.下 ...

  3. Android开发:图文分析 Handler通信机制 的工作原理

    前言 在Android开发的多线程应用场景中,Handler机制十分常用 下面,将图文详解 Handler机制 的工作原理 目录 1. 定义 一套 Android 消息传递机制 2. 作用 在多线程的 ...

  4. 《android开发艺术探索》读书笔记(四)--View工作原理

    接上篇<android开发艺术探索>读书笔记(三) No1: View的三大流程:测量流程.布局流程.绘制流程 No2: ViewRoot对应于ViewRootImpl类,它是连接Wind ...

  5. 【随笔】android开发的学习路线

    第一阶段:Java面向对象编程 1.Java基本数据类型与表达式,分支循环. 2.String和StringBuffer的使用.正则表达式. 3.面向对象的抽象,封装,继承,多态,类与对象,对象初始化 ...

  6. android开发的学习路线(转)

    第一阶段:Java面向对象编程 1.Java基本数据类型与表达式,分支循环. 2.String和StringBuffer的使用.正则表达式. 3.面向对象的抽象,封装,继承,多态,类与对象,对象初始化 ...

  7. android开发的学习路线

    参考资料:千锋3G学院--课程大纲    http://www.mobiletrain.org 看了专业的培训机构的课程大纲,才知道,自己学习android的路途才刚刚开始!特此整理分享一下,希望能帮 ...

  8. android 多线程断点续传下载

    今天跟大家一起分享下Android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基 ...

  9. android 开发从入门到精通

    Android-Tips This is an awesome list of tips for android. If you are a beginner, this list will be t ...

随机推荐

  1. Hibernate Criteria用法大全

    1.标准查询简介 2.比较运算符 3.分页使用标准 4.排序结果 5.预测与聚合 6.关联 7. 动态关联抓取 8.查询示例 9.投影(Projections).聚合(aggregation)和分组( ...

  2. Azure 中的虚拟网络和虚拟机

    创建 Azure 虚拟机 (VM) 时,必须创建虚拟网络 (VNet) 或使用现有的 VNet. 此外,还需要确定如何在 VNet 上访问 VM. 在创建资源之前必须做好规划,确保了解网络资源的限制. ...

  3. sqlserver 跨服务器备份表

    exec sp_configure 'show advanced options',1 reconfigure exec sp_configure 'Ad Hoc Distributed Querie ...

  4. 关于<asp:checkBoxList>控件的对齐方法

    定义和用法 TextAlign 属性用于获取或设置 CheckBoxList 项目的文本的文本对齐方式. 语法 <asp:CheckBoxList TextAlign="align&q ...

  5. cron定时任务介绍

    什么是cron? Cron是linux系统中用来定期执行或指定程序任务的一种服务或软件.与它相关的有两个工具:crond 和 crontab.crond 就是 cron 在系统内的宿主程序,cront ...

  6. security/pam_appl.h:没有那个文件或目录

    在编译开源库时, 提示 pam.h:4:10: 致命错误:security/pam_appl.h:没有那个文件或目录 #include <security/pam_appl.h> 解决方法 ...

  7. Linux每日小技巧---ss命令

    ss命令 ss是Socket Statistics的缩写.顾名思义,ss命令可以用来获取socket统计信息,它可以显示和netstat类似的内容.但ss的优势在于它能够显示更多更详细的有关TCP和连 ...

  8. 记录一次mysql使用load into命令导入csv格式数据的过程

    今天从qwiklab实验获取一组数据,大概有5万条,在qwiklab实验室使用的是pgsql数据库,但是今天想把他插入本地的mysql数据库中. 1.首先是查看一下数据内容: 数据中有的是空值,有的是 ...

  9. 读高性能JavaScript编程 第三章

    第三章  DOM Scripting  最小化 DOM 访问,在 JavaScript 端做尽可能多的事情. 在反复访问的地方使用局部变量存放 DOM 引用. 小心地处理 HTML 集合,因为他们表现 ...

  10. 【Alpha 冲刺】 12/12

    今日任务总结 人员 今日原定任务 完成情况 遇到问题 贡献值 胡武成 完成app端api编写 已完成 JAVA后端跨域访问没有处理(目前已解决),导致前端localhost请求失败而误以为自己操作失误 ...