【入门到精通】鸿蒙next开发:大文件高速并发传输解决方案(下)

往期鸿蒙5.0全套实战文章必看:(文中附带全栈鸿蒙5.0学习资料)


本文由上下两部分构成,若您想快速浏览上半部分的精彩内容,可通过 大文件高速并发传输解决方案(上)直接访问。

3. 实践案例

案例简介

以大文件下载场景为例,本案例(Sample)基于SFFT三方库和ArkUI,实现了一个体现SFFT多线程并发下载、断点续下等功能特性的下载页面,案例中提供了关键的开发步骤和代码实现,旨在为开发者开发大文件下载场景提供案例指导。

版本约束如下:

支持的OS语言

ArkTS

支持的设备

手机/折叠屏/平板/PC

支持的OS设备

单框架

支持的API

>=12

Sample效果展示

11.gif

实现说明

布局说明

页面基于Navigation组件实现,菜单栏提供全部下载/暂停、全部删除和上传/下载视图切换功能。内容区为DownloadView(模式切换后为UploadView)。View由基本UI元素和DownloadItem元素项组成,DownloadItem中集成了网络库的主要功能,包括单个文件的下载、暂停和删除。效果图如下:

12.png

网络库的接口调用是本Sample的核心和重点,下面将围绕super-fast-file-trans三方库的使用的关键步骤及其细节展开介绍:

使用SFFT三方库实现文件下载功能:

(1)导入SFFT :

import { DownloadConfig, DownloadListener, DownloadManager } from "@hadss/super-fast-file-trans"; 
import { DownloadTask } from "@hadss/super-fast-file-trans"; 
import { DownloadProgressInfo } from "@hadss/super-fast-file-trans";

(2)对下载项DownloadItem进行封装,该类使用SFFT库的下载能力对单个下载项进行处理,同时作为组件供UI视图进行排版布局。

13.png

import { DownloadConfig, DownloadListener, DownloadManager } from "@hadss/super-fast-file-trans"; 
import { DownloadTask } from "@hadss/super-fast-file-trans"; 
import { DownloadProgressInfo } from "@hadss/super-fast-file-trans"; 
import { FileItem } from "../model/FileItem"; 
import MemoryTool from "../util/MemoryTool"; 
import { BusinessError } from "@kit.BasicServicesKit"; 
 
@Component 
export struct DownloadItem { 
  @State itemKey: number = 0; // 下载项key,所属列表内唯一标识属性 
  @State fileName: string = '文件'; // 文件名 
  @State url: string = ''; // 下载地址url 
  @State concurrency: number = 1; 
  @State isPaused: boolean = false; // 是否暂停过 
  @State isDownloading: boolean = false; 
  @State transferredSize: number = 0; 
  @State totalSize: number = 0; 
  @State speed: number = 0; 
  @State currentRate: number = 0; 
  @Link downloadTaskList: Array<FileItem>; // 当前item所属的下载列表 
  @Link downloadingList?: Set<number>; 
  @StorageLink('isDownloadAll') @Watch('onDownloadAll') isDownloadAll: boolean = false; // 接收外部全部下载指令 
  @StorageLink('isPauseAll') @Watch('onPauseAll') isPauseAll: boolean = false; // 接收外部全部下载指令 
  @State downloadListener: DownloadListener = {}; 
  private downloadInstance: DownloadTask | undefined; 
  private downloadConfig: DownloadConfig = { url: this.url, fileName: this.fileName }; 
  private memoryTool: MemoryTool = new MemoryTool(); 
 
