开源 Arkts 鸿蒙应用 开发(十七)通讯--http多文件下载

 文章的目的为了记录使用Arkts 进行Harmony app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

 相关链接:

开源 Arkts 鸿蒙应用 开发(一)工程文件分析-CSDN博客

开源 Arkts 鸿蒙应用 开发(二)封装库.har制作和应用-CSDN博客

开源 Arkts 鸿蒙应用 开发(三)Arkts的介绍-CSDN博客

开源 Arkts 鸿蒙应用 开发(四)布局和常用控件-CSDN博客

开源 Arkts 鸿蒙应用 开发(五)控件组成和复杂控件-CSDN博客

开源 Arkts 鸿蒙应用 开发(六)数据持久--文件和首选项存储-CSDN博客

开源 Arkts 鸿蒙应用 开发(七)数据持久--sqlite关系数据库-CSDN博客

开源 Arkts 鸿蒙应用 开发(八)多媒体--相册和相机-CSDN博客

开源 Arkts 鸿蒙应用 开发(九)通讯--tcp客户端-CSDN博客

开源 Arkts 鸿蒙应用 开发(十)通讯--Http-CSDN博客

开源 Arkts 鸿蒙应用 开发(十一)证书和包名修改-CSDN博客

开源 Arkts 鸿蒙应用 开发(十二)传感器的使用-CSDN博客

开源 Arkts 鸿蒙应用 开发(十三)音频--MP3播放_arkts avplayer播放音频 mp3-CSDN博客

开源 Arkts 鸿蒙应用 开发(十四)线程--任务池(taskpool)-CSDN博客

开源 Arkts 鸿蒙应用 开发(十五)自定义绘图控件--仪表盘-CSDN博客

开源 Arkts 鸿蒙应用 开发(十六)自定义绘图控件--波形图-CSDN博客

开源 Arkts 鸿蒙应用 开发(十七)通讯--http多文件下载-CSDN博客

开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器-CSDN博客

 推荐链接:

开源 java android app 开发(一)开发环境的搭建-CSDN博客

开源 java android app 开发(二)工程文件结构-CSDN博客

开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客

开源 java android app 开发(四)GUI界面重要组件-CSDN博客

开源 java android app 开发(五)文件和数据库存储-CSDN博客

开源 java android app 开发(六)多媒体使用-CSDN博客

开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客

开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客

开源 java android app 开发(九)后台之线程和服务-CSDN博客

开源 java android app 开发(十)广播机制-CSDN博客

开源 java android app 开发(十一)调试、发布-CSDN博客

开源 java android app 开发(十二)封库.aar-CSDN博客

推荐链接:

开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客

开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客

开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-CSDN博客

开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-CSDN博客

开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客

本章内容主要演示了一个多文件下载的HarmonyOS应用界面,主要包含三个部分:MultipleFilesDownload主组件、FileDownloadItem下载项组件和ProgressButton进度按钮组件。

1.工程结构

2.源码解析

3.演示效果

4.工程下载网址

一、工程结构,主要包括3个文件,Index.ets,FileDownloadItem.ets,ProgressButton.ets。

二、源码解析

2.1  Index.ets文件:管理多个文件的下载任务,提供"全部开始/暂停"功能,显示下载列表。

简单说明:

使用@Entry和@Component装饰器声明为入口组件,

维护一个下载URL数组downloadUrlArray

使用ForEach渲染多个FileDownloadItem组件

提供全局的开始/暂停所有下载的功能

跟踪下载计数和失败计数

以下为代码:



import { request } from '@kit.BasicServicesKit';
import { FileDownloadItem } from '../view/FileDownloadItem';


const NO_TASK: number = 0;

function downloadConfig(downloadUrl: string): request.agent.Config {
  const config: request.agent.Config = {
    action: request.agent.Action.DOWNLOAD,
    url: downloadUrl,
    overwrite: true,
    method: 'GET',
    saveas: './',
    mode: request.agent.Mode.BACKGROUND,
    gauge: true,
    retry: false
  };
  return config;
}

@Entry
@Component
struct MultipleFilesDownload {
  /**
   * enter the download urls
   */
  private downloadUrlArray: string[] = ["https://m.down.sandai.net/mobile/OfficialSite_MobileThunder1.apk"];
  @State downloadConfigArray: request.agent.Config[] = [];
  @State isStartAllDownload: boolean = false;
  @State downloadCount: number = 0;
  @State downloadFailCount: number = 0;

