之前的文章中,介紹了使用SFFT實現(xiàn)多線程下載的功能,今天有同學(xué)問到了異常退出如何繼續(xù)下載,這篇介紹一下如何實現(xiàn)斷點續(xù)下的功能。
實現(xiàn)效果:
實現(xiàn)下載暫停繼續(xù),異常退出程序,重新進入可以接續(xù)下載。

演示.gif
實現(xiàn)思路:
實現(xiàn)斷點續(xù)下,只需要我們將下載任務(wù)的下載進度記錄下來,如果暫停或者異常退出,重新進入下載任務(wù)的時候,判斷有沒有下載的緩存記錄,如果有就接續(xù)下載,如果沒有就重新下載。SFFT已經(jīng)幫我們實現(xiàn)了緩存記錄和讀取,我們只需要調(diào)用即可。
實現(xiàn)過程:
1.要想實現(xiàn)斷點續(xù)下,需要在初始化下載任務(wù)DownloadTask時,配置DownloadConfig開啟斷點續(xù)下isBreakpointResume
function getDownloadConfig(url:string,fileName:string,concurrency?:number): DownloadConfig{
return {
url: url, // 遠端文件url地址
fileName: fileName+'ssf', // 本地文件名
concurrency:concurrency!=0?concurrency:1, // 啟用的線程數(shù),concurrency為1~8的正整數(shù)
isBreakpointResume: true, // 是否啟用斷點續(xù)下,isBreakpointResume為true時啟用
maxRetries: 3, // 重試次數(shù)為3次
retryInterval: 2000, // 重試間隔為2000ms
}
}
SFFT源碼 DownloadController
//開始下載
public async start() {
...
// 關(guān)閉當前已有的下載任務(wù)并清除相關(guān)信息
await this.cleanDirtyDownload();
...
// 生成下載任務(wù)并存儲任務(wù)信息和分片信息到數(shù)據(jù)庫與緩存
...
// 進行文件下載
this.executeDownload();
}
private executeDownload() {
...
// 啟用斷點續(xù)傳的情況下,定時更新各分片下載進度到數(shù)據(jù)庫
await DownloadInfoManager.getInstance().updateBlockInfos(this.downloadTaskMetadata);
...
}
由start方法可以發(fā)現(xiàn),調(diào)用start時,會清除之前下載的緩存,重新下載。
SFFT源碼 DownloadCacheManager
2.退出重新進入到下載頁面時,初始話下載任務(wù)后,判斷當前任務(wù)是否有下載緩存記錄。
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;
}
根據(jù)下載地址、文件名、存儲路徑判斷當前下載任務(wù)是否有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.如果當前下載任務(wù)有緩存記錄,繼續(xù)下載,需要調(diào)用resume方法
public async resume() {
//如果沒有開啟斷點續(xù)下 直接返回
//嘗試從緩存中匹配下載信息并寫入到downloadTask,不存在則直接退出,無法續(xù)下
//初始化下載進度
//校驗鏈接和參數(shù)
//回調(diào)進度
//開始下載
實現(xiàn)源碼
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, // 啟用的線程數(shù),concurrency為1~8的正整數(shù)
isBreakpointResume: true, // 是否啟用斷點續(xù)下,isBreakpointResume為true時啟用
maxRetries: 3, // 重試次數(shù)為3次
retryInterval: 2000, // 重試間隔為2000ms
}
}
export async function initSfft(downloadUrl:string,downloadListener: DownloadListener,concurrency?:number){
await DownloadManager.getInstance().init(context as common.UIAbilityContext);
// 根據(jù)配置創(chuàng)建下載任務(wù)
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()!
}