  async aboutToAppear() { 
    this.downloadConfig = { url: this.url, fileName: this.fileName, concurrency: this.concurrency }; 
    let downloadListener: DownloadListener = { 
      onStart: async (trialConnectionResponse: Record<string, string | string[] | undefined>) => { 
        this.isDownloading = true; 
        this.downloadingList?.add(this.itemKey); 
      }, 
      onResume: async (trialConnectionResponse: Record<string, string | string[] | undefined>, 
        downloadProgress: DownloadProgressInfo) => { 
        this.isDownloading = true; 
        this.downloadingList?.add(this.itemKey); 
      }, 
      onPause: (downloadProgress: DownloadProgressInfo) => { 
        this.isDownloading = false; 
        this.speed = 0; 
        this.downloadingList?.delete(this.itemKey); 
      }, 
      onSuccess: (filePath: string) => { 
        this.isDownloading = false; 
        this.downloadingList?.delete(this.itemKey); 
      }, 
      onFail: (err: BusinessError) => { 
        this.isDownloading = false; 
        this.downloadingList?.delete(this.itemKey); 
      }, 
      onProgressUpdate: (downloadProgress: DownloadProgressInfo) => { 
        this.transferredSize = downloadProgress.transferredSize; 
        this.totalSize = downloadProgress.totalSize; 
        this.speed = downloadProgress.speed; 
        this.currentRate = this.transferredSize / this.totalSize * 100; 
      } 
    } 
    this.downloadInstance = 
      DownloadManager.getInstance().createDownloadTask(this.downloadConfig, downloadListener); 
    await DownloadManager.getInstance().init(getContext()); 
    this.downloadInstance?.getProgress().then((progress: DownloadProgressInfo) => { 
      this.transferredSize = progress.transferredSize; 
      this.totalSize = progress.totalSize; 
      this.currentRate = progress.transferredSize / progress.totalSize * 100; 
      if (this.transferredSize === 0) { 
        this.isPaused = false; 
      } else { 
        this.isPaused = true; 
      } 
    }); 
  } 
 
  onDownloadAll() { 
    if (this.isDownloadAll && !this.isDownloading) { 
      this.download(); 
    } 
  } 
 
  async onPauseAll() { 
    if (this.isPauseAll && this.isDownloading) { 
      await this.pause(); 
      this.isPaused = true; 
    } 
  } 
 
  async download() { 
    if (!this.isPaused || this.transferredSize == this.totalSize) { 
      await this.start(); 
    } else { 
      await this.resume(); 
    } 
  } 
 
  async start() { 
    await this.downloadInstance?.start(); 
  } 
 
  async resume() { 
    await this.downloadInstance?.resume(); 
  } 
 
  async pause() { 
    await this.downloadInstance?.pause(); 
  } 
 
  async cancel() { 
    this.isDownloading = false; 
    await this.downloadInstance?.cancel(); 
  } 
 
  build() { 
    Column() { 
      Row() { 
        Column() { 
          Image($r('app.media.logo')) 
            .width(46) 
            .height(46) 
        } 
 
        Column() { 
          Row() { 
            Text(this.fileName) 
              .fontWeight(FontWeight.Bold) 
              .fontColor(Color.Black) 
              .fontSize(18) 
              .opacity(0.9) 
          } 
 
          Row() { 
            Text(this.memoryTool.convert(this.transferredSize) + '/' + 
            this.memoryTool.convert(this.totalSize) + ' - ' + 
            this.memoryTool.convert(this.speed) + '/s') 
              .fontSize(14) 
              .fontColor(Color.Black) 
              .opacity(0.6) 
          } 
 
        } 
        .alignItems(HorizontalAlign.Start) 
        .justifyContent(FlexAlign.SpaceAround) 
        .width(164) 
        .height(46) 
 
        Column() { 
          Row() { 
            Image(this.isDownloading ? $r('app.media.pause') : $r('app.media.play')) 
              .width(24) 
              .height(24) 
              .onClick(async () => { 
                if (this.isDownloading) { 
                  this.isPaused = true; 
                  await this.pause(); 
                } else { 
                  await this.download(); 
                } 
              }) 
            Image($r('app.media.cancel')) 
              .width(24) 
              .height(24) 
              .onClick(() => { 
                this.cancel(); 
                this.downloadTaskList = this.downloadTaskList.filter((item: FileItem) => { 
                  return item.fileKey != this.itemKey; 
                }); 
              }) 
          } 
          .justifyContent(FlexAlign.SpaceBetween) 
          .width(64) 
          .height(24) 
        } 
        .height(46) 
        .justifyContent(FlexAlign.Start) 
      } 
      .justifyContent(FlexAlign.SpaceBetween) 
      .width(307) 
      .height(62) 
 
      Row() { 
        Progress({ value: this.currentRate, total: 100, type: ProgressType.Linear }) 
          .width(307) 
          .height(24) 
      } 
    } 
    .width(328) 
    .height(86) 
    .justifyContent(FlexAlign.Center) 
  } 
}