  aboutToAppear(): void {
    for (let i = 0; i < this.downloadUrlArray.length; i++) {
      const config: request.agent.Config = downloadConfig(this.downloadUrlArray[i]);
      this.downloadConfigArray.push(config);
    }
    this.downloadCount = this.downloadUrlArray.length;
  }

  build() {
    Column() {
      Row() {
        Text($r('app.string.multiple_files_download_transfer_list'))
          .fontWeight(FontWeight.Bold)
          .fontSize($r('app.integer.title_font_size'))
          .width('100%')
          .fontColor($r('app.color.text_color'))
      }
      .alignItems(VerticalAlign.Bottom)
      .width('91.1%')
      .height(112)

      Row() {
        Row() {
          Text($r('app.string.multiple_files_download_queue'))
            .fontSize($r('app.integer.multiple_files_download_text_font_size_fourteen'))
            .fontColor($r('sys.color.ohos_id_color_text_secondary'))
        }.width($r('app.string.multiple_files_download_row_width'))

        Row() {
          Text(this.isStartAllDownload && this.downloadCount > NO_TASK ? $r('app.string.pause_all') :
          $r('app.string.start_all'))
            .fontSize($r('app.integer.multiple_files_download_text_font_size_fourteen'))
            .fontWeight(500)
            .fontColor($r('sys.color.ohos_id_color_text_primary_activated'))
            .textAlign(TextAlign.End)
            .width($r('app.string.multiple_files_download_row_text_width'))
            .onClick(() => {
              if (this.downloadCount === NO_TASK) {
                this.getUIContext().showAlertDialog({
                  message: $r('app.string.multiple_files_download_completed'),
                  alignment: DialogAlignment.Center
                });
                return;
              }
              this.isStartAllDownload = !this.isStartAllDownload;
            })
        }.width($r('app.string.multiple_files_download_row_width'))
      }
      .margin({
        left: 16,
        right: 16,
        top: $r('app.integer.multiple_files_download_margin_top_twenty_eight'),
        bottom: $r('app.integer.margin_eight')
      })

      List() {
        ForEach(this.downloadConfigArray, (item: request.agent.Config) => {
          ListItem() {
            FileDownloadItem({
              downloadConfig: item,
              isStartAllDownload: this.isStartAllDownload,
              downloadCount: this.downloadCount,
              downloadFailCount: this.downloadFailCount
            })
          }
        }, (item: request.agent.Config) => JSON.stringify(item))
      }
      .width('100%')
      .height('100%')
    }
    .focusable(false)
  }
}

@Builder
export function getMultipleFilesDownload(): void {
  MultipleFilesDownload();
}

2.2  FileDownloadItem.ets组件:显示文件名、下载进度和状态,提供暂停/继续下载功能,处理下载过程中的各种回调。

简单说明:

completedCallback: 下载完成

failedCallback: 下载失败

pauseCallback: 暂停下载

resumeCallback: 继续下载

progressCallback: 下载进度更新

responseCallback: HTTP响应

使用ProgressButton组件显示下载进度和控制按钮

处理文件大小和下载进度的显示转换



import { common } from '@kit.AbilityKit';
import { request } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

import { ProgressButton } from './ProgressButton';

const uiContext: UIContext | undefined = AppStorage.get('uiContext');

const TAG = 'FileDownloadItem';
const BYTE_CONVERSION: number = 1024;
const context = uiContext!.getHostContext()! as common.UIAbilityContext;

function getFileNameFromUrl(url: string): string {
  const segments = url.split('/');
  let tmp = segments.pop() || '';
  if (tmp.indexOf('?') != -1) {
    return tmp.split('?')[0];
  }
  return tmp;
}

