网络-上传和下载(ArkTS)

介绍

本示例使用@ohos.request接口创建上传和下载任务,实现上传、下载功能,hfs作为服务器,实现了文件的上传和下载和任务的查询功能。

效果预览

1

使用说明

1.本示例功能需要先配置服务器环境后使用,具体配置见[上传下载服务配置]。

2.首页展示上传和下载两个入口组件,点击进入对应的页面,如果要使用后台下载任务,请开启后台任务开关。

3.上传页面(请先在图库中确定已开启图库权限):

​ 点击**+**,从相册选择拉起图库选择照片,图片选择页面支持拍照,选择照片后点击发表进行上传。

​ 在首页中打开后台任务开关后,上传页面开启的是后台上传任务,后台任务在应用退出到后台时可以在通知栏看到任务状态。

4.下载页面:

​ 点击文件列表选择要下载的文件后,点击下载选择指定路径后开始下载。

​ 点击查看下载文件进入下载文件页面,点击文件夹查看文件夹内的文件。

​ 在首页中打开后台任务开关后,下载页面开启的是后台下载任务,后台任务在应用退出到后台时可以在通知栏看到任务状态。

​ 前台下载时只支持单文件下载,后台下载时支持选择多个文件下载。

具体实现

  • 该示例分为两个模块:

    • 上传模块

      • 使用@ohos.request中接口agent.create创建上传任务,调用@ohos.request中的Task相关接口实现上传任务的创建、取消、进度加载,失败的任务会调用查询接口获取失败原因并打印在日志中,支持多个文件上传。
  • 源码参考:[RequestUpload.ets]

/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { request } from '@kit.BasicServicesKit';
import { urlUtils } from '../utils/UrlUtils';
import { logger } from '../utils/Logger';
import { MediaUtils } from '../utils/MediaUtils';
import { BackgroundTaskState, UPLOAD_TOKEN, TOAST_BOTTOM, TASK_MAX } from '../utils/Constants';

const TAG: string = 'RequestUpload';
const HEADER: Record<string, string> = { 'Content-Type': 'multipart/form-data' };

class Upload {
  private mediaUtils: MediaUtils = new MediaUtils();
  private config: request.agent.Config = {
    action: request.agent.Action.UPLOAD,
    headers: HEADER,
    url: '',
    mode: request.agent.Mode.FOREGROUND,
    method: 'POST',
    title: 'upload',
    network: request.agent.Network.ANY,
    data: [],
    token: UPLOAD_TOKEN
  }
  private context: common.UIAbilityContext | undefined = undefined;
  private uploadTask: request.agent.Task | undefined = undefined;
  private backgroundTask: request.agent.Task | undefined = undefined;
  private waitList: Array<string> = [];
  progressCallback: Function | undefined = undefined;
  completedCallback: Function | undefined = undefined;
  failedCallback: Function | undefined = undefined;

  constructor() {
    setInterval(() => {
      this.flushBackgroundTask()
    }, 2000);
  }

  async uploadFilesBackground(fileUris: Array<string>): Promise<void> {
    logger.info(TAG, `uploadFiles begin, ${JSON.stringify(fileUris)}`);
    this.context = getContext(this) as common.UIAbilityContext;
    if (fileUris.length === 0) {
      return;
    }
    fileUris.forEach((item: string) => {
      this.waitList.push(item);
    });
  }

  async flushBackgroundTask() {
    let tasks = await request.agent.search({
      state: request.agent.State.RUNNING
    });
    let state = AppStorage.get<number>('backTaskState');
    if (state === BackgroundTaskState.RUNNING) {
      if (tasks.length < TASK_MAX && this.waitList.length > 0) {
        this.createBackgroundTask(this.waitList);
        this.waitList = [];
      } else {
        if (this.backgroundTask === undefined || tasks.indexOf(this.backgroundTask.tid) === -1) {
          AppStorage.setOrCreate('backTaskState', BackgroundTaskState.NONE);
          this.backgroundTask = undefined;
        }
      }
    }
  }

  async createBackgroundTask(fileUris: Array<string>) {
    if (this.context === undefined) {
      return;
    }
    this.config.url = await urlUtils.getUrl(this.context);
    this.config.data = await this.getFilesAndData(this.context.cacheDir, fileUris);
    this.config.mode = request.agent.Mode.BACKGROUND;
    try {
      this.backgroundTask = await request.agent.create(this.context, this.config);
      await this.backgroundTask.start();
      let state = AppStorage.get<number>('backTaskState');
      if (state === BackgroundTaskState.PAUSE) {
        await this.backgroundTask.pause();
      }
      logger.info(TAG, `createBackgroundTask success`);
    } catch (err) {
      logger.error(TAG, `task  err, err  = ${JSON.stringify(err)}`);
    }
  }

  async uploadFiles(fileUris: Array<string>, callback: (progress: number, isSucceed: boolean) => void): Promise<void> {
    logger.info(TAG, `uploadFiles begin, ${JSON.stringify(fileUris)}`);
    if (fileUris.length === 0) {
      return;
    }
    // 查询到存在正在执行的上传任务,提示并返回
    let tasks = await request.agent.search({
      state: request.agent.State.RUNNING,
      action: request.agent.Action.UPLOAD,
      mode: request.agent.Mode.FOREGROUND
    });
    if (tasks.length > 0) {
      promptAction.showToast({ message: $r('app.string.have_upload_task_tips'), bottom: TOAST_BOTTOM });
      return;
    }
    let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
    this.config.data = await this.getFilesAndData(context.cacheDir, fileUris);
    this.config.url = await urlUtils.getUrl(context);
    this.config.mode = request.agent.Mode.FOREGROUND;
    try {
      this.uploadTask = await request.agent.create(context, this.config);
      this.uploadTask.on('progress', this.progressCallback = (progress: request.agent.Progress) => {
        logger.info(TAG, `progress,  progress = ${progress.processed} ${progress.state}`);
        let processed = Number(progress.processed.toString()).valueOf();
        let size = progress.sizes[0];
        let process: number = Math.floor(processed / size * 100);
        if (process < 100) {
          callback(process, false);
        }
      });
      this.uploadTask.on('completed', this.completedCallback = (progress: request.agent.Progress) => {
        logger.info(TAG, `complete,  progress = ${progress.processed} ${progress.state}`);
        callback(100, true);
        this.cancelTask();
      });
      this.uploadTask.on('failed', this.failedCallback = async (progress: request.agent.Progress) => {
        if (this.uploadTask) {
          let taskInfo = await request.agent.touch(this.uploadTask.tid, UPLOAD_TOKEN);
          logger.info(TAG, `fail,  resean = ${taskInfo.reason}, faults = ${JSON.stringify(taskInfo.faults)}`);
        }
        callback(100, false);
        this.cancelTask();
      });
      await this.uploadTask.start();
    } catch (err) {
      logger.error(TAG, `task  err, err  = ${JSON.stringify(err)}`);
      callback(100, false);
    }
  }

  async cancelTask() {
    if (this.uploadTask === undefined) {
      return;
    }
    try {
      this.uploadTask.off('progress');
      this.progressCallback = undefined;
      this.uploadTask.off('failed');
      this.failedCallback = undefined
      this.uploadTask.off('completed');
      this.completedCallback = undefined
      await this.uploadTask.stop();
      await request.agent.remove(this.uploadTask.tid);
    } catch (err) {
      logger.info(TAG, `deleteTask fail,err= ${JSON.stringify(err)}`);
    }
    this.uploadTask = undefined;
  }

  async pauseOrResume() {
    let state = AppStorage.get<number>('backTaskState');
    if (state === BackgroundTaskState.RUNNING) {
      await this.pause();
      AppStorage.setOrCreate('backTaskState', BackgroundTaskState.PAUSE);
    } else if (state === BackgroundTaskState.PAUSE) {
      await this.resume();
      AppStorage.setOrCreate('backTaskState', BackgroundTaskState.RUNNING);
    } else {
      logger.info(TAG, 'this task state is error');
    }
  }

  async pause() {
    logger.info(TAG, 'pause');
    if (this.backgroundTask === undefined) {
      return;
    }
    try {
      await this.backgroundTask.pause();
    } catch (err) {
      logger.info(TAG, `pause fail,err= ${JSON.stringify(err)}`);
    }
  }

  async resume() {
    logger.info(TAG, 'resume');
    if (this.backgroundTask === undefined) {
      return;
    }
    try {
      await this.backgroundTask.resume();
    } catch (err) {
      logger.info(TAG, `resume fail,err= ${JSON.stringify(err)}`);
    }
  }

  private async getFilesAndData(cacheDir: string, fileUris: Array<string>): Promise<Array<request.agent.FormItem>> {
    logger.info(TAG, `getFilesAndData begin`);
    let files: Array<request.agent.FormItem> = [];
    for (let i = 0; i < fileUris.length; i++) {
      logger.info(TAG, `getFile fileUri = ${fileUris[i]}`);
      let imagePath = await this.mediaUtils.copyFileToCache(cacheDir, fileUris[i]);
      logger.info(TAG, `getFilesAndData ${JSON.stringify(imagePath)}`);
      let file: request.agent.FormItem = {
        name: imagePath.split('cache/')[1],
        value: {
          path: './' + imagePath.split('cache/')[1]
        }
      }
      files.push(file);
    }
    logger.info(TAG, `getFilesAndData ${JSON.stringify(files)}`);
    return files;
  }
}

export const requestUpload = new Upload();
  • 源码参考:[AddPictures.ets]
/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { picker } from '@kit.CoreFileKit';
import { logger } from '@ohos/uploaddownload';
import { BusinessError } from '@kit.BasicServicesKit';

const TAG: string = 'AddPictures';

@Extend(Image) function imageStyle() {
  .width('100%')
  .aspectRatio(1)
  .objectFit(ImageFit.Fill)
  .backgroundColor($r('app.color.light_gray'))
  .borderRadius(12)
}

const TEXT_WIDTH_FULL: Length = '100%';
@Component
export struct AddPictures {
  @Consume imageList: Array<string>;

  build() {
    Column() {
      Text($r('app.string.tip'))
        .fontColor($r('app.color.text_normal'))
        .fontWeight(400)
        .fontFamily('HarmonyHeiTi')
        .fontSize(14)
        .opacity(0.4)
        .margin({ top: $r('app.float.add_pictures_margin_top'), bottom: $r('app.float.add_pictures_margin_bottom') })
        .width(TEXT_WIDTH_FULL)
      GridRow({ columns: { sm: 3, md: 6, lg: 8 }, gutter: 12 }) {
        ForEach(this.imageList, (item: string) => {
          GridCol({ span: 1 }) {
            Image(item)
              .imageStyle()
          }
        })
        GridCol({ span: 1 }) {
          Row() {
            Image($r('app.media.ic_public_add'))
              .size({ width: 24, height: 24 })
              .objectFit(ImageFit.Contain)
          }
          .width('100%')
          .height('100%')
          .justifyContent(FlexAlign.Center)
        }
        .width('100%')
        .aspectRatio(1)
        .backgroundColor($r('app.color.white'))
        .borderRadius(12)
        .onClick(() => {
          this.showDialog();
        })
      }
    }
    .width('100%')
  }

  addImages = (images: Array<string>) => {
    images.forEach((item: string) => {
      if (!this.imageList.includes(item)) {
        this.imageList.push(item);
      }
    })
    logger.info(TAG, `addImages imageList=${JSON.stringify(this.imageList)}`);
  }

  showDialog() {
    AlertDialog.show({
      message: $r('app.string.pick_album'),
      alignment: DialogAlignment.Bottom,
      offset: { dx: 0, dy: -12 },
      primaryButton: {
        value: $r('app.string.cancel'),
        fontColor: $r('app.color.btn_text_blue'),
        action: () => {
        }
      },
      secondaryButton: {
        value: $r('app.string.ok'),
        fontColor: $r('app.color.btn_text_blue'),
        action: () => {
          try {
            let photoSelectOptions = new picker.PhotoSelectOptions();
            photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
            photoSelectOptions.maxSelectNumber = 5;
            let photoPicker = new picker.PhotoViewPicker();
            photoPicker.select(photoSelectOptions).then((photoSelectResult: picker.PhotoSelectResult) => {
              this.addImages(photoSelectResult.photoUris);
            }).catch((err: BusinessError) => {
              logger.error(TAG, `'PhotoViewPicker.select failed with err: ${JSON.stringify(err)}`);
            });
          } catch (err) {
            logger.error(TAG, `'PhotoViewPicker failed with err: ${JSON.stringify(err)}`);
          }
        }
      }
    })
  }
}
  • 源码参考:[Upload.ets]
/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { AddPictures } from '../components/AddPictures';
import { BackgroundTaskState, requestUpload, TOAST_BOTTOM } from '@ohos/uploaddownload';

const TIME_MAX: number = 5;

@Entry
@Component
struct Upload {
  @StorageLink('isBackground') isBackground: boolean = false;
  @StorageLink('backTaskState') @Watch('stateChange') backTaskState: BackgroundTaskState = BackgroundTaskState.NONE;
  @State isBegin: boolean = false;
  @Provide imageList: Array<string> = [];
  @State progress: number = 0;
  @State countdown: number = 0;

  build() {
    Navigation() {
      Scroll() {
        AddPictures()
      }
      .padding({ left: 24, right: 24 })
      .width('100%')
      .layoutWeight(1)
      .align(Alignment.Top)

      Column() {
        Button() {
          if (this.isBackground && this.backTaskState !== BackgroundTaskState.NONE) {
            if (this.backTaskState === BackgroundTaskState.RUNNING) {
              Text($r('app.string.pause'))
                .fontSize(16)
                .fontWeight(500)
                .fontColor($r('app.color.white'))
            } else {
              Text($r('app.string.continue'))
                .fontSize(16)
                .fontWeight(500)
                .fontColor($r('app.color.white'))
            }
          } else if (this.isBegin && !this.isBackground) {
            Row() {
              Progress({ value: this.progress, type: ProgressType.Ring })
                .width(20)
                .height(20)
                .backgroundColor('#FFFFFF')
                .color('#558DFF')
                .style({ strokeWidth: 2, scaleCount: 100, scaleWidth: 2 })
              Text(`${this.getResourceString($r('app.string.uploading'))}${this.progress}%`)
                .fontSize(16)
                .fontColor('#FFFFFF')
                .fontWeight(500)
                .margin({ left: 12 })
            }.alignItems(VerticalAlign.Center)
          } else {
            if (this.countdown > 0) {
              Text(`${this.countdown}s`)
                .fontSize(16)
                .fontWeight(500)
                .fontColor($r('app.color.white'))
            } else {
              Text($r('app.string.upload'))
                .fontSize(16)
                .fontWeight(500)
                .fontColor($r('app.color.white'))
            }
          }
        }
        .id('publish')
        .width('100%')
        .height(40)
        .margin({ bottom: this.isBegin ? 16 : 24 })
        .enabled(this.countdown > 0 ? false : true)
        .backgroundColor($r('app.color.button_blue'))
        .onClick(() => {
          if (this.isBackground && this.backTaskState !== BackgroundTaskState.NONE) {
            requestUpload.pauseOrResume();
          } else {
            this.uploadFiles();
          }
        })

        if (this.isBegin) {
          Button() {
            Text($r('app.string.cancel'))
              .fontSize(16)
              .fontWeight(500)
              .fontColor($r('app.color.btn_text_blue'))
          }
          .id('cancel')
          .width('100%')
          .height(40)
          .margin({ bottom: 24 })
          .backgroundColor($r('app.color.button_light_gray'))
          .onClick(() => {
            // cancel task
            requestUpload.cancelTask();
            this.progress = 0;
            this.isBegin = false;
          })
        }
      }
      .width('100%')
      .padding({ left: 24, right: 24 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.light_gray'))
    .title($r('app.string.upload'))
    .hideBackButton(false)
    .titleMode(NavigationTitleMode.Mini)
    .mode(NavigationMode.Stack)
  }

  aboutToAppear() {
    this.isBegin = false;
    this.backTaskState = BackgroundTaskState.NONE;
  }

  stateChange() {
    if (this.backTaskState === BackgroundTaskState.NONE) {
      this.imageList = [];
    }
  }

  uploadFiles() {
    if (this.imageList.length === 0) {
      return;
    }
    if (this.isBackground) {
      AppStorage.setOrCreate('backTaskState', BackgroundTaskState.RUNNING)
      requestUpload.uploadFilesBackground(this.imageList);
      promptAction.showToast({ message: $r('app.string.background_task_start'), bottom: TOAST_BOTTOM });
    } else {
      this.isBegin = true;
      this.progress = 0;
      requestUpload.uploadFiles(this.imageList, (progress: number, isSucceed: boolean) => {
        this.progress = progress;
        if (this.progress === 100 && isSucceed) {
          this.isBegin = false;
          this.imageList = [];
          promptAction.showToast({ message: $r('app.string.upload_success'), bottom: TOAST_BOTTOM })
        }
        if (this.progress === 100 && isSucceed === false) {
          this.isBegin = false;
          this.countdown = TIME_MAX;
          let interval = setInterval(() => {
            if (this.countdown > 0) {
              this.countdown--;
            } else {
              clearInterval(interval);
            }
          }, 1000);
          promptAction.showToast({ message: $r('app.string.upload_fail'), bottom: TOAST_BOTTOM })
        }
      });
    }
  }

  getResourceString(resource: Resource) {
    let context = getContext(this) as common.UIAbilityContext;
    return context.resourceManager.getStringSync(resource.id);
  }
}
  • 参考接口:@ohos.request,@ohos.file.picker

    • 下载模块

      • 使用@ohos.request中接口agent.create创建上传任务,调用@ohos.request中的Task相关接口实现上传任务的创建、取消、暂停、继续、进度加载,失败的任务会调用查询接口获取失败原因并打印在日志中,前台下载任务只支持单个文件下载,后台下载任务支持多文件下载。使用@ohos.file.fs完成指定路径的创建和查询已下载的文件。
  • 源码参考:[RequestDownload.ets]

/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { request } from '@kit.BasicServicesKit';
import { logger } from '../utils/Logger';
import { TOAST_BOTTOM, TASK_MAX, TASK_PAUSE_MSG, TASK_NET_PAUSE_MSG, TASK_RESUME_MSG, TASK_NET_RESUME_MSG } from '../utils/Constants';

const TAG: string = 'RequestDownload';
let isNetPause = false;
class RequestDownload {
  private context: common.UIAbilityContext | undefined = undefined;
  private waitList: Array<string[]> = [];
  private downloadTask: request.agent.Task | undefined = undefined;
  progressCallback: Function | undefined = undefined;
  completedCallback: Function | undefined = undefined;
  failedCallback: Function | undefined = undefined;

  constructor() {
    setInterval(() => {
      this.flushBackgroundTask()
    }, 2000);
  }

  async downloadFilesBackground(folder: string, files: Array<string>) {
    logger.info(TAG, 'downloadFiles');
    this.context = getContext(this) as common.UIAbilityContext;
    files.forEach((item: string) => {
      this.waitList.push([folder, item]);
    });
  }

  async flushBackgroundTask() {
    let tasks = await request.agent.search({
      state: request.agent.State.RUNNING
    });
    if (tasks.length < TASK_MAX && this.waitList.length > 0) {
      let downloadList: Array<string[]> = [];
      if (this.waitList.length <= TASK_MAX - tasks.length) {
        downloadList = this.waitList;
        this.waitList = [];
      } else {
        downloadList = this.waitList.slice(0, TASK_MAX - tasks.length);
        this.waitList = this.waitList.slice(TASK_MAX - tasks.length, this.waitList.length);
      }
      logger.info(TAG, `this.waitList = ${JSON.stringify(this.waitList)}`);
      this.createBackgroundTask(downloadList);
    }
  }

  async createBackgroundTask(downloadList: Array<string[]>) {
    if (this.context === undefined) {
      return;
    }
    for (let i = 0; i < downloadList.length; i++) {
      try {
        let splitUrl = downloadList[i][1].split('//')[1].split('/');
        let downloadConfig: request.agent.Config = {
          action: request.agent.Action.DOWNLOAD,
          url: downloadList[i][1],
          method: 'POST',
          title: 'download',
          mode: request.agent.Mode.BACKGROUND,
          network: request.agent.Network.ANY,
          saveas: `./${downloadList[i][0]}/${splitUrl[splitUrl.length-1]}`,
          overwrite: true,
          gauge: true
        }
        let downTask = await request.agent.create(this.context, downloadConfig);
        await downTask.start();
      } catch (err) {
        logger.error(TAG, `task  err, err  = ${JSON.stringify(err)}`);
        this.waitList.push(downloadList[i]);
      }
    }
  }

  async pause() {
    if (this.downloadTask) {
      let taskInfo = await request.agent.show(this.downloadTask.tid);
      logger.info(TAG, `task pause, taskInfo  = ${JSON.stringify(taskInfo)}`);
      await this.downloadTask.pause();
    }
  }

  async resume() {
    if (this.downloadTask) {
      let taskInfo = await request.agent.show(this.downloadTask.tid);
      logger.info(TAG, `task resume, taskInfo  = ${JSON.stringify(taskInfo)}`);
      await this.downloadTask.resume();
    }
  }

  async downloadFile(folder: string, url: string, callback: (progress: number, isSuccess: boolean) => void) {
    logger.info(TAG, 'downloadFile');
    // 查询到存在正在执行的下载任务,提示并返回
    let tasks = await request.agent.search({
      state: request.agent.State.RUNNING,
      action: request.agent.Action.DOWNLOAD,
      mode: request.agent.Mode.FOREGROUND
    });
    if (tasks.length > 0) {
      promptAction.showToast({ message: $r('app.string.have_download_task_tips'), bottom: TOAST_BOTTOM });
      return;
    }
    let splitUrl = url.split('//')[1].split('/');
    let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
    let downloadConfig: request.agent.Config = {
      action: request.agent.Action.DOWNLOAD,
      url: url,
      method: 'GET',
      title: 'download',
      mode: request.agent.Mode.BACKGROUND,
      retry: true,
      network: request.agent.Network.ANY,
      saveas: `./${folder}/${splitUrl[splitUrl.length-1]}`,
      overwrite: true
    }
    logger.info(TAG, `downloadFile, downloadConfig = ${JSON.stringify(downloadConfig)}`);
    try {
      this.downloadTask = await request.agent.create(context, downloadConfig);
      this.downloadTask.on('progress', this.progressCallback = (progress: request.agent.Progress) => {
        logger.info(TAG, `progress,  progress = ${progress.processed} ${progress.state}`);
        let processed = Number(progress.processed.toString()).valueOf();
        let size = progress.sizes[0];
        let process: number = Math.floor(processed / size * 100);
        if (process < 100) {
          callback(process, false);
        }
      })
      this.downloadTask.on('completed', this.completedCallback = (progress: request.agent.Progress) => {
        logger.info(TAG, `download complete, file= ${url}, progress = ${progress.processed}`);
        callback(100, true);
        this.deleteTask();
      })
      this.downloadTask.on('pause', this.failedCallback = async (progress: request.agent.Progress) => {
        if (this.downloadTask) {
          let taskInfo = await request.agent.show(this.downloadTask.tid);
          logger.info(TAG, `pause,  resean = ${taskInfo.reason}, progress = ${progress.processed}, faults = ${JSON.stringify(taskInfo.faults)}`);
          isNetPause = taskInfo.faults === 0;
          if (isNetPause) {
            callback(TASK_NET_PAUSE_MSG, isNetPause);
          }
          else {
            callback(TASK_PAUSE_MSG, isNetPause);
          }
        }
      })
      this.downloadTask.on('resume', this.failedCallback = async (progress: request.agent.Progress) => {
        if (this.downloadTask) {
          let taskInfo = await request.agent.show(this.downloadTask.tid);
          logger.info(TAG, `resume,  resean = ${taskInfo.reason}, progress = ${progress.processed}, faults = ${JSON.stringify(taskInfo.faults)}`);
          if (isNetPause) {
            isNetPause = false;
            callback(TASK_NET_RESUME_MSG, isNetPause);
          }
          else {
            callback(TASK_RESUME_MSG, isNetPause);
          }
        }
      })
      this.downloadTask.on('failed', this.failedCallback = async (progress: request.agent.Progress) => {
        if (this.downloadTask) {
          let taskInfo = await request.agent.show(this.downloadTask.tid);
          logger.info(TAG, `fail,  resean = ${taskInfo.reason}, progress = ${progress.processed}, faults = ${JSON.stringify(taskInfo.faults)}`);
        }
        callback(100, false);
        this.deleteTask();
      })
      await this.downloadTask.start();
    } catch (err) {
      logger.error(TAG, `task  err, err  = ${JSON.stringify(err)}`);
      callback(100, false);
    }
  }

  async deleteTask() {
    if (this.downloadTask) {
      try {
        this.downloadTask.off('progress');
        this.progressCallback = undefined;
        this.downloadTask.off('completed');
        this.completedCallback = undefined
        this.downloadTask.off('failed');
        this.failedCallback = undefined
        await request.agent.remove(this.downloadTask.tid);
      } catch (err) {
        logger.info(TAG, `deleteTask fail, err= ${JSON.stringify(err)}`);
      }
    }

    this.downloadTask = undefined;
  }
}

export const requestDownload = new RequestDownload();
  • 源码参考:[Download.ets]
/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { promptAction } from '@kit.ArkUI';
import { router } from '@kit.ArkUI';
import { CustomDataSource } from '../components/CustomDataSource';
import {
  FileModel,
  FileType,
  fileUtils,
  logger,
  requestFiles,
  requestDownload,
  TOAST_BOTTOM,
  TASK_PAUSE_MSG,
  TASK_RESUME_MSG,
  TASK_NET_PAUSE_MSG,
  TASK_NET_RESUME_MSG
} from '@ohos/uploaddownload';
import { SelectFolderDialog } from '../components/SelectFolderDialog';

const TAG: string = 'Download';

const OFFSET_DY: Length = -12;
const OFFSET_DX: Length = 0;
const LOADING_PROGRESS_WIDTH: Length = 100;
const FULL_WIDTH: Length = '100%';
const FULL_HEIGHT: Length = '100%';
const LIST_WIDTH: Length = '100%';
const LIST_HEIGHT: Length = 'auto';
const LIST_BORDER_RADIUS: Length | BorderRadiuses = 24;
const PADDING_TOP: Length = 4;
const PADDING_BOTTOM: Length = 4;
const STROKE_WIDTH: Length = 1;
const START_MARGIN: Length = 44;
const END_MARGIN: Length = 12;
const COLUMN_PADDING_LEFT: Length = 12;
const COLUMN_PADDING_RIGHT: Length = 12;
const COLUMN_PADDING_BOTTOM: Length = 12;
const BUTTON_FONT_SIZE = 16;
const MARGIN_TOP: Length = 12;
const MARGIN_LEFT: Length = 12;
const MARGIN_RIGHT: Length = 12;
const MARGIN_BOTTOM: Length = 12;
const BUTTON_HEIGHT: Length = 45;
@Entry
@Component
struct Download {
  private fileData: CustomDataSource = new CustomDataSource([]);
  @StorageLink('isBackground') isBackground: boolean = false;
  @Provide downloadFolder: Array<string> = [];
  @State isGetData: boolean = false;
  @State checkFile: Array<string> = [];
  @State checkList: Array<boolean> = [];
  @State isRunning: boolean = false;
  @State isPause: boolean = false;
  @State isNetPause: boolean = false;
  @State progress: number = 0;
  private selectFolder = (folder: string) => {
    logger.info(TAG, `selectFolder = ${folder}`);
    this.download(folder);
  }
  private folderDialogController: CustomDialogController = new CustomDialogController({
    builder: SelectFolderDialog({ selectFolder: this.selectFolder }),
    autoCancel: true,
    alignment: DialogAlignment.Bottom,
    offset: { dx: OFFSET_DX,
      dy: OFFSET_DY }
  });

  build() {
    Navigation() {
      Column() {
        if (this.isGetData) {
          LoadingProgress()
            .width(LOADING_PROGRESS_WIDTH)
            .layoutWeight(1)
        } else {
          List({ space: 12 }) {
            LazyForEach(this.fileData, (item: FileModel, index: number) => {
              ListItem() {
                this.FileItem(item, index)
              }
            }, (item: FileModel) => JSON.stringify(item))
          }
          .width(LIST_WIDTH)
          .height(LIST_HEIGHT)
          .scrollBar(BarState.Off)
          .backgroundColor(Color.White)
          .borderRadius(LIST_BORDER_RADIUS)
          .padding({ top: PADDING_TOP,
            bottom: PADDING_BOTTOM })
          .divider({ strokeWidth: STROKE_WIDTH,
            startMargin: START_MARGIN,
            endMargin: END_MARGIN })
        }
        Column().layoutWeight(1)

        this.BottomView()
      }
      .padding({ left: COLUMN_PADDING_LEFT,
        right: COLUMN_PADDING_RIGHT,
        bottom: COLUMN_PADDING_BOTTOM })
      .height(FULL_HEIGHT)
    }
    .width(FULL_WIDTH)
    .height(FULL_HEIGHT)
    .hideBackButton(false)
    .titleMode(NavigationTitleMode.Mini)
    .mode(NavigationMode.Stack)
    .backgroundColor($r('app.color.light_gray'))
    .hideToolBar(false)
    .title($r('app.string.download'))
  }

  @Builder
  FileItem(file: FileModel, index: number) {
    Row() {
      Row() {
        if (file.fileType === FileType.FOLDER) {
          Image($r('app.media.ic_files_folder'))
            .size({ width: 24, height: 24 })
            .objectFit(ImageFit.Contain)
        } else if (file.fileType === FileType.IMAGE) {
          Image($r('app.media.ic_public_picture'))
            .size({ width: 24, height: 24 })
            .objectFit(ImageFit.Contain)
        } else if (file.fileType === FileType.MUSIC) {
          Image($r('app.media.ic_public_music'))
            .size({ width: 24, height: 24 })
            .objectFit(ImageFit.Contain)
        } else if (file.fileType === FileType.Video) {
          Image($r('app.media.ic_public_video'))
            .size({ width: 24, height: 24 })
            .objectFit(ImageFit.Contain)
        } else {
          Image($r('app.media.ic_public_document'))
            .size({ width: 24, height: 24 })
            .objectFit(ImageFit.Contain)
        }

        Text(decodeURIComponent(file.name))
          .fontSize(16)
          .fontWeight(400)
          .layoutWeight(1)
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .margin({ left: 12 })
      }
      .layoutWeight(1)

      Checkbox({ name: '', group: 'checkboxGroup' })
        .select(this.checkList[index])
        .selectedColor($r('app.color.button_blue'))
        .margin({ left: 12 })
        .hitTestBehavior(HitTestMode.None)
    }
    .width('100%')
    .padding({ left: 12, right: 12 })
    .height(48)
    .onClick(() => {
      this.fileCheck(index);
    })
  }

  @Builder
  BottomView() {
    Column({ space: 12 }) {
      Button() {
        Row() {
          if (!this.isBackground && this.isRunning) {
            if (this.isPause || this.isNetPause) {
              Text($r('app.string.continue'))
                .fontColor(Color.White)
                .fontSize(BUTTON_FONT_SIZE)
            }
            else {
              Text(`${this.progress}%`)
                .fontColor(Color.White)
                .fontSize(BUTTON_FONT_SIZE)
              Text($r('app.string.downloading'))
                .fontColor(Color.White)
                .fontSize(BUTTON_FONT_SIZE)
                .margin({ left: MARGIN_LEFT })
            }
          } else {
            Text($r('app.string.download'))
              .fontColor(Color.White)
              .fontSize(BUTTON_FONT_SIZE)
          }
        }
      }
      .id('download_to')
      .type(ButtonType.Capsule)
      .height(BUTTON_HEIGHT)
      .width(FULL_WIDTH)
      .backgroundColor($r('app.color.button_blue'))
      .onClick(() => {
        if (!this.isRunning) {
          this.folderDialogController.open();
        }
        else {
          if (!this.isNetPause) {
            if (this.isPause) {
              requestDownload.resume();
            }
            else {
              requestDownload.pause();
            }
          }
        }
      })

      Button($r('app.string.view_download_files'))
        .id('view_download_files')
        .type(ButtonType.Capsule)
        .backgroundColor($r('sys.color.ohos_id_color_button_normal'))
        .width('100%')
        .fontSize(BUTTON_FONT_SIZE)
        .margin({ bottom: MARGIN_BOTTOM })
        .fontColor($r('app.color.btn_text_blue'))
        .onClick(() => {
          router.pushUrl({
            url: 'pages/DownloadFiles'
          });
        })
    }
    .margin({ top: MARGIN_TOP,
      left: MARGIN_LEFT,
      right: MARGIN_RIGHT })
  }

  aboutToAppear() {
    this.isRunning = false;
    this.isPause = false;
    this.isGetData = true;
    requestFiles.requestFiles().then((data: FileModel[]) => {
      this.checkList = [];
      this.isRunning = false;
      this.fileData.dataArray = data;
      this.fileData.dataArray.forEach(() => {
        this.checkList.push(false);
      })
      this.isGetData = false;
      this.fileData.notifyDataReload();
    })
    fileUtils.listFolders().then((folders: Array<string>) => {
      this.downloadFolder = folders;
    })
  }

  fileCheck(index: number) {
    if (!this.isBackground) {
      for (let i = 0; i < this.checkList.length; i++) {
        if (i !== index) {
          this.checkList[i] = false;
        }
      }
    }
    this.checkList[index] = !this.checkList[index];
    logger.info(TAG, `this.checkList = ${JSON.stringify(this.checkList)}`);
  }

  download(folder: string) {
    this.checkFile = [];
    if (this.checkList === undefined) {
      return;
    }
    logger.info(TAG, `this.checkList = ${JSON.stringify(this.checkList)}`);
    for (let i = 0; i < this.checkList.length; i++) {
      if (this.checkList[i]) {
        let fileModel = this.fileData.getData(i);
        logger.info(TAG, `fileModel = ${JSON.stringify(fileModel)}`);
        fileModel.files.forEach((url: string) => {
          let splitUrl = url.split('//')[1].split('/');
          if (splitUrl[splitUrl.length-1] !== '') {
            this.checkFile.push(url);
          }
        });
      }
    }
    logger.info(TAG, `this.checkFile = ${JSON.stringify(this.checkFile)}`);
    if (this.checkFile.length === 0) {
      promptAction.showToast({ message: $r('app.string.check_file_tips'), bottom: TOAST_BOTTOM });
      return;
    }
    this.progress = 0;
    if (this.isBackground) {
      this.isRunning = false;
      requestDownload.downloadFilesBackground(folder, this.checkFile);
      this.checkFile = [];
      this.checkList = [];
      this.fileData.dataArray.forEach(() => {
        this.checkList.push(false);
      })
      this.fileData.notifyDataReload();
      promptAction.showToast({ message: $r('app.string.background_task_start'), bottom: TOAST_BOTTOM });
    } else {
      this.isRunning = true;
      requestDownload.downloadFile(folder, this.checkFile[0], this.downloadFileCallback);
    }
  }

  downloadFilesCallback = (downloadCount: number, isSuccess: boolean) => {
    this.progress = downloadCount;
    if (downloadCount === this.checkFile.length) {
      this.downloadFinish(isSuccess);
    }
  }
  downloadFileCallback = (progress: number, isSuccess: boolean) => {
    logger.info(TAG, `downloadFileCallback = ${progress}`);
    if (progress === TASK_PAUSE_MSG) {
      this.isPause = true;
    }
    else if (progress === TASK_RESUME_MSG) {
      this.isPause = false;
    }
    else if (progress === TASK_NET_PAUSE_MSG) {
      this.isNetPause = true;
      let message = $r('app.string.net_pause');
      promptAction.showToast({ message: message, bottom: TOAST_BOTTOM });
    }
    else if (progress === TASK_NET_RESUME_MSG) {
      this.isNetPause = false;
      let message = $r('app.string.net_resume');
      promptAction.showToast({ message: message, bottom: TOAST_BOTTOM });
    }
    else {
      this.progress = progress;
      if (this.progress === 100) {
        this.downloadFinish(isSuccess);
      }
    }
  }

  downloadFinish(isSuccess: boolean) {
    this.isRunning = false;
    this.checkFile = [];
    this.checkList = [];
    this.fileData.dataArray.forEach(() => {
      this.checkList.push(false);
    })
    this.fileData.notifyDataReload();
    let message = isSuccess ? $r('app.string.download_finish') : $r('app.string.download_fail');
    promptAction.showToast({ message: message, bottom: TOAST_BOTTOM });
  }
}
  • 源码参考:[FileUtils.ets]
/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { common } from '@kit.AbilityKit';
import { fileIo } from '@kit.CoreFileKit';
import { logger } from '../utils/Logger';

const TAG: string = 'FileUtil';
const ALBUMS: string[] = ['Pictures', 'Videos', 'Others'];

class FileUtil {
  constructor() {
  }

  async initDownloadDir(): Promise<void> {
    let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
    logger.info(TAG, `initDownloadDir cacheDir=${context.cacheDir}`);
    try {
      fileIo.mkdirSync(`${context.cacheDir}/${ALBUMS[0]}`);
      fileIo.mkdirSync(`${context.cacheDir}/${ALBUMS[1]}`);
      fileIo.mkdirSync(`${context.cacheDir}/${ALBUMS[2]}`);
    } catch (err) {
      logger.info(TAG, `initDownloadDir err =${JSON.stringify(err)}`);
    }
  }

  async listFolders(): Promise<Array<string>> {
    await this.initDownloadDir();
    return ALBUMS;
  }

  async clearFolder(folderName: string): Promise<void> {
    let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
    try {
      let files: string[] = fileIo.listFileSync(`${context.cacheDir}/${folderName}`);
      logger.info(TAG, `listFiles listFileSync =${JSON.stringify(files)}`);
      for (let i = 0; i < files.length; i++) {
        fileIo.unlinkSync(`${context.cacheDir}/${folderName}/${files[i]}`);
      }
    } catch (err) {
      logger.info(TAG, `listFiles err =${JSON.stringify(err)}`);
    }
  }

  async listFiles(folderName: string): Promise<Array<string>> {
    let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
    let files: string[] = [];
    try {
      files = fileIo.listFileSync(`${context.cacheDir}/${folderName}`);
      logger.info(TAG, `listFiles listFileSync =${JSON.stringify(files)}`);
    } catch (err) {
      logger.info(TAG, `listFiles err =${JSON.stringify(err)}`);
    }
    return files;
  }
}

export const fileUtils = new FileUtil();
  • 源码参考:[FileBrowse.ets]
/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { fileUtils } from '../utils/FileUtils';

@Preview
@Component
export struct FileBrowse {
  @State folders: Array<string> = ['folder'];
  @State files: Array<string> = [];
  @State currentFolder: string = '';

  aboutToAppear() {
    fileUtils.listFolders().then((folders: Array<string>) => {
      this.folders = folders;
    })
  }

  build() {
    Navigation() {
      List({ space: 12 }) {
        ForEach(this.folders, (item: string) => {
          ListItem() {
            NavRouter() {
              Row() {
                Image($r('app.media.ic_files_folder'))
                  .size({ width: 32, height: 26 })
                  .objectFit(ImageFit.Contain)
                Text(item)
                  .fontSize(16)
                  .width('100%')
                  .margin({ left: 12 })
              }
              .height(56)
              .padding({ left: 16 })
              .backgroundColor(Color.White)
              .borderRadius(24)

              NavDestination() {
                this.FilesView()
              }
              .title(this.CustomTitle(item))
              .backgroundColor($r('app.color.light_gray'))
            }
            .onStateChange(async (isActivated: boolean) => {
              if (isActivated) {
                this.currentFolder = item;
                this.files = await fileUtils.listFiles(item);
              }
            })
          }
        })
      }
      .padding({ left: 12, right: 12 })
    }
    .hideBackButton(false)
    .titleMode(NavigationTitleMode.Mini)
    .title($r('app.string.download_files_title'))
    .mode(NavigationMode.Stack)
    .backgroundColor($r('app.color.light_gray'))
  }

  @Builder
  CustomTitle(title: string) {
    Row() {
      Text(title)
        .fontSize(20)
        .fontColor($r('app.color.text_normal'))
        .fontWeight(700)
        .margin({ left: 8 })
    }
    .width('100%')
  }

  @Builder
  FilesView() {
    Column() {
      List({ space: 12 }) {
        if (this.files.length === 0) {
          ListItem() {
            Text($r('app.string.folder_empty'))
              .fontSize(16)
              .width('100%')
              .margin({ top: 50 })
              .textAlign(TextAlign.Center)
          }
        }
        ForEach(this.files, (item: string) => {
          ListItem() {
            Text(decodeURIComponent(item))
              .fontSize(16)
              .width('100%')
          }
          .padding(12)
          .height(48)
          .backgroundColor(Color.White)
          .borderRadius(24)
        })
      }
      .padding({ left: 12, right: 12 })
      .layoutWeight(1)

      Column() {
        Button() {
          Image($r('app.media.ic_public_delete'))
            .objectFit(ImageFit.Cover)
            .size({ width: 24, height: 24 })
        }
        .type(ButtonType.Circle)
        .width(40)
        .height(40)
        .backgroundColor('#FF0000')
        .margin({ left: 5 })

        Text($r('app.string.clear_folder'))
          .fontSize(14)
          .fontColor($r('app.color.text_normal'))
          .opacity(0.6)
          .margin({ top: 8 })
      }
      .margin({ bottom: 24, top: 6 })
      .onClick(() => {
        fileUtils.clearFolder(this.currentFolder);
        this.files = [];
      })
    }
    .height('100%')
    .backgroundColor($r('app.color.light_gray'))
  }
}
  • 参考接口:@ohos.request,@ohos.file.fs

以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
1

除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下

内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!

鸿蒙【北向应用开发+南向系统层开发】文档

鸿蒙【基础+实战项目】视频

鸿蒙面经

在这里插入图片描述

为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部