(3)以Sample提供的效果为例,开发者使用步骤(2)封装的DownloadItem组件进行下载视图DownloadView的封装。在本步骤开发者需填入下载所需的相关参数,初始化DownloadConfig传入DownloadItem中。

14.png

import { DownloadItem } from "../components/DownloadItem"; 
import { FileItem } from "../model/FileItem"; 
 
@Component 
export struct DownloadView { 
  @State regularList: Array<FileItem> = 
    [new FileItem(0, '影视剧.mp4', 
      'https://vd3.bdstatic.com/mda-pid1q4efihenk6su/576p/h264/1694654179925144900/mda-pid1q4efihenk6su.mp4', 1), 
      new FileItem(1, '外国历史.mp4', 
        'https://vd3.bdstatic.com/mda-kkjr1tjxv6w6251c/hd/mda-kkjr1tjxv6w6251c.mp4', 1)]; 
  @State multiList: Array<FileItem> = [new FileItem(2, '流行音乐.mp4', 
    'https://vd3.bdstatic.com/mda-qmfgk7auzqy1ph62/576p/h264/1734369330734616164/mda-qmfgk7auzqy1ph62.mp4', 4), 
    new FileItem(3, '中国.mp4', 
      'https://vd3.bdstatic.com/mda-nfm9j7syzbu70z5w/576p/cae_h264/1655914071297652987/mda-nfm9j7syzbu70z5w.mp4', 4)]; 
  @State @Watch('onDownloadSum') downloadingList: Set<number> = new Set(); 
  @StorageLink('isDeleteAll') @Watch('onDeleteAll') allDelete: boolean = false; 
  @Link isAllDownload: boolean; 
 
  onDownloadSum() { 
    if (this.downloadingList.size == this.regularList.length + this.multiList.length) { 
      this.isAllDownload = true; 
      AppStorage.setOrCreate('isPauseAll', false); 
    } 
    if (this.downloadingList.size == 0) { 
      this.isAllDownload = false; 
      AppStorage.setOrCreate('isDownloadAll', false); 
    } 
  } 
 
  onDeleteAll() { 
    AppStorage.setOrCreate('allDelete', false); 
    this.regularList = []; 
    this.multiList = []; 
  } 
 
  build() { 
    Column() { 
      Row() { 
        Text($r('app.string.regularDownload')) 
          .fontSize(14) 
          .fontColor(Color.Black) 
          .opacity(0.6) 
      } 
      .width(328) 
      .height(20) 
      .justifyContent(FlexAlign.Start) 
 
      Column() { 
        ForEach(this.regularList, (item: FileItem, index: number) => { 
          DownloadItem({ 
            itemKey: item.fileKey, 
            fileName: item.fileName, 
            url: item.url, 
            downloadTaskList: this.regularList, 
            downloadingList: this.downloadingList 
          }) 
        }, (item: string, index: number) => item); 
      } 
      .margin({ top: 8 }) 
 
      Row() { 
        Text($r('app.string.multiDownload')) 
          .fontSize(14) 
          .fontColor(Color.Black) 
          .opacity(0.6) 
      } 
      .width(328) 
      .height(20) 
      .margin({ top: 212 - this.regularList.length * 86 }) 
      .justifyContent(FlexAlign.Start) 
 
      Column() { 
        ForEach(this.multiList, (item: FileItem, index: number) => { 
          DownloadItem({ 
            itemKey: item.fileKey, 
            fileName: item.fileName, 
            url: item.url, 
            concurrency: item.concurrency, 
            downloadTaskList: this.multiList, 
            downloadingList: this.downloadingList 
          }) 
        }, (item: string, index: number) => item); 
      } 
      .margin({ top: 8 }) 
    } 
    .height('100%') 
    .width('100%') 
    .justifyContent(FlexAlign.Start) 
  } 
}

(4)开发者在Index应用首页,根据自身应用的需要使用DownloadView进行页面侧的UX布局实现

网络库Sample提供的界面对应的UX效果与代码如下:

import { DownloadView } from '../view/DownloadView'; 
import { UploadView } from '../view/UploadView'; 
import { LengthMetrics } from '@kit.ArkUI'; 
import { CustomMenuItem } from '../components/CustomMenuItem'; 
import { DownloadManager } from '@hadss/super-fast-file-trans'; 
 