@Component
export struct FileDownloadItem {
  @State downloadConfig: request.agent.Config = { action: request.agent.Action.DOWNLOAD, url: '' };
  @State fileName: string = '';
  @State textState: string | Resource = "";
  @Link @Watch('onDownLoadUpdated') isStartAllDownload: boolean;
  private downloadTask: request.agent.Task | undefined = undefined;
  @Link downloadCount: number;
  @Link downloadFailCount: number;
  @State isShow: boolean = false;
  @State downloading: boolean = false;
  @State sFileSize: string | number = "-";
  @State nFileSize: number = 0;
  @State sCurrentDownloadSize: string = "-";
  @State @Watch('updateProgress') nCurrentDownloadSize: number = 0;
  @State progress: number = 0;
  @State isPaused: boolean = false;
  private completedCallback = (progress: request.agent.Progress) => {
    this.textState = $r("app.string.download_completed");
    if (this.sFileSize === -1) {
      this.sFileSize = this.sCurrentDownloadSize
      this.nCurrentDownloadSize = 1;
    }
    this.downloadCount--;
  }
  private failedCallback = (progress: request.agent.Progress) => {
    this.textState = $r("app.string.download_fail");
    this.downloadFailCount++;
    if (this.downloadFailCount === this.downloadCount) {
      this.isStartAllDownload = false;
    }
    if (this.downloadTask) {
      request.agent.show(this.downloadTask.tid, (err: Error, taskInfo: request.agent.TaskInfo) => {
        if (err) {
          hilog.error(0x0000, TAG, 'agent show error:', err);
          return;
        }
      });
    }
  }
  private pauseCallback = (progress: request.agent.Progress) => {
    this.isPaused = true;
    this.downloading = false;
    this.textState = $r("app.string.paused");
  }
  private resumeCallback = (progress: request.agent.Progress) => {
    this.isPaused = false;
    this.textState = $r("app.string.downloading");
    this.downloading = true;
  }
  private progressCallback = (progress: request.agent.Progress) => {
    this.textState = $r("app.string.downloading");
    this.downloading = true;
    this.isShow = true;
    if (this.downloadTask) {
      if (this.sFileSize === '-') {
        if (progress.sizes[0] === -1) {
          this.sFileSize = -1;
          this.nCurrentDownloadSize = 0;
          this.nFileSize = 1;
        } else {
          this.nFileSize = progress.sizes[0];
          this.sFileSize = (progress.sizes[0] / BYTE_CONVERSION).toFixed() + 'kb';
          this.nCurrentDownloadSize = progress.processed;
        }
      } else if (this.sFileSize === -1) {
        hilog.info(0x0000, TAG, 'file size unknown');
      } else {
        this.nCurrentDownloadSize = progress.processed;
      }
      this.sCurrentDownloadSize = (progress.processed / BYTE_CONVERSION).toFixed() + 'kb';
    }
  }
  private responseCallback = (response: request.agent.HttpResponse) => {
    hilog.info(0x0000, TAG, 'response:' + response.statusCode);
  };

  updateProgress() {
    setTimeout(() => {
      if (this.textState == $r("app.string.paused")) {
        this.isPaused = true;
        return;
      }
      let tmp = this.nCurrentDownloadSize / this.nFileSize * 100;
      if (tmp <= this.progress) {
        return;
      }
      this.progress = tmp;
    }, 10)
  }

  aboutToAppear(): void {
    this.fileName = getFileNameFromUrl(this.downloadConfig.url);
  }

  onDownLoadUpdated(): void {
    if (this.isStartAllDownload) {
      if (this.textState === $r("app.string.download_fail")) {
        this.downloadTask = undefined;
        this.isShow = false;
        this.textState = "";
      }
      this.startDownload();
    } else {
      if (this.downloadFailCount > 0 && this.downloadFailCount === this.downloadCount) {
        this.downloadFailCount = 0;
      } else {
        this.pauseDownload();
      }
    }
  }

  startDownload(): void {
    if (this.downloadTask === undefined) {
      request.agent.create(context, this.downloadConfig).then((task: request.agent.Task) => {
        task.on('completed', this.completedCallback);
        task.on('failed', this.failedCallback);
        task.on('pause', this.pauseCallback);
        task.on('resume', this.resumeCallback);
        task.on('progress', this.progressCallback);
        task.on('response', this.responseCallback);

        task.start().then(() => {
          this.downloadTask = task;
        }).catch((err: Error) => {
          hilog.error(0x0000, TAG, 'task start error:', err);
        })
      }).catch((err: Error) => {
        hilog.error(0x0000, TAG, 'create error:', err);
      });
    } else {
      this.resumeDownload();
    }
  }

  pauseOrResumeDownload(): void {
    if (this.downloadTask) {
      request.agent.show(this.downloadTask.tid, (err: Error, taskInfo: request.agent.TaskInfo) => {
        if (err) {
          hilog.error(0x0000, TAG, 'agent show error:', err);
          return;
        }
        if (taskInfo.progress.state === request.agent.State.PAUSED) {
          this.resumeDownload();
        } else {
          this.pauseDownload();
        }
      });
    }
  }

