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. Python+Selenium笔记(五):生成测试报告

    #HTMLTestRunner代码修改参考 微微微笑 的说明,下面是链接,这个已经说的很详细了 https://www.cnblogs.com/miniren/p/5301081.html (一) 前 ...

  2. markdown简单常用语法

    一.标题(符号要和文字之间加上一个字符的空格)# 一级标题## 二级标题### 三级标题#### 四级标题##### 五级标题###### 六级标题二.列表(符号要和文字之间加上一个字符的空格)有序列 ...

  3. 用例设计之API用例覆盖准则

    基本原则 本文主要讨论API测试的用例/场景覆盖,基本原则如下: 用户场景闭环(从哪来到哪去) 遍历所有的实现逻辑路径 需求点覆盖 覆盖维度 API协议(参数&业务场景) 中间件检查 异常场景 ...

  4. Vue组件学习

    根据Vue官方文档学习的笔记 在学习vue时,组件学习比较吃力,尤其是组件间的通信,所以总结一下,官方文档的组件部分. 注册组件 全局组件 语法如下,组件模板需要使用一个根标签包裹起来.data必须是 ...

  5. SQL Server 从2000复制数据到2008及以上版本的一种方法

    1.通过Linked Servers 执行sql出现错误提示,无法执行复制数据操作. sql: insert into tb_User select from [**.**.*.**].DB.dbo. ...

  6. PowerShell下载文件

    $webRequest = [System.Net.HttpWebRequest]::Create("http://go.microsoft.com/fwlink/?LinkID=14915 ...

  7. Qt: QAction在QToolBar中快捷键行为注意事项

    在QMenuBar中添加快捷键很简单,只要在text的特定字母前加&,如&k按下ALT+k即会触发(QPushButton也是一样).但在QToolBar则不然,需要调action-& ...

  8. NOIP2018考前抱佛脚——图论基础复习

    目录 存图方式 邻接矩阵存图 邻接表存图 链式前向星 最小生成树 例1 P1536 村村通 题目描述 输入输出格式 输入输出样例 标程 例2 P1546 最短网络 Agri-Net 题目背景 题目描述 ...

  9. oracle备份与恢复

    Oracle数据库有三种标准的备份方法,它们分别是导出/导入(EXP/IMP).热备份和冷备份.导出备件是一种逻辑备份,冷备份和热备份是物理备份. 导出/导入(Export/Import) 利用Exp ...

  10. Promise 模式解析:Promise模式与异步及声明式编程

    一.构建流程 1.(异步)数据源(请求)的构建:Promise的构建并执行请求: 2.处理流程的构建:then将处理函数保存: 二.处理: 1.请求的响应返回: 2.调用后继处理流程. 三. 1.构建 ...