鸿蒙网络编程系列30-断点续传下载文件示例

1. 断点续传简介

在文件的下载中,特别是大文件的下载中,可能会出现各种原因导致的下载暂停情况,如果不做特殊处理,下次还需要从头开始下载,既浪费了时间,又浪费了流量。不过,HTTP协议通过Range首部提供了对文件分块下载的支持,也就是说可以指定服务器返回文件特定范围的数据,这就为我们实现文件的断点续传提供了基础。RCP也很好的封装了这一点,通过Request对象的transferRange属性,可以支持分块下载,transferRange可以是TransferRange或者TransferRange数组,TransferRange类型包括两个属性from和to:

from?: number;
to?: number;

from用于设置传输数据的起始字节,to用于设置传输数据的结束字节。

有了RCP的支持,就比较容易实现文件的断点续传,本文将通过一个示例进行演示。

2.断点续传下载文件示例

本示例运行后的界面如图所示:

1.png

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

2.png

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

3.png

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

4.png

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

5.png

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

6.png

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

7.png

这样,就实现了任意时候中断下载或者退出应用都不影响已下载的部分,下次可以继续下载,实现了真正的断点续传。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/55920.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于web的酒店客房管理系统【附源码】

基于web的酒店客房管理系统&#xff08;源码L文说明文档&#xff09; 目录 4 系统设计 4.1 系统概述 4.2系统结构 4.3.数据库设计 4.3.1数据库实体 4.3.2数据库设计表 5系统详细实现 5.1 用户信息管理 5.2 会员信息管理 5.3 客房信息管理 5.…

k8s系列-Rancher 上操作的k8s容器网络配置总结

Rancher 上操作的k8s容器网络配置总结 要在 Rancher 中配置Spring Boot 应用 ykhd-zhjgyw-xpwfxfjfl 服务&#xff0c;正确的配置方式如下&#xff1a; 1. 应用程序监听端口 在 application.yaml 文件中&#xff0c;配置的应用监听端口是 10001&#xff0c;并且应用的上下文…

【Linux】Shell概念、命令、操作(重定向、管道、变量)

文章目录 一、概念篇1、shell的概念2、shell的分类 二、命令篇1、cat2、echo3、ps4、grep4.1、匹配行首4.2、大小写 5、sed 三、操作篇1、自动补全2、查看历史命令3、命令替换4、重定向4.1、输入重定向4.2、输出重定向4.3、错误重定向 5、管道6、shell中的变量6.1、本地变量6.2…

依赖标签分类任务Smin值计算(蛋白质功能预测,GO标签)

前言 Smin是在蛋白质功能预测中比较流行的一个指标&#xff0c;具体由来我也不甚清楚&#xff0c;只是在最近复现的几篇论文中反复出现了&#xff0c;所以记录一下。 计算方法 &#xff08;图来自于PSPGO论文&#xff09; 其中&#x1d70f;表示阈值&#xff0c;t表示GO标签…

Maven入门到进阶:构建、依赖与插件管理详解

文章目录 一、Maven介绍1、什么是Maven2、Maven的核心功能 二、Maven核心概念1、坐标GAVP1.1、GroupId1.2、ArtifactId1.3、Version1.3.1、版本号的组成 1.4、Packaging 2、POM、父POM和超级POM2.1、POM (Project Object Model)2.1、父POM&#xff08;Parent POM&#xff09;2.…

django连接mysql数据库

存在问题&#xff1a; django如何连接mysql数据库 解决方案&#xff1a; 创建工程和项目APP&#xff1b;修改Django的settings.py文件&#xff08;根据自己的数据库配置信息修改&#xff09;&#xff1b; 并在setting.py文件中添加app DATABASES {default: {# ENGINE: djang…

python 爬虫 入门 二、数据解析(正则、bs4、xpath)

目录 一、待匹配数据获取 二、正则 三、bs4 &#xff08;一&#xff09;、访问属性 &#xff08;二&#xff09;、获取标签的值 &#xff08;三&#xff09;、查询方法 四、xpath 后续&#xff1a;登录和代理 上一节我们已经知道了如何向服务器发送请求以获得数据&#x…

关于SSD1306的OLED的显示的研究

文章目录 函数作用参数解释嵌套函数分析主代码分析逻辑流程总结 难点的解析&#xff1a;生成器的主要逻辑分解&#xff1a;每次生成的元组 (pixel_x, pixel_y, pixel_mask)&#xff1a;生成器的整体流程举例总结 反转后的文本绘制竖直布局有问题的旋转180度旋转坐标轴绘制矩形绘…

SVM(支持向量机)