  pauseDownload(): void {
    if (this.downloadTask) {
      request.agent.show(this.downloadTask.tid, (err: Error, taskInfo: request.agent.TaskInfo) => {
        if (err) {
          hilog.error(0x0000, TAG, 'agent show error:', err);
          return;
        }
        if (this.downloadTask && (taskInfo.progress.state === request.agent.State.WAITING || taskInfo.progress.state
          === request.agent.State.RUNNING || taskInfo.progress.state === request.agent.State.RETRYING)) {
          this.downloadTask.pause().then(() => {
          }).catch((err: Error) => {
            hilog.error(0x0000, TAG, 'task pause error:', err);
          });
        }
      });
    }
  }

  resumeDownload(): void {
    if (this.downloadTask) {
      request.agent.show(this.downloadTask.tid, (err: Error, taskInfo: request.agent.TaskInfo) => {
        if (err) {
          hilog.error(0x0000, TAG, 'agent show error:', err);
          return;
        }
        if (this.downloadTask && taskInfo.progress.state === request.agent.State.PAUSED) {
          this.downloadTask.resume().then(() => {
          }).catch((err: Error) => {
            hilog.error(0x0000, TAG, 'task resume error:', err);
          });
        }
      });
    }
  }

  build() {
    RelativeContainer() {
      Image($r('app.media.multiple_files_download_file'))
        .height($r("app.integer.multiple_files_download_image_size"))
        .width($r("app.integer.multiple_files_download_image_size"))
        .id('fileImage')
        .alignRules({
          center: { anchor: '__container__', align: VerticalAlign.Center }
        })

      Text(this.fileName)
        .fontSize($r("app.integer.multiple_files_download_text_font_size"))
        .padding({ left: $r("app.integer.multiple_files_download_padding") })
        .alignRules({
          left: { anchor: 'fileImage', align: HorizontalAlign.End },
          top: { anchor: 'fileImage', align: VerticalAlign.Top },
        })
        .fontWeight(FontWeight.Medium)
        .margin({ top: $r('app.integer.item_name_top_margin') })
        .id('fileName')

      ProgressButton({
        paused: this.isPaused,
        progress: this.progress,
        progressButtonWidth: $r('app.integer.progress_btn_width'),
        content: this.textState,
        enable: true,
        clickCallback: () => {
          this.pauseOrResumeDownload();
        }
      })
        .visibility(this.isShow ? Visibility.Visible : Visibility.Hidden)
        .alignRules({
          right: { anchor: '__container__', align: HorizontalAlign.End },
          center: { anchor: '__container__', align: VerticalAlign.Center }
        })

      Text(this.sCurrentDownloadSize + "/" + this.sFileSize)
        .fontSize($r('app.integer.multiple_files_download_text_font_size_fourteen'))
        .width($r('app.string.multiple_files_download_text_width'))
        .fontColor($r('app.color.multiple_files_download_text_font_color'))
        .margin({ top: $r("app.integer.multiple_files_download_margin_top") })
        .padding({ left: $r("app.integer.multiple_files_download_padding") })
        .alignRules({
          top: { anchor: 'fileName', align: VerticalAlign.Bottom },
          left: { anchor: 'fileImage', align: HorizontalAlign.End }
        })
        .id('downloadVal')

    }
    .height($r('app.integer.item_height'))
    .margin({ left: 16, right: 16 })
    .padding({ left: 12, right: 12 })
  }
}

2.3  ProgressButton.ets,这个自定义的组建实现了:显示下载进度百分比,根据状态显示不同文本(下载中/暂停/完成),提供点击回调功能。

简单说明:

使用HarmonyOS的Progress组件显示进度条

根据进度值自动更新显示文本

支持启用/禁用状态

提供圆角胶囊样式



const EMPTY_STRING: string = '';
const MAX_PROGRESS: number = 100;
const MAX_PERCENTAGE: string = '100%';
const MIN_PERCENTAGE: string = '0%';
const TEXT_OPACITY: number = 0.4;
const BUTTON_NORMAL_WIDTH: number = 44;
const BUTTON_NORMAL_HEIGHT: number = 28;
const BUTTON_BORDER_RADIUS: number = 14;
const TEXT_ENABLE: number = 1.0;
const MIN_WIDTH: Length = 44;
const PADDING_TEXT: Length = 8;

const PROGRESS_BUTTON_PROGRESS_KEY = 'progress_button_progress_key';
const PROGRESS_BUTTON_PRIMARY_FONT_KEY = 'progress_button_primary_font_key';
const PROGRESS_BUTTON_CONTAINER_BACKGROUND_COLOR_KEY = 'progress_button_container_background_color_key';
const PROGRESS_BUTTON_EMPHASIZE_SECONDARY_BUTTON_KEY = 'progress_button_emphasize_secondary_button_key';

@Component
export struct ProgressButton {
  @Prop @Watch('updateText') paused: boolean = false;
  @Prop @Watch('getProgressContext') progress: number;
  @State textProgress: string = EMPTY_STRING;
  @Prop content: string | Resource = EMPTY_STRING;
  @State isLoading: boolean = false;
  progressButtonWidth?: Length = BUTTON_NORMAL_WIDTH;
  clickCallback: () => void = () => {
  };
  @Prop enable: boolean = true;
  @State progressColor: ResourceColor = '#330A59F7';
  @State containerBorderColor: ResourceColor = '#330A59F7';
  @State containerBackgroundColor: ResourceColor = $r('sys.color.ohos_id_color_foreground_contrary');

  private getButtonProgress(): number {
    if (this.progress < 0) {
      return 0;
    } else if (this.progress > MAX_PROGRESS) {
      return MAX_PROGRESS;
    }
    return this.progress;
  }

  updateText() {
    if (this.paused) {
      setTimeout(() => {
        this.isLoading = !this.paused;
      }, 10);
    } else {
      this.isLoading = !this.paused;
    }
  }

  private getProgressContext() {
    if (this.progress < 0) {
      this.isLoading = false;
      this.textProgress = MIN_PERCENTAGE;
    } else if (this.progress >= MAX_PROGRESS) {
      this.isLoading = false;
      this.textProgress = MAX_PERCENTAGE;
    } else {
      this.isLoading = true;
      this.textProgress = Math.floor(this.progress / MAX_PROGRESS * MAX_PROGRESS).toString() + "%";
    }
  }

  build() {
    Button() {
      Stack() {
        Progress({
          value: this.getButtonProgress(), total: MAX_PROGRESS,
          style: ProgressStyle.Capsule
        })
          .height(BUTTON_NORMAL_HEIGHT)
          .borderRadius(BUTTON_BORDER_RADIUS)
          .width('100%')
          .hoverEffect(HoverEffect.None)
          .clip(false)
          .enabled(this.enable)
          .key(PROGRESS_BUTTON_PROGRESS_KEY)
          .color(this.progressColor)
        Text(this.isLoading ? this.textProgress : this.content)
          .fontSize($r('sys.float.ohos_id_text_size_button3'))
          .fontWeight(FontWeight.Medium)
          .key(PROGRESS_BUTTON_PRIMARY_FONT_KEY)
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .padding({ left: PADDING_TEXT, right: PADDING_TEXT })
          .opacity(this.enable ? TEXT_ENABLE : TEXT_OPACITY)

        Row()
          .key(PROGRESS_BUTTON_CONTAINER_BACKGROUND_COLOR_KEY)
          .backgroundColor(Color.Transparent)
          .border({ width: 1, color: this.containerBorderColor })
          .height(BUTTON_NORMAL_HEIGHT)
          .borderRadius(BUTTON_BORDER_RADIUS)
          .width('100%')
      }
    }
    .borderRadius(BUTTON_BORDER_RADIUS)
    .clip(false)
    .hoverEffect(HoverEffect.None)
    .key(PROGRESS_BUTTON_EMPHASIZE_SECONDARY_BUTTON_KEY)
    .backgroundColor(this.containerBackgroundColor)
    .constraintSize({ minWidth: MIN_WIDTH })
    .width((!this.progressButtonWidth || this.progressButtonWidth < BUTTON_NORMAL_WIDTH) ?
      BUTTON_NORMAL_WIDTH : this.progressButtonWidth)
    .stateEffect(this.enable)
    .onClick(() => {
      if (!this.enable) {
        return;
      }
      if (this.progress < MAX_PROGRESS) {
        this.isLoading = !this.isLoading;
      }
      this.clickCallback && this.clickCallback();
    })
  }
}

2.4  module.json5权限需要添加ohos.permission.INTERNET

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone"
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:reason_internet",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "always"
        }
      },
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:layered_image",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:startIcon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
      }
    ]
  }
}

三、演示效果,下载地址数组里只用写了1个,所以只有1个文件,需要的可以自行添加。

四、工程下载网址:https://download.csdn.net/download/ajassi2000/91685765?spm=1011.2124.3001.6210

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值