之前的文章中,介绍了使用SFFT实现多线程下载的功能,今天有同学问到了异常退出如何继续下载,这篇介绍一下如何实现断点续下的功能。
实现效果:
实现下载暂停继续,异常退出程序,重新进入可以接续下载。

演示.gif
实现思路:
实现断点续下,只需要我们将下载任务的下载进度记录下来,如果暂停或者异常退出,重新进入下载任务的时候,判断有没有下载的缓存记录,如果有就接续下载,如果没有就重新下载。SFFT已经帮我们实现了缓存记录和读取,我们只需要调用即可。
实现过程:
1.要想实现断点续下,需要在初始化下载任务DownloadTask时,配置DownloadConfig开启断点续下isBreakpointResume
function getDownloadConfig(url:string,fileName:string,concurrency?:number): DownloadConfig{
return {
url: url, // 远端文件url地址
fileName: fileName+'ssf', // 本地文件名
concurrency:concurrency!=0?concurrency:1, // 启用的线程数,concurrency为1~8的正整数
isBreakpointResume: true, // 是否启用断点续下,isBreakpointResume为true时启用
maxRetries: 3, // 重试次数为3次
retryInterval: 2000, // 重试间隔为2000ms
}
}
SFFT源码 DownloadController
//开始下载
public async start() {
...
// 关闭当前已有的下载任务并清除相关信息
await this.cleanDirtyDownload();
...
// 生成下载任务并存储任务信息和分片信息到数据库与缓存
...
// 进行文件下载
this.executeDownload();
}
private executeDownload() {
...
// 启用断点续传的情况下,定时更新各分片下载进度到数据库
await DownloadInfoManager.getInstance().updateBlockInfos(this.downloadTaskMetadata);
...
}
由start方法可以发现,调用start时,会清除之前下载的缓存,重新下载。
SFFT源码 DownloadCacheManager
2.退出重新进入到下载页面时,初始话下载任务后,判断当前任务是否有下载缓存记录。
public getTaskInfoByUrlAndPath(url: string, fileDir: string, fileName: string): DownloadTaskInfo | undefined {
let taskInfo: DownloadTaskInfo | undefined;
for (const value of this.taskCache.values()) {
if (value.url === url && value.fileDir === fileDir && value.fileName === fileName) {
taskInfo = value;
break;
}
}
return taskInfo;
}
根据下载地址、文件名、存储路径判断当前下载任务是否有downloadTask信息。
3.通过DownloadController获取缓存进度
public async getProgress(): Promise<DownloadProgressInfo> {
try {
// 尝试从缓存中匹配downloadTask信息
DownloadInfoManager.getInstance().setTaskInfoByCache(this.downloadTaskMetadata);
return await this.downloadProgress.getDownloadProgressInfo();
} catch (err) {
Logger.error(LoggerConstants.DOWNLOAD, `Get progress failed,code: ${err.code}, message: ${err.message}`);
return {
transferredSize: 0,
totalSize: 0,
speed: 0
} as DownloadProgressInfo;
}
}
4.如果当前下载任务有缓存记录,继续下载,需要调用resume方法
public async resume() {
//如果没有开启断点续下 直接返回
//尝试从缓存中匹配下载信息并写入到downloadTask,不存在则直接退出,无法续下
//初始化下载进度
//校验链接和参数
//回调进度
//开始下载
实现源码
import { rcp } from '@kit.RemoteCommunicationKit';
import { getProgressPercent } from '../utils/CommonUtil';
import { download } from '../net/FileRequest';
import Logger from '../utils/Logger';
import { ProgressBtn } from './ProgressButton';
import { DownloadListener, DownloadProgressInfo } from '@hadss/super_fast_file_trans';
import { getProgress, initSfft, pause, resume, start } from '../net/SFFTRequest';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@ComponentV2
struct RcpDownLoadTest {
@Local downloadUrl: string =
'https://cangjie-lang.cn/v1/files/auth/downLoad?nsId=142267&fileName=cangjie-sdk-windows-x64-1.0.3.exe&objectKey=68e724d33115f673ef1280f8';
@Local downloadProgress: number = 0;
@Local downloadTime: number = 0;
@Local downloadtotalSize: string = '';
@Local ssfProgress: number = 0;
ssfstarttime: number = 0;
@Local totalSize: number = 0;
@Local ssfdownloadtime: number = 0;
onDownloadProgress: rcp.OnDownloadProgress = (totalSize, downloadedSize) => {
this.downloadtotalSize = (totalSize / 1024 / 1024).toFixed(2) + 'MB'
this.downloadProgress = getProgressPercent(totalSize, downloadedSize);
}
@Local downloadListener: DownloadListener = {}
@Local concurrency: number = 1;
@Local speed: number = 0;
@Local downloading:boolean = false;
async aboutToAppear() {
this.downloadListener = {
onStart: (trialResponseHeaders: Record<string, string | string[] | undefined>) => {
this.ssfstarttime = new Date().getTime()
this.downloading = true
},
onSuccess: (filePath: string) => {
this.ssfdownloadtime = (new Date().getTime() - this.ssfstarttime) / 1000
this.ssfProgress = 0;
},
onProgressUpdate: (downloadProgress: DownloadProgressInfo) => {
let transferredSize = downloadProgress.transferredSize;
this.totalSize = downloadProgress.totalSize;
this.speed = downloadProgress.speed/1024/1024;
this.ssfProgress = transferredSize / this.totalSize * 100;
},
onFail:(err: BusinessError) => {
},
onPause: (downloadProgressInfo: DownloadProgressInfo) => {
this.downloading = false
},
onResume:()=>{
this.downloading = true
}
}
await initSfft(this.downloadUrl, this.downloadListener, this.concurrency)
await getProgress().then((value)=>{
this.totalSize = value.totalSize
this.speed = value.speed
this.ssfProgress =this.totalSize==0?0:value.transferredSize / value.totalSize * 100;
})
}
build() {
Column({ space: 10 }) {
Text('文件大小:' + (this.totalSize / 1024 / 1024).toFixed(2) + 'MB ' + this.speed.toFixed(2)+'MB/S '+ ' 下载耗时:' + this.ssfdownloadtime + 'S').fontSize(18)
ProgressBtn({
progress: Number.parseFloat(this.ssfProgress.toFixed(2)) ,
text: 'SFFT多线程文件下载'
})
.margin({ bottom: 10 })
.onClick( () => {
if(this.downloading){
pause();
}else {
if (this.ssfProgress==0) {
start()
}else {
resume();
}
}
})
}
}
}
---------------------------SFFT初始化--------------------------
import { DownloadConfig, DownloadTask, DownloadManager, DownloadListener,DownloadProgressInfo } from '@hadss/super_fast_file_trans';
import { common } from '@kit.AbilityKit';
const uiContext: UIContext | undefined = AppStorage.get('uiContext');
let context = uiContext!.getHostContext()!;
let downloadInstance: DownloadTask | undefined;
function getDownloadConfig(url:string,fileName:string,concurrency?:number): DownloadConfig{
return {
url: url, // 远端文件url地址
fileName: fileName+'ssf', // 本地文件名
concurrency:concurrency!=0?concurrency:1, // 启用的线程数,concurrency为1~8的正整数
isBreakpointResume: true, // 是否启用断点续下,isBreakpointResume为true时启用
maxRetries: 3, // 重试次数为3次
retryInterval: 2000, // 重试间隔为2000ms
}
}
export async function initSfft(downloadUrl:string,downloadListener: DownloadListener,concurrency?:number){
await DownloadManager.getInstance().init(context as common.UIAbilityContext);
// 根据配置创建下载任务
downloadInstance = DownloadManager.getInstance().createDownloadTask(getDownloadConfig(downloadUrl,downloadUrl.split('/').pop() || '',concurrency), downloadListener);
}
export async function start() {
await downloadInstance?.start()
}
export async function pause(){
await downloadInstance?.pause()
}
export async function resume(){
await downloadInstance?.resume()
}
export async function getProgress(): Promise<DownloadProgressInfo>{
return await downloadInstance?.getProgress()!
}