@Entry 
@Component 
struct Index { 
  @State isDownloadStatus: boolean = true; 
  @State isAllDownload: boolean = false; 
  @State isClickable: boolean = true; 
 
  @Builder 
  MyMenu() { 
    Menu() { 
      MenuItem({ content: '下载文件' }) 
        .width(103) 
        .onClick(() => { 
          this.isDownloadStatus = true; 
          AppStorage.setOrCreate('isDeleteAll', false); 
        }) 
      MenuItem({ content: '上传文件' }) 
        .width(103) 
        .onClick(() => { 
          this.isDownloadStatus = false; 
          AppStorage.setOrCreate('isDeleteAll', false); 
        }) 
    } 
    .menuItemDivider({ 
      strokeWidth: new LengthMetrics(0.5), 
      color: '#E6000000', 
      startMargin: new LengthMetrics(0), 
      endMargin: new LengthMetrics(0) 
    }) 
  } 
 
  @Builder 
  DownloadMenu() { 
    Row() { 
      if (!this.isAllDownload) { 
        CustomMenuItem({ image: $r('app.media.download') }) 
          .onClick(() => { 
            AppStorage.setOrCreate('isDownloadAll', true); 
            AppStorage.setOrCreate('isPauseAll', false); 
          }) 
      } else { 
        CustomMenuItem({ image: $r('app.media.pause') }) 
          .onClick(() => { 
            AppStorage.setOrCreate('isPauseAll', true); 
            AppStorage.setOrCreate('isDownloadAll', false); 
          }) 
      } 
      CustomMenuItem({ image: $r('app.media.delete') }) 
        .onClick(() => { 
          AppStorage.setOrCreate('isDeleteAll', true); 
          this.isAllDownload = false; 
          AppStorage.setOrCreate('isDownloadAll', false); 
          DownloadManager.getInstance().cleanAll(getContext()); 
        }) 
      CustomMenuItem({ image: $r('app.media.switch') }) 
        .bindMenu(this.MyMenu()) 
    } 
    .height(56) 
    .margin({ right: 16 }) 
    .alignItems(VerticalAlign.Center) 
  } 
 
  @Builder 
  UploadMenu() { 
    Row() { 
      CustomMenuItem({ image: $r('app.media.upload') }) 
        .onClick(() => { 
          AppStorage.setOrCreate('isUploadAll', true); 
        }) 
      CustomMenuItem({ image: $r('app.media.delete') }) 
        .onClick(() => { 
          AppStorage.setOrCreate('isDeleteAll', true); 
        }) 
      CustomMenuItem({ image: $r('app.media.switch') }) 
        .bindMenu(this.MyMenu()) 
    } 
    .height(56) 
    .margin({ right: 16 }) 
    .alignItems(VerticalAlign.Center) 
  } 
 
  build() { 
    Column() { 
      Navigation() { 
        Column() { 
          if (this.isDownloadStatus) { 
            DownloadView({ isAllDownload: this.isAllDownload }) 
              .width(474) 
              .height(328) 
              .margin({ top: 50 }) 
          } else { 
            UploadView() 
              .width(474) 
              .height(328) 
              .margin({ top: 50 }) 
          } 
        } 
        .width('100%') 
        .justifyContent(FlexAlign.Start) 
      } 
      .menus(this.isDownloadStatus ? this.DownloadMenu() : this.UploadMenu()) 
      .title($r('app.string.sampleTitle')) 
      .width('100%') 
      .height('100%') 
    } 
  } 
}

Sample代码

由于上传场景的代码实现与下载场景的代码实现相似,本文不再赘述。

4. 总结与回顾

本文深入探讨了实现大文件高速传输的关键技术,介绍了super_fast_file_trans三方库(SFFT)提供的多线程并发下载、断点续下、分片上传、断点续传、自动重试等多个功能特性,旨在帮助开发者快速实现大文件传输场景,有效提高鸿蒙开发中文件数据传输的可靠性和高效性,为了使读者能更加容易理解上述特性,本文给出了上述特性的实现原理、效果展示和代码实现。除此之外,本文基于SFFT给出了一个文件下载场景的Sample实现,包括接口使用,界面实现,源码地址等,旨在协助开发者使用SFFT三方库快速又高性能地实现大文件数据传输场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值