android 多线程异步下载文件,造轮子之 Android 多线程多任务断点续传下载器(设计篇)...

2023-11-11

前段时间面试,被问到 app 的自动更新是怎么做的,文件下载怎么实现的?用了多线程吗?是否支持断点续传?一下蒙逼,因为直接用第三方框架实现的文件下载,这些问题完全没想过。

回来后觉得这里面其实涉及很多知识点,就打算自己动手封装一个支持多线程多任务断点续传的库,用了一个星期的业余时间,目前主要功能基本完成,所以记录一下这个过程中遇到的问题和收获

1. 涉及知识点

听起来并不复杂的一个功能,但是实际动手做起来发现还是涉及了很多知识点的,概括一下主要涉及以下几个部分:

HTTP 请求:为了尽量的减少对第三方框架的依赖,这个库里的 HTTP 请求部分就直接用 HttpURLConnection 来实现

断点续传:主要借助 RandomAccessFile 这个类来实现,这个类可以从文件的任意指定位置开始进行读写

多线程下载:涉及到多个子线程的同步,中断异常的处理,线程池的使用等

多任务下载:这里涉及到任务的调度和同步,比如限制同时下载的任务数,达到这个上限以后,再添加任务要等待,如何处理。暂停或者取消一个任务后如何自动启动等待的任务等。这里使用了阻塞队列和信号量来实现任务的调度

事件的发布:可以用广播, EventBus,Handler,回调

数据的持久化:退出程序后,保存下载任务的状态,再次打开后加载所有的任务,并能够继续下载。这里可以用数据库,临时文件 或者 SharedPreference 实现。

2. 整体设计

1. 下载请求的封装

用一个 JavaBean 类封装一个下载请求,包括了下载的 url 地址,文件保存的目录以及文件名,基本的参数其实就这三个,还可以根据需要添加更多的配置参数,比如指定并发的线程数等。如果参数比较多的情况,可以使用 Builder 模式

2. 任务的调度

回想我们使用迅雷下载时,填入下载链接,选好保存路径之后点击开始,任务就自动开始下载了。如果此时任务数已经达到上限,那么就会等待,直到有任务结束,再自动开始等待的任务。仔细思考之后,有以下几个要点:

因为添加任务是在主线程进行,所以应该是非阻塞的,任何时候执行添加一个任务都应该立即返回。所以这里考虑使用一个无上限的阻塞队列 LinkedBlockingQueue

任务一旦添加就自动开始,这里参考了 Volley 的 NetworkDispatcher 的设计,开启一个专门的任务调度线程,用一个死循环不断的从阻塞队列取出任务来执行,当队列为空时就阻塞,非空时就被唤醒并执行任务。其实就是一个典型的生产-消费模型

达到最大任务数后要等待有任务停止(包括成功,失败,暂停,取消几种情况)才能开始,这里很自然的想到用 Semaphore 来实现,当从阻塞队列取出一个任务后,还需要先成功获取一个信号量,才能继续开始执行,否则就阻塞。当任务停止的时候,释放一个信号量,之前等待的任务就可以自动开始执行了。

当然这里也可以不用信号量,通过一个计数器加一个等待队列实现调度。一个任务结束后,需要检查当前正在下载的任务数,以及是否有任务在等待队列,如果有并且计数器值小于上限值,就从等待队列取出一个任务执行。个人感觉使用信号量在概念上更加清晰。

3. 下载任务的执行

一个支持多线程断点续传的任务开始后,其实是分成了串行的两步执行的:

(1) 发起一次 HTTP 请求,获取下载文件的长度信息

(2) 根据文件的长度以及设置的线程数N,把下载任务分成N个子任务,每个子任务再分别发起HTTP请求,负责下载自己那一部分的数据并写入同一个文件中(RandomAccessFile 已经处理了同步问题)。

所以这里我的设计是先使用一个 AsyncTask 获取文件长度,再异步的回调里,开启N个子任务线程进行下载。

这里当然是使用线程池来执行子任务了,子任务都实现 Runnable 丢到线程池里。另外由于 AsyncTask 默认的实现是串行的,也可以让 AsyncTask 在默认的线程池上执行,这样就可以实现多个任务同时开始下载了。

4. 下载任务的封装和管理

首先要用一个类来描述一个下载任务,这个类的设计要考虑以下几点:

每一个下载任务和一个下载请求一一对应,所以下载任务中应该包含一个下载请求的字段

每个任务需要一个唯一的ID,这里考虑使用url+保存路径+文件名的字符串进行MD5运算,来作为一个任务的ID

需要记录下载文件的大小

需要一个字段标示当前任务所处的状态,比如正在下载,暂停,失败等,操作该字段需要同步

需要一个字段标示当前任务已经下载的字节数,操作该字段也需要同步

