鸿蒙网络编程系列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.rhkb.cn/news/452147.html

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

相关文章

信息安全工程师(58)网络安全漏洞处置技术与应用

前言 网络安全漏洞处置技术与应用是一个复杂而关键的领域&#xff0c;它涉及漏洞的发现、评估、修补以及后续的监控与防范等多个环节。 一、网络安全漏洞发现技术 网络安全漏洞发现技术是漏洞处置的首要步骤&#xff0c;它旨在通过各种手段识别出网络系统中存在的潜在漏洞。这些…

jupyter notebook远程连接服务器

jupyter notebook远程连接服务器 文章目录 jupyter notebook远程连接服务器jupyter是什么配置步骤安装jupyter生成jupyter配置文件编辑jupyter配置文件设置密码ssh隧道 启动顺序jupyter添加kernel下载ipykernel包添加kernel 测试遇到的问题 jupyter是什么 Jupyter Notebook是一…

数据结构之队列(python)

华子目录 1.队列存储结构1.1队列基本介绍1.2队列的实现方式 2.顺序队列2.1顺序队列的介绍2.2顺序队列的简单实现2.3代码实现 3.链式队列和基本操作3.1链式队列数据入队3.2链式队列数据出队3.3队列的链式表示和实现 1.队列存储结构 1.1队列基本介绍 队列的两端都"开口&qu…

FFmpeg 4.3 音视频-多路H265监控录放C++开发三 :安装QT5.14.2, 并将QT集成 到 VS2019中。

一&#xff0c;安装QT&#xff0c; 重点&#xff1a;在安装QT的时候要安装msvc201x版本的组件&#xff0c; 二 &#xff0c; 安装 qt-vs-tools Index of /development_releases/vsaddin/2.8.1 三&#xff0c;需要安装过 windows10 SDK&#xff0c;一般我们在安装vs2019的时候就…

餐饮店怎么标注地图位置信息?

随着市场竞争的日益激烈&#xff0c;商家若想在竞争中脱颖而出&#xff0c;就必须想方设法去提高自身的曝光度和知名度&#xff0c;为店铺带来更多的客流量。其中&#xff0c;地图标注便是一种简单却极为有效的方法。通过在地图平台上添加店铺位置信息&#xff0c;不仅可以方便…

电子级异丙醇溶液除硼树脂

电子级异丙醇溶液的净化除杂是一个精细的过程&#xff0c;旨在去除溶液中的杂质&#xff0c;以满足电子行业对高纯度化学品的严格要求。以下是电子级异丙醇溶液净化除杂的相关信息&#xff1a; 净化除杂方法 ● 精馏工序&#xff1a;通过精馏塔进行初步分离&#xff0c;去除大部…

(44)MATLAB读取语音信号进行频谱分析

文章目录 前言一、MATLAB代码二、仿真结果画图三、频谱分析 前言 语音信号是我们最常见的一种信号&#xff0c;本文使用MATLAB读取一段语音信号画出其波形&#xff0c;然后使用FFT变换给出其频谱&#xff0c;对其频谱进行分析。 一、MATLAB代码 读取语音数据并得出频谱的代码…

JMeter如何设置HTTP代理服务器?

1、 2、添加线程组 3、设置HTTP代理服务器&#xff0c;目标控制器选择“测试计划>线程组” 过滤掉不需要的信息 4、设置电脑手动代理 5、点击启动&#xff0c;在浏览器操作就可以了

Halcon实战——基于NCC模板匹配的芯片检测(附源码)

Halcon实战——基于NCC模板匹配的芯片检测&#xff08;附源码&#xff09; 关于作者 作者&#xff1a;小白熊 作者简介&#xff1a;精通python、matlab、c#语言&#xff0c;擅长机器学习&#xff0c;深度学习&#xff0c;机器视觉&#xff0c;目标检测&#xff0c;图像分类&am…