SVM&#xff08;支持向量机&#xff09; 引言 支持向量机(Support Vector Machine,SVM)&#xff0c;可以用来解答二分类问题。支持向量(Support Vector)&#xff1a;把划分数据的决策边界叫做超平面&#xff0c;点到超平面的距离叫做间隔。在SVM中&#xff0c;距离超平面最近…

【配色网站分享】

个人比较喜欢收藏一些好看的插画、UI设计图和配色&#xff0c;于是有了此篇&#xff0c;推荐一些配色网站&#xff0c;希望能对自己和大家有些帮助。 1.uiGradients 一个主打渐变风网站&#xff0c;还可以直接复制颜色。 左上角的“show all gradients”可以查看一些预设的渐…

upload-labs靶场Pass-02

upload-labs靶场Pass-02 分析源码 $is_upload false; $msg null; if (isset($_POST[submit])) {if (file_exists(UPLOAD_PATH)) {if (($_FILES[upload_file][type] image/jpeg) || ($_FILES[upload_file][type] image/png) || ($_FILES[upload_file][type] image/gif)) …

搭建Golang gRPC环境:protoc、protoc-gen-go 和 protoc-gen-go-grpc 工具安装教程

参考文章&#xff1a; 安装protoc、protoc-gen-go、protoc-gen-go-grpc-CSDN博客 一、简单介绍 本文开发环境&#xff0c;均为 windows 环境&#xff0c;mac 环境其实也类似 ~ ① 编译proto文件&#xff0c;相关插件 简单介绍&#xff1a; protoc 是编译器&#xff0c;用于将…

excel 表格中url转图片

待处理的单元格通过如下公式获取目标格式&#xff1a; "<table><img src"&A4&" height20></table>" 然后下拉后获取多列的单元格转换结果&#xff0c; 然后将这些转换后的结果拷贝到纯文本文档中&#xff0c; 然后再将纯文本…

音乐播放器-0.专栏介绍​

1.简介 本专栏使用Qt QWidget作为显示界面&#xff0c;你将会学习到以下内容&#xff1a; 1.大量ui美化的实例。 2.各种复杂ui布局。 3.常见显示效果实现。 4.大量QSS实例。 5.Qt音频播放&#xff0c;音乐歌词文件加载&#xff0c;展示。 6.播放器界面换肤。 相信学习了本专栏…

【Qt】Qt的介绍——Qt的概念、使用Qt Creator新建项目、运行Qt项目、纯代码方式、可视化操作、认识对象模型(对象树)

文章目录 Qt1. Qt的概念2. 使用Qt Creator新建项目3. 运行Qt项目3.1 纯代码方式实现3.2 可视化操作实现 4. 认识对象模型&#xff08;对象树&#xff09; Qt 1. Qt的概念 Qt 是一个跨平台的 C 图形用户界面应用程序开发框架。它是软件开发者提供的用于界面开发的程序框架&#…

Mysql(5)—函数

一、关于函数 1.1 简介 MySQL提供了许多内置的函数以帮助用户进行数据操作和分析。这些函数可以分为几类&#xff0c;包括聚合函数、字符串函数、数值函数、日期和时间函数、控制流函数等。 ​ ‍ 1.2 发展 早期版本&#xff08;MySQL 3.x 和 4.x&#xff09; : MySQL 最初…

无人机之三维航迹规划篇

一、基本原理 飞行环境建模&#xff1a;在三维航迹规划中&#xff0c;首先需要对飞行环境进行建模。这包括对地形、障碍物、气象等因素进行准确的测量和分析&#xff0c;以获得可行的飞行路径。 飞行任务需求分析&#xff1a;根据无人机的任务需求&#xff0c;确定航迹规划的…

Java最全面试题->计算机基础面试题->计算机网络面试题

计算机网络 下边是我自己整理的面试题&#xff0c;基本已经很全面了&#xff0c;想要的可以私信我&#xff0c;我会不定期去更新思维导图 哪里不会点哪里 1.说一下TCP/IP四层模型 TCP/IP协议是美国国防部高级计划研究局为实现ARPANET互联网而开发的。 网络接口层&#xff…

现代物流管理:SpringBoot技术突破

3系统分析 3.1可行性分析 通过对本智能物流管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本智能物流管理系统采用SSM框架&#xff0c;JAVA作为开发语…

【云从】九、CDN加速

文章目录 1、CDN基本概念2、CDN加速3、云CDN 1、CDN基本概念 源站&#xff1a;用户稳定运行的业务应用服务器 静态内容&#xff1a;用户多次访问某一资源&#xff0c;响应返回的数据都是相同的内容 例如:图片、视频、软件安装包、安卓 apk 安装包、压缩包文件等动态内容&…