鸿蒙网络编程系列30-断点续传下载文件示例
1. 断点续传简介
在文件的下载中,特别是大文件的下载中,可能会出现各种原因导致的下载暂停情况,如果不做特殊处理,下次还需要从头开始下载,既浪费了时间,又浪费了流量。不过,HTTP协议通过Range首部提供了对文件分块下载的支持,也就是说可以指定服务器返回文件特定范围的数据,这就为我们实现文件的断点续传提供了基础。RCP也很好的封装了这一点,通过Request对象的transferRange属性,可以支持分块下载,transferRange可以是TransferRange或者TransferRange数组,TransferRange类型包括两个属性from和to:
from?: number; to?: number;
from用于设置传输数据的起始字节,to用于设置传输数据的结束字节。
有了RCP的支持,就比较容易实现文件的断点续传,本文将通过一个示例进行演示。
2.断点续传下载文件示例
本示例运行后的界面如图所示:

首选输入要下载文件的URL,这里默认是下载的百度网盘的安装文件,大概98M左右,然后单击“选择”按钮选择本地保存路径,如下图所示:

选择保存的文件名称为demo.rpm,然后回到主界面,单击“下载”按钮就行下载:

如果要停止就可以单击“停止”按钮:

停止后可以单击“下载”按钮继续下载,或者也可以停止后退出应用:

重启启动后还会保持上传退出时的状态:

此时单击“下载”按钮还可以接着上次的进度继续下载,直到下载完成:

这样,就实现了任意时候中断下载或者退出应用都不影响已下载的部分,下次可以继续下载,实现了真正的断点续传。
3.断点续传下载文件示例编写
步骤1:创建Empty Ability项目。
步骤2:在module.json5配置文件加上对权限的声明
"requestPermissions": [{"name": "ohos.permission.INTERNET"}]
这里添加了访问互联网的权限。
步骤3:在Index.ets文件里添加如下的代码:
import fs from '@ohos.file.fs';
import { rcp } from '@kit.RemoteCommunicationKit';
import { getSaveFilePath} from './FileProcessHelper';
import { PersistenceV2 } from '@kit.ArkUI';
import { picker } from '@kit.CoreFileKit';
@ObservedV2//下载信息
class DownloadFileInfo {@Trace public url: string = "";@Trace public filePath: string = "";//任务状态//0:未开始 1:部分下载 2:下载完成@Trace public state: number = 0;@Trace public totalSize: number = 0;@Trace public downloadedSize: number = 0;
constructor(url: string, filePath: string) {this.url = urlthis.filePath = filePath}
}
@Entry
@ComponentV2
struct Index {@Local title: string = '断点续传下载文件示例';//连接、通讯历史记录@Local msgHistory: string = ''//每次下载的字节数downloadPerBatchSize: number = 64 * 1024defaultUrl ="https://4d677c-1863975141.antpcdn.com:19001/b/pkg-ant.baidu.com/issue/netdisk/LinuxGuanjia/4.17.7/baidunetdisk_4.17.7_x86_64.rpm"//是否正在下载@Local isRunning: boolean = false
//断点续传下载文件信息,使用PersistenceV2进行持久化存储@Local downloadFileInfo: DownloadFileInfo = PersistenceV2.connect(DownloadFileInfo,() => new DownloadFileInfo(this.defaultUrl, ""))!scroller: Scroller = new Scroller()
//当前会话currentSession: rcp.Session = rcp.createSession();//当前请求currentReq: rcp.Request | undefined = undefined
build() {Row() {Column() {Text(this.title).fontSize(14).fontWeight(FontWeight.Bold).width('100%').textAlign(TextAlign.Center).padding(5)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {Text("url地址:").fontSize(14).width(80).flexGrow(0)
TextInput({ text: this.downloadFileInfo.url }).onChange((value) => {this.downloadFileInfo.url = value}).width(110).fontSize(11).flexGrow(1)}.width('100%').padding(5)
Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {Text("本地保存路径:").fontSize(14).width(80).flexGrow(1)
Button("选择").onClick(async () => {this.downloadFileInfo.filePath = await getSaveFilePath(getContext(this))if (fs.accessSync(this.downloadFileInfo.filePath)) {fs.unlinkSync(this.downloadFileInfo.filePath)}}).width(110).fontSize(14)
Button(this.isRunning ? "停止" : "下载").onClick(() => {if (this.isRunning) {this.isRunning = falsethis.currentSession.cancel(this.currentReq)} else {this.downloadFile()}
}).enabled(this.downloadFileInfo.filePath != "" && this.downloadFileInfo.state != 2).width(110).fontSize(14)}.width('100%').padding(5)
Text(this.downloadFileInfo.filePath).fontSize(14).width('100%').padding(5)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {Column() {Text(`${(this.downloadFileInfo.totalSize == 0 ? 0 :((this.downloadFileInfo.downloadedSize / this.downloadFileInfo.totalSize) * 100).toFixed(2))}%`)}.width(200)
Column() {Progress({value: this.downloadFileInfo.downloadedSize,total: this.downloadFileInfo.totalSize,type: ProgressType.Capsule})}.width(150).flexGrow(1)}.visibility(this.downloadFileInfo.state != 0 ? Visibility.Visible : Visibility.None).width('100%').padding(10)
Scroll(this.scroller) {Text(this.msgHistory).textAlign(TextAlign.Start).padding(10).width('100%').backgroundColor(0xeeeeee)}.align(Alignment.Top).backgroundColor(0xeeeeee).height(300).flexGrow(1).scrollable(ScrollDirection.Vertical).scrollBar(BarState.On).scrollBarWidth(20)}.width('100%').justifyContent(FlexAlign.Start).height('100%')}.height('100%')}
//获取要下载的文件大小async getDownloadFileSize() {let size = 0const session = rcp.createSession();let resp = await session.head(this.downloadFileInfo.url)if (resp.statusCode != 200) {return 0;}if (resp.headers["content-length"] != undefined) {size = Number.parseInt(resp.headers["content-length"].toString())}this.msgHistory += `已获取文件大小${size}\r\n`return size;}
//下载到文件async downloadFile() {//如果文件大小为0,就获取文件大小if (this.downloadFileInfo.totalSize == 0) {this.downloadFileInfo.totalSize = await this.getDownloadFileSize()if (this.downloadFileInfo.totalSize == 0) {this.msgHistory += "获取文件大小失败\r\n"return}}
this.isRunning = truethis.downloadFileInfo.state = 1//每次下载的开始位置和结束位置let startIndex = this.downloadFileInfo.downloadedSizelet endIndex = startIndex + this.downloadPerBatchSizelet localFile = fs.openSync(this.downloadFileInfo.filePath, fs.OpenMode.READ_WRITE)fs.lseek(localFile.fd, 0, fs.WhenceType.SEEK_END)
//循环下载, 直到文件下载完成while (this.downloadFileInfo.downloadedSize < this.downloadFileInfo.totalSize && this.isRunning) {if (endIndex >= this.downloadFileInfo.totalSize) {endIndex = this.downloadFileInfo.totalSize - 1}let partDownloadResult = await this.downloadPartFile(startIndex, endIndex, localFile)if (!partDownloadResult) {return}
this.downloadFileInfo.downloadedSize += endIndex - startIndex + 1startIndex = endIndex + 1endIndex = startIndex + this.downloadPerBatchSize}
if (this.downloadFileInfo.downloadedSize == this.downloadFileInfo.totalSize) {this.downloadFileInfo.state = 2this.msgHistory += "文件下载完成\r\n"}fs.closeSync(localFile.fd)}
//下载指定范围的文件并追加写入到本地文件async downloadPartFile(from: number, to: number, localFile: fs.File): Promise<boolean> {this.currentReq = new rcp.Request(this.downloadFileInfo.url, "GET");this.currentReq.transferRange = { from: from, to: to }let resp = await this.currentSession.fetch(this.currentReq)
if (resp.statusCode != 200 && resp.statusCode != 206) {this.msgHistory += `服务器状态响应异常,状态码${resp.statusCode}\r\n`return false}
if (resp.body == undefined) {this.msgHistory += "服务器响应数据异常\r\n"return false}
if (resp.body.byteLength != to - from + 1) {this.msgHistory += "服务器响应的数据长度异常\r\n"return false}
fs.writeSync(localFile.fd, resp.body)fs.fsyncSync(localFile.fd)return true}
//选择文件保存位置async getSaveFilePath(): Promise<string> {let selectedSaveFilePath: string = ""let documentSaveOptions = new picker.DocumentSaveOptions();let documentPicker = new picker.DocumentViewPicker(getContext(this));await documentPicker.save(documentSaveOptions).then((result: Array<string>) => {selectedSaveFilePath = result[0]})return selectedSaveFilePath}
}
步骤4:编译运行,可以使用模拟器或者真机。
步骤5:按照本节第2部分“断点续传下载文件示例”操作即可。
4. 断点续传功能分析
要实现断点续传功能,关键点在于事先获取文件的大小以及每次请求时获取文件的一部分数据,获取文件大小是通过函数getDownloadFileSize实现的,在这个函数里通过http的head方法可以只获取响应的首部,其中包括文件的大小;获取文件部分数据是通过设置请求的transferRange属性实现的:
this.currentReq = new rcp.Request(this.downloadFileInfo.url, "GET");this.currentReq.transferRange = { from: from, to: to }let resp = await this.currentSession.fetch(this.currentReq)
另外,示例支持应用退出以及重启后的断点续传,这一点是通过PersistenceV2实现的,把断点续传下载文件信息自动进行持久化存储,下次启动时还可以自动加载,从而实现了完全的文件断点续传。
当然,这个示例还有很多需要完善的地方,比如,可以在重启后的断点续传前重新获取文件大小,并且和本地进行比较,防止服务端文件发生变化。
(本文作者原创,除非明确授权禁止转载)
本文源码地址:
https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/rcp/RCPDownloadFileDemo
本系列源码地址:
https://gitee.com/zl3624/harmonyos_network_samples
相关文章:
鸿蒙网络编程系列30-断点续传下载文件示例
1. 断点续传简介 在文件的下载中,特别是大文件的下载中,可能会出现各种原因导致的下载暂停情况,如果不做特殊处理,下次还需要从头开始下载,既浪费了时间,又浪费了流量。不过,HTTP协议通过Range…...
深入拆解TomcatJetty(二)
深入拆解Tomcat&Jetty(二) 专栏地址:https://time.geekbang.org/column/intro/100027701 1、Tomcat支持的IO模型和应用层协议 IO模型: NIO:非阻塞 I/O,采用 Java NIO 类库实现。NIO2:异…...
单元化架构,分布式系统的新王!
0 关键收获 单元化架构通过减少故障的爆炸半径来增加系统弹性单元化架构是那些任何停机时间都被认为是不可接受的,或者可以显著影响最终用户的系统的一个好选择单元化架构通过强制使用固定大小的单元作为部署单元,并倾向于扩展而不是扩展的方法…...
【力扣打卡系列】滑动窗口与双指针(乘积小于K的子数组)
坚持按题型打卡&刷&梳理力扣算法题系列,语言为go,Day6 乘积小于K的子数组 题目描述解题思路 双指针移动,遍历右端点right,滑动左端点left子数组的个数:固定右端点r,子数组的个数其实就是从l到r的元…...
浅谈微前端【qiankun】的应用
一、为什么要使用微前端 微前端的核心理念是将一个大型的单体前端应用拆分成多个独立的小型应用,以便各个应用能够独立开发、部署和更新。这带来了以下几个好处: 独立开发与部署:各个团队可以独立开发自己的子应用,快速上线新功能…...
【JavaEE】——四次挥手,TCP状态转换,滑动窗口,流量控制
阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 一:断开连接的本质 二:四次挥手 1:FIN 2:过程梳理 …...
D42【python 接口自动化学习】- python基础之函数
day42 高阶函数 学习日期:20241019 学习目标:函数﹣- 55 高阶函数:函数对象与函数调用的用法区别 学习笔记: 函数对象和函数调用 # 函数对象和函数调用 def foo():print(foo display)# 函数对象 a foo print(a) # &…...
GitLab 老旧版本如何升级?
极狐GitLab 正式对外推出 GitLab 专业升级服务 https://dl.gitlab.cn/cm33bsfv! 专业的技术人员为您的 GitLab 老旧版本实例进行专业升级!服务详情可以在官网查看详细解读! 那些因为老旧版本而被攻击的例子 话不多说,直接上图&a…...
现今 CSS3 最强二维布局系统 Grid 网格布局
深入学习 CSS3 目前最强大的布局系统 Grid 网格布局 Grid 网格布局的基本认识 Grid 网格布局: Grid 布局是一个基于网格的二位布局系统,是目前 CSS 最强的布局系统,它可以同时对列和行进行处理(它将网页划分成一个个网格,可以任…...
【图解版】力扣第146题:LRU缓存
力扣第146题:LRU缓存 一、LRU算法1. 基本概念2. LRU 和 LFU 的区别:3. 为什么 LRU 不需要记录使用频率? 二、Golang代码实现三、代码图解1. LRUCache、DLinkedNode两个结构体2. 初始化结构体对象3. addToHead函数4. removeNode函数5. moveToH…...
数据库知识点整理
DDL DDL-数据库操作 show databases ------------ 查看所有数据库 select database(); ----------查看当前数据库 create database 数据库名;---- 创建数据库 use 数据库名; --------------使用数据库 drop database 数据库名;--…...
【JVM】内存模型
文章目录 内存模型的基本概念案例 程序计数器栈Java虚拟机栈局部变量表栈帧中局部变量表的实际状态栈帧中存放的数据有哪些 操作数栈帧数据 本地方法栈 堆堆空间是如何进行管理的? 方法区静态变量存储 直接内存直接内存的作用 内存模型的基本概念 在前面的学习中,我们知道了字…...
代码随想录:二叉树的四种遍历
144. 二叉树的前序遍历 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullpt…...
【Linux】从多线程同步到生产者消费者模型:多线程编程实践
目录 1.线程的同步 1.1.为什么需要线程的同步? 2.2.条件变量的接口函数 2.生产消费模型 2.1 什么是生产消费模型 2.2.生产者消费者模型优点 2.3.为何要使用生产者消费者模型 3.基于BlockingQueue的生产者消费者模型 3.1为什么要将if判断变成whileÿ…...
如何在word里面给文字加拼音?
如何在word里面给文字加拼音?在现代社会,阅读已经成为了我们日常生活中不可或缺的一部分。尤其是在学习汉语的过程中,拼音的帮助显得尤为重要。为了帮助大家更好地理解和掌握汉字的发音,许多教师和学生都希望能够在Word文档中为文…...
Detr论文精读
摘要: 作者提到,该方法将物体检测看做直接的集合预测,在传统的目标检测算法中,会先生成候选区域,然后对每个候选区域进行单独的预测(包括物体的分类和预测框的回归),集合预测就是直…...
找寻孤独伤感视频素材的热门资源网站推荐
在抖音上,伤感视频总是能够引起观众的共鸣,很多朋友都在寻找可以下载伤感视频素材的地方。作为一名资深的视频剪辑师,今天我来分享几个提供高清无水印伤感素材的网站,如果你也在苦苦寻找这些素材,不妨看看以下推荐&…...
大模型~合集13
我自己的原文哦~ https://blog.51cto.com/whaosoft/12302606 #TextRCNN、TextCNN、RNN 小小搬运工周末也要学习一下~~虽然和世界没关 但还是地铁上看书吧, 大老勿怪 今天来说一下 文本分类必备经典模型 模型 SOTA!模型资源站收录情况 模型来源论文 RAE ht…...
【Next.js 项目实战系列】04-修改 Issue
原文链接 CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话,给我的库点个star,关注一下吧 上一篇【Next.js 项目实战系列】03-查看 Issue 修改 Issue 添加修改 Button 本节代码链接 安装 Radix UI 的 Ra…...
【Linux】并行与并发(含时间片)
简单来说 并发:多个进程轮流使用同一个CPU,在逻辑层面上,一段时间内推进完成了多个进程 并行:机器中有多个CPU可以使用,在物理层面上,做到同一时间会有多个进程同时在运行 举个例子:一群人需要…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
【若依】框架项目部署笔记
参考【SpringBoot】【Vue】项目部署_no main manifest attribute, in springboot-0.0.1-sn-CSDN博客 多一个redis安装 准备工作: 压缩包下载:http://download.redis.io/releases 1. 上传压缩包,并进入压缩包所在目录,解压到目标…...
【汇编逆向系列】六、函数调用包含多个参数之多个整型-参数压栈顺序,rcx,rdx,r8,r9寄存器
从本章节开始,进入到函数有多个参数的情况,前面几个章节中介绍了整型和浮点型使用了不同的寄存器在进行函数传参,ECX是整型的第一个参数的寄存器,那么多个参数的情况下函数如何传参,下面展开介绍参数为整型时候的几种情…...
(12)-Fiddler抓包-Fiddler设置IOS手机抓包
1.简介 Fiddler不但能截获各种浏览器发出的 HTTP 请求,也可以截获各种智能手机发出的HTTP/ HTTPS 请求。 Fiddler 能捕获Android 和 Windows Phone 等设备发出的 HTTP/HTTPS 请求。同理也可以截获iOS设备发出的请求,比如 iPhone、iPad 和 MacBook 等苹…...
华为云Flexus+DeepSeek征文 | MaaS平台避坑指南:DeepSeek商用服务开通与成本控制
作者简介 我是摘星,一名专注于云计算和AI技术的开发者。本次通过华为云MaaS平台体验DeepSeek系列模型,将实际使用经验分享给大家,希望能帮助开发者快速掌握华为云AI服务的核心能力。 目录 作者简介 前言 一、技术架构概览 1.1 整体架构设…...
SE(Secure Element)加密芯片与MCU协同工作的典型流程
以下是SE(Secure Element)加密芯片与MCU协同工作的典型流程,综合安全认证、数据保护及防篡改机制: 一、基础认证流程(参数保护方案) 密钥预置 SE芯片与MCU分别预置相同的3DES密钥(Key1、Key2…...
