Android开发——断点续传原理以及实现

Android开发——断点续传原理以及实现0 前言在 Android 开发中 断点续传听起来挺容易 在下载一个文件时点击暂停任务暂停 点击开始会继续下载文件 但是真正实现起来知识点还是蛮多的 因此今天有时间实现了一下 并进行记录 本文原创 转载请注明出处为 SEU Calvin 的博客 1 断点续传原理在本地下载过程中要使用数据库实时存储到底存储到文件的哪个位置了 这样点击开始继续传递时 才能通过 HTTP 的 GET 请求中的 setReque

0.  前言

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

 

1.  断点续传原理

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

 

2.  Activity的按钮响应

当点击开始按钮时,将url写在了FileInfo类的对象info中并通过IntentActivity传递到了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.  数据库操作封装

ServicehandleMessage()方法中拿到有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 
  
    get(String url); //是否存在 public boolean isExits(String url); } 
  

5.  具体的文件下载逻辑

public class DownLoadUtil { //构造方法略 public void download(){ List 
  
    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告知服务器从哪里开始传递数据,传递到哪里结束,本地使用RandomAccessFileseek()方法进行数据的本地存储。使用广播将进度的百分比传递给ActivityActivity再改变ProcessBar进行UI调整。

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

最后补充一点,(17/4/11被面试问到了)关于断线续传的HTTP状态码并不是200,而是206,即HttpStatus.SC_PARTIAL_CONTENT。

6.  效果如下所示

Android开发——断点续传原理以及实现

Demo源码地址:链接: https://pan.baidu.com/s/1BYLtrt_rxuHFovuhirOw6g  密码: e25p

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/220338.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午8:44
下一篇 2026年3月17日 下午8:44


相关推荐

  • MySQL安装及可视化工具使用

    MySQL安装及可视化工具使用资源下载安装前配置安装及启动可视化工具安装及使用资源下载 MySQL 数据库 mysql 5 6 19 winx64 百度云地址可视化工具下载安装前配置解压文件将下载的数据库的压缩文件解压在本地文件夹 个人解压在 D MySQL 下 如下图 创建 my ini 文件将解压文件下的 my default ini 文件复制一份 改名为 my ini 如果没有 创建一个空的 txt 文件 改名为 my

    2026年3月20日
    1
  • Oracle number 类型转换为 varchar2「建议收藏」

    Oracle number 类型转换为 varchar2「建议收藏」项目初期表结构设计是非常重要,在字段类型定义样也要格外小心,业务开展后,修改字段类型代价非常大。本文主要记录在oracle中number类型转换为varchar2数据类型方法以及案例演示。number类型的数据直接存人varchar2类型的字段中,会出现格式问题,如:.5,5.等to_char(number)可将number类型转换为varchar2类型,可以指定格式fmt(可选);参数n,可以是NUMBER、BINARY_FLOAT或BINARY_DOUBL

    2022年7月24日
    224
  • (超详细!!)Pytorch循环神经网络(RNN)快速入门与实战

    (超详细!!)Pytorch循环神经网络(RNN)快速入门与实战0 前言很久没用过 Pytorch 忘得差不多了 最近课题需要用 所以整理一下 RNN 的使用方法 记得去年学这部分一直很迷糊 所以希望这篇博客能对大家有所帮助 1 简单循环神经网络结构先简单聊聊 RNN 的结构 最简单的一层 RNN 网络结构如下图所示 其中 每个箭头都表示一个权值 输入为向量 X x1 x2 xT X x 1 x 2 x T X x1 x2 xT 输出向量为 Y y1 y2 yT Y y1 y2 y T Y y1 y2 yT

    2026年3月26日
    1
  • 海量数据处理的 Top K 相关问题

    海量数据处理的 Top K 相关问题

    2021年11月22日
    36
  • WAR包补丁工具_修改war包配置文件

    WAR包补丁工具_修改war包配置文件简要:因目前处于运维历史悠久的WEB项目中,每次需求开发完成需要更打补丁文件,因此编写打补丁工具,以解决手动查找补丁文件的繁琐且重复操作。纯Java代码编写,使用Swing作为界面UI,原有代码只针对特殊使用场景,可以适当加以修改。适用:编译工具:EclipseLunaRelease(4.4.0)运行环境:JDK1.7代码:界面GUI部分:使用JSplitPane…

    2022年10月5日
    2
  • 在线教程| 腾讯混元开源端侧翻译工具HY

    在线教程| 腾讯混元开源端侧翻译工具HY

    2026年3月12日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号