OpenCV高级图形用户界面(10)创建一个新的窗口函数namedWindow()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 创建一个窗口。 函数 namedWindow 创建一个可以作为图像和跟踪条占位符的窗口。创建的窗口通过它们的名字来引用。 如果已经存在同名的窗口&am…

应用层协议 序列化

自定义应用层协议 例子&#xff1a;网络版本计算器 序列化反序列化 序列化&#xff1a;将消息&#xff0c;昵称&#xff0c;日期整合成消息-昵称-日期 反序列化&#xff1a;消息-昵称-日期->消息&#xff0c;昵称&#xff0c;日期 在序列化中&#xff0c;定义一个结构体…

第8篇:网络安全基础

目录 引言 8.1 网络安全的基本概念 8.2 网络威胁与攻击类型 8.3 密码学的基本思想与加密算法 8.4 消息认证与数字签名 8.5 网络安全技术与协议 8.6 总结 第8篇&#xff1a;网络安全基础 引言 在现代信息社会中&#xff0c;计算机网络无处不在&#xff0c;从互联网到局…

C语言_指针_进阶

引言&#xff1a;在前面的c语言_指针初阶上&#xff0c;我们了解了简单的指针类型以及使用&#xff0c;下面我们将进入更深层次的指针学习&#xff0c;对指针的理解会有一个极大的提升。从此以后&#xff0c;指针将不再是难点&#xff0c;而是学习底层语言的一把利器。 本章重点…

Mysql(2)—SQL语法详解(通俗易懂)

一、关于SQL 1.1 简介 SQL&#xff08;Structured Query Language&#xff0c;结构化查询语言&#xff09;是一种用于管理关系型数据库的标准编程语言。它主要用于数据的查询、插入、更新和删除等操作。SQL最初在1970年代由IBM的研究人员开发&#xff0c;旨在处理关系数据模型…

API的力量:解决编程技术问题的利器

在软件开发的世界里&#xff0c;编程技术问题无处不在。从数据获取到用户认证&#xff0c;从支付处理到地图服务&#xff0c;这些问题的解决方案往往需要深厚的专业知识和大量的开发时间。然而&#xff0c;应用程序编程接口&#xff08;API&#xff09;的出现&#xff0c;为开发…

长序列时间序列预测模型:Informer与TimesNet

Informer超越长序列时间序列预测 Informer是一种针对长序列时间序列预测的高效Transformer模型&#xff0c;旨在解决传统Transformer在处理长序列时的局限性。该模型引入了一些关键技术&#xff0c;以提高效率和准确性。以下是对Informer模型的详细介绍&#xff1a; 1. 模型背…

CMOS晶体管的串联与并联

CMOS晶体管的串联与并联 前言 对于mos管的串联和并联&#xff0c;一直没有整明白&#xff0c;特别是设计到EDA软件中&#xff0c;关于MOS的M和F参数&#xff0c;就更困惑了&#xff0c;今天看了许多资料以及在EDA软件上验证了电路结构与版图的对应关系&#xff0c;总算有点收…

opencv 图像翻转- python 实现

在做图像数据增强时会经常用到图像翻转操作 flip。 具体代码实现如下&#xff1a; #-*-coding:utf-8-*- # date:2021-03 # Author: DataBall - XIAN # Function: 图像翻转import cv2 # 导入OpenCV库path test.jpgimg cv2.imread(path)# 读取图片 cv2.namedWindow(image,1) …

go压缩的使用

基础&#xff1a;使用go创建一个zip func base(path string) {// 创建 zip 文件zipFile, err : os.Create("test.zip")if err ! nil {panic(err)}defer zipFile.Close()// 创建一个新的 *Writer 对象zipWriter : zip.NewWriter(zipFile)defer zipWriter.Close()// 创…

D39【python 接口自动化学习】- python基础之函数

day39 函数的返回值 学习日期&#xff1a;20241016 学习目标&#xff1a;函数&#xfe63;-52 函数的返回值&#xff1a;如何得到函数的执行结果&#xff1f; 学习笔记&#xff1a; return语句 返回值类型 def foo():return abc var foo() print(var) #abc# 函数中return函…