需要一个List字段保存已经开始下载的任务的子任务的信息,每个子任务中保存当前写入文件的位置以及结束写入的位置

然后就是需要一个集合来保存所有的已添加的任务,因为各种对任务的操作,比如暂停,取消,删除等都是要根据ID来找到一个对应任务,所以使用Map来保存可以保证查找的效率。

5. 事件发布设计

所有的事件都通过 LocalBroadcastManager 发布,然后使用者可以有两种方法实现对事件的监听,一种是定义自己的 Receiver 接收处理各种广播事件。还有一种是注册 Listener,然后我们在框架内部实现一个 BroadcastReceiver,根据不同的事件调用 Listener 的不同的方法,这样封装的更好,不过某些场景自己注册Receiver还是更灵活一些,可以在 switch 里面对多个 case 合并处理

6. 任务状态的切换

最主要的部分就是如何暂停或者取消一个正在进行的任务。在下载的子任务线程里,会有一个循环从InputStream读取数据并写入文件的操作,我们就在这个循环这里加入对任务状态的判断,当状态是Downloading时,就继续下载,当状态被设为 Paused 时,就跳出循环,这样就实现了任务的暂停。

当然也可以用 FutureTask.cancel(),在循环里判断 isInterrupted() 来实现,不过因为我们已经有了一个表示任务状态的字段,直接使用这个字段可以达到同样的效果。

当恢复一个暂停的任务时,不能让它直接开始,要把重新加到任务队列里面去,然后等待调度。因为可能已经达到任务上限,所以还是要重新拿到信号量才可以开始。

7. 任务的持久化

不考虑大量任务管理的场景的话,可以直接用 SharedPreference 配合 Gson 的序列化和反序列化,实现任务的持久化。用数据库的话就麻烦一点,要自己读写各个字段,当然也可以用 GreenDao,Realm 等orm框架,不过作为一个实验性项目,这块暂时先不做那么复杂吧。

3. 总结

初步的分析结束,整体的思路已经清楚了,主要的难点应该是在任务的调度,多线程的协作和同步。最后从用户的角度总结一下最终要实现的功能:

定义一个下载请求并加入下载队列,获得任务的ID以便后续的操作。任务自动开始下载,如果达到上限就等待

通过任务ID可以暂停,取消,恢复一个任务的执行

任何情况下退出都应该能保存任务的下载状态

下一篇就写具体的代码实现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android 多线程异步下载文件,造轮子之 Android 多线程多任务断点续传下载器(设计篇)... 的相关文章

  • layui显示表格数据的id的两种形式

    1 获取数据库表字段id field id title 用户ID width 100 fixed left align center templet function d return d id 2 templet属性获得id为 title
  • 【大模型】—LangChain开源框架介绍

    大模型 LangChain开源框架介绍 2023年可以说是AI大语言模型发展元年 随着OpenAI的ChatGPT和GPT 4的发布 点燃了人工智能大语言模型的发展浪潮 各大科技公司纷纷推出了自家的大语言模型产品 各国更是将大语言模型的发展
  • springboot 跨域过滤器配置

    添加maven包依赖
  • gbk to utf8 utf8 to gbk

    My Study About My Learn or Study etc GBK和UTF8之间的转换 By Cnangel on October 8 2012 10 10 AM No Comments 关于GBK和UTF 8之间的转换 很多
  • osg学习(七十一)如何给顶点着色器传递顶点数据

    缩放不会影响传递到着色器中顶点坐标缩放 osg会自动向着色器传递osg Vertex osg ModelViewProjectionMatrix等变量 不需要再定义 在着色器中直接使用即可 设置顶点数据 osg Geometry cpp v
  • 2579 启蒙练习-跑步问题

    有二个人在n米的椭圆形的跑道跑步 他们从同一个起点出发 两个人运动方向相同时 每a秒相遇一次 两个人运动方向相反时 每b秒相遇一次 求二人的速度 v1 v2 分别是多少 本题数据保证 n a b v1 v2 都会是整数 收起 输入 三个数
  • SQL Server 基础语法1(超详细!)

    文章目录 创建数据库 增加次要数据库文件 删除次要数据库文件 删除数据库 建立表格 新增列 改变长度 删除表 查询表 删除列 创建数据库 create database school 数据库名 on 数据文件 name school dat
  • SQL Server 基础操作(五)导入和导出数据表

    导入数据表 1 选择需要导数据的数据库右击 任务 导入数据 2 选择数据源 数据源代表数据表从哪里导入到当前的数据库中 填写数据源服务器名称 本地导入 1433 远程导入 IP 1433 3 选择导入的目标数据库 选择导入到那个数据库中 4
  • hive数据仓库课后答案

    第一章 数据仓库的简介 一 填空题 1 数据仓库的目的是构建面向 分析 的集成化数据环境 2 Hive是基于 Hadoop 的一个数据仓库工具 3 数据仓库分为3层 即 源数据层 数据应用层 和数据仓库层 4 数据仓库层可以细分为 明细层
  • k8s部署SpringCloud应用

    一 准备工作 将v2目录上传到 root 目录 下载地址 链接 https pan baidu com s 1oqED4Kew5BeLFqms6U6ISw 提取码 lzx9 springcloud1 项目 用k8s部署 eureka eur
  • (JAVA练习)输入,输出二维数组

    题目 输入 输出二维数组 解答 import java util Scanner public class Erweishuzu public static void main String args 二维数组练习 Scanner sc n
  • element-ui 中dialog居中

    标题element ui 中dialog居中 el dialog display flex flex direction column margin 0 important position absolute top 50 left 50
  • 一款强大的浏览器翻译插件 - 沉浸式的翻译

    起因 前一段时间谷歌翻译宣布跑路 不再对大陆用户提供服务 听闻这一噩耗我不由得心里一惊 燕子 啊不是 谷歌没有你我可咋活呀 对于没太大工作需求 顶多遇上几个不认识单词或需要翻译网页的我来说 Chrome 自带的谷歌翻译可以说是我最常用的翻译
  • micropython源码分析之qstr

    前言 最近在研究micropython的源码编译过程 简单记录下关于qstr部分内容 本篇文章基于micropython1 18版本源码 1 19版本及之后可能会略有差异 标识符与相应对象的联系 Micropython中有很多标识符 例如l
  • 工作笔记:TrueCrypt编译记录

    工作笔记 TrueCrypt编译记录 TrueCrypt的最新版本6 2可以从官方网站上下载 我从这里下载了一个6 1的 http freedos pri ee truecrypt 在TrueCrypt官方网站上很多旧版本都没了 这里却很全
  • 关于Python中中文文本文件使用二进制方式读取后的解码UnicodeDecodeError问题

    最近老猿在进行文件操作的验证测试 发现对于中文文本文件如果使用二进制方式打开 返回的类型是bytes 如果要转换成可读的字符串信息需要进行解码 可是老猿使用decode 或decode UTF 8 解码后报错 Traceback most
  • 从零开始SpringCloud Alibaba实战(79)——Spring-Boot+AOP+统计单次请求方法的执行次数和耗时

    文章目录 前言 代码 ThreadLocal方案 前言 作为工程师 不能仅仅满足于实现了现有的功能逻辑 还必须深入认识系统 一次请求 流经了哪些方法 执行了多少次DB操作 访问了多少次文件操作 调用多少次API操作 总共有多少次IO操作 多
  • Java技术体系平台

    Java SE Java Standard Edition 标准版 支持面向桌面级应用 如Windows下的应用程序 的Java平台 提供了完整的Java核心API 此版本以前称为J2SE Java EE Java Enterprise E
  • CMSIS 到底是什么?

    CMSIS 到底是什么 先来看看ARM公司对CMSIS的定义 ARM Cortex 微控制器软件接口标准 CMSIS 是 Cortex M 处理器系列的与供应商无关的硬件抽象层 CMSIS 可实现与处理器和外设之间的一致且简单的软件接口 从

随机推荐

  • 【网络自定向下的学习】——TCP3次握手和4次挥手详解

    目录 前言 一 可靠数据传输 1 确认应答机制 2 超时重传机制 二 建立连接 三次握手 1 建立连接的过程 2 为什么会有三次握手 3 三次握手可以携带数据吗 4 什么是半连接队列 三 断开连接 4次挥手 1 4次挥手的过程 2 为什么连
  • 浅谈ChatGPT与企业数字化转型

    ChatGPT作为当今一个现象级的爆款概念 它的出现 会与企业数字化碰撞出怎么样的花火 很多数字化转型中的企业 咨询师 也都把目光转向ChatGPT 以及ChatGPT背后的大模型 也许 ChatGPT会给数字化转型带来新一轮的发展 助推剂
  • .net 抽奖概率计算

    公司需要做一个大转盘抽奖的活动 其实最关键的地方就是奖品的概率计算了 不过前两天做的这个计算规则挺简单 设置每个奖品的概率 所有奖品概率之和 乘以 随机值 0 1之间的double类型小数 抽中值 然后循环判断每个奖品的概率 直到大于抽中值
  • RT-Thread内核启动流程

    一般了解一份代码大多从启动部分开始 同样这里也采用这种方式 先寻找启动的源头 RT Thread 支持多种平台和多种编译器 而 rtthread startup 函数是 RT Thread 规定的统一启动入口 一般执行顺序是 系统先从启动文
  • Git rebase -i 合并多次提交

    我们在开发项目的过程中可能提交了多次代码 但有时候需要合并多次commit 实现的效果如下 如果你需要合并多个commit就通过Git log看下查你要合并commit的ID 记住最早的commit ID 如 123abc git reba
  • 什么是MVVM,vue的MVVM原理

    1 Mvvm定义MVVM是Model View ViewModel的简写 即模型 视图 视图模型 模型 指的是后端传递的数据 视图 指的是所看到的页面 视图模型 mvvm模式的核心 它是连接view和model的桥梁 它有两个方向 一是将
  • [906]git设置忽略文件.gitignore

    在仓库目录下新建一个名为 gitignore的文件 因为是点开头 没有文件名 没办法直接在windows目录下直接创建 必须通过右键Git Bash 按照linux的方式来新建 gitignore文件 gitignore文件对其所在的目录及
  • KeyError错误

    KeyError错误出现时可能是检索不到这个键名 就我自己碰到的这个问题来说 是编码的原因 前因是用了一个别人写的读取标定参数的函数 def read calib file path taken from https github com
  • 用一个数组表示股票每天的价格,数组的第i个数表示股票在第i天的价格。 如果只允许进行一次交易,也就是说只允许买一支股票并卖掉,求最大的收益。

    用一个数组表示股票每天的价格 数组的第i个数表示股票在第i天的价格 如果只允许进行一次交易 也就是说只允许买一支股票并卖掉 求最大的收益 提示 从前向后遍历数组 记录当前出现过的最低价格 作为买入价格 并计算以当天价格出售的收益 作为可能的
  • Kati详解-Android10.0编译系统(五)

    Android取经之路 的源码都基于Android Q 10 0 进行分析 Android取经之路 系列文章 系统启动篇 Android系统架构Android是怎么启动的Android 10 0系统启动之init进程Android10 0系
  • GDI+ 中图像基本变换

    背景 图形变换是指对绘制的图形进行平移 旋转 伸缩等操作 由 Graphics 类提供对应的成员函数进行实现 平移变换 概述 平移变换将所绘制图形的坐标 x y 全部平移一个增量 dx dy 对应成员函数 TranslateTransfor
  • 有向图和有权图的邻接矩阵表示法

    矩阵有多少行多少列 取决于顶点的个数 有向的 称作弧 v2没有发出任何胡 v3发出一条到v4的 到其他顶点都没有弧 记为0 邻接矩阵的每一行记录了什么 记录了以当前的顶点出发的弧 即出度边 以当前顶点的为弧尾的值 每一列是什么呢 比如 v1
  • Android 中自定义ViewGroup实现流式布局的效果

    博主前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住也分享一下给大家 点击跳转到网站 前言 自定义View与自定义ViewGroup的区别 自定义View 在没有现成的View 需要自己实现的时候 就使用自定义View 一
  • cin,cout和scanf,printf速度差距

    这道题的数据量大概在1e5左右 第1 2行为C C 输入输出加速后cin cout的耗费时间 第3行为不加速的 cin cout的时间 第4行为scanf printf的时间
  • 【51单片机实验笔记】声学篇(一) 蜂鸣器基本控制

    目录 前言 硬件介绍 PWM基础 蜂鸣器简介 原理图分析 蜂鸣器驱动电路 软件实现 蜂鸣器短鸣 蜂鸣器功能封装 总结 前言 蜂鸣器在生活中的应用实则相当广泛 通过本章你将学会制造噪声 笑 你将学会驱动它们 并发出响声 硬件介绍 PWM基础
  • 常用植被物候提取方法 (TIMESATE/R语言/Python)

    文章内容仅用于自己知识学习和分享 如有侵权 还请联系并删除 一 Background 这篇文章介绍的非常全面 物候的提取通常包含两个步骤 1 曲线的重构拟合 curve fitting 和 2 物候矩阵的提取 phenological me
  • linux系统运维工程师面试题集锦(一)

    1 常见Linux的发行版有哪些 并描述不同发行版之间的联系与区别 Fedora 是基于RHEL CentOS Scientific Linux 和Oracle Linux的社区版本 相比RHEL Fedora打包了显著的更多的软件包 SU
  • Vue使用debugger

    vue开发时会遇到需要调试代码的情况 使用debugger可以很方便的进行debug 1 build webpack dev conf js 将devtool cheap module eval source map 改为devtool e
  • datasource无法加载问题

    解决办法 将静态资源导入去掉 这里我是确保代码 jar包与yaml正确的情况下发现的 将pom文件中自己写的静态资源导入删掉
  • android 多线程异步下载文件,造轮子之 Android 多线程多任务断点续传下载器(设计篇)...

    前段时间面试 被问到 app 的自动更新是怎么做的 文件下载怎么实现的 用了多线程吗 是否支持断点续传 一下蒙逼 因为直接用第三方框架实现的文件下载 这些问题完全没想过 回来后觉得这里面其实涉及很多知识点 就打算自己动手封装一个支持多线程多