鸿蒙6.0应用开发——文件上传下载

应用支持将文件上传到网络服务器,也支持从网络服务器下载资源文件到本地目录。

上传应用文件

开发者可以使用上传下载模块(ohos.request)的上传接口将本地文件上传。文件上传过程通过系统服务代理完成。在API version 12中,request.agent.create接口增加了设置代理地址的参数,支持设置自定义代理地址。

说明

· 当前上传应用文件功能,request.uploadFile方式仅支持上传应用缓存文件路径(cacheDir)下的文件,request.agent方式支持上传用户公共文件和应用缓存文件路径下的文件。

· 使用上传下载模块,需声明权限:ohos.permission.INTERNET。

· 上传下载模块不支持Charles、Fiddler等代理抓包工具。

· 上传下载模块接口目前暂不支持子线程调用场景,如TaskPool等。

以下示例代码展示了两种将缓存文件上传至服务器的方法:

async requestUploadFile(fileName: string, callback: (progress: number, isSuccess: boolean) => void,
  context: common.UIAbilityContext) {
  // 获取应用文件路径
  // 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
  let url = await urlUtils.getUrl(context);
  let cacheDir = context.cacheDir;

  // 新建一个本地应用文件
  try {
    let file = fileIo.openSync(cacheDir + '/test.txt', fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
    fileIo.writeSync(file.fd, 'upload file test');
    fileIo.closeSync(file);
  } catch (error) {
    let err: BusinessError = error as BusinessError;
    logger.error(TAG, `Invoke uploadFile failed, code=${err.code}, message=${err.message}`);
  }

  // 上传任务配置项
  let files: request.File[] = [
  // uri前缀internal://cache 对应cacheDir目录
    {
      filename: fileName,
      name: 'test',
      uri: 'internal://cache/' + fileName,
      type: 'txt'
    }
  ]
  let data: request.RequestData[] = [{ name: 'name', value: 'value' }];
  let uploadConfig: request.UploadConfig = {
    url: url,
    header: {
      'key1': 'value1',
      'key2': 'value2'
    },
    method: 'POST',
    files: files,
    data: data
  }

  // 将本地应用文件上传至网络服务器
  try {
    request.uploadFile(context, uploadConfig)
      .then((uploadTask: request.UploadTask) => {
        uploadTask.on('complete', (taskStates: Array<request.TaskState>) => {
          for (let i = 0; i < taskStates.length; i++) {
            logger.info(TAG, `upload complete taskState: ${JSON.stringify(taskStates[i])}`);
          }
          callback(100, true);
        });
      })
      .catch((err: BusinessError) => {
        logger.error(TAG, `Invoke uploadFile failed, code=${err.code}, message=${err.message}`);
      })
  } catch (error) {
    let err: BusinessError = error as BusinessError;
    logger.error(TAG, `Invoke uploadFile failed, code=${err.code}, message=${err.message}`);
  }
}

代码逻辑走读:

  1. 获取应用文件路径
    • 使用urlUtils.getUrl(context)获取应用的文件路径url
    • 获取应用的缓存目录cacheDir
  2. 创建本地文件
    • 尝试打开或创建一个名为test.txt的本地文件,并写入测试内容'upload file test'
    • 如果打开或创建文件失败,记录错误日志。
  3. 配置上传任务
    • 定义上传文件的相关参数,包括文件名filename、文件类型type等。
    • 定义上传数据data
    • 配置上传任务的URL、头信息、方法、文件列表和数据。
  4. 执行上传任务
    • 使用request.uploadFile方法执行上传任务。
    • 在上传完成后,通过回调函数callback(100, true)通知上传成功。
    • 如果上传过程中出现错误,记录错误日志。
  5. 错误处理
    • 在整个过程中,通过try-catch块捕获可能的异常,并记录错误日志。
async requestAgentUpload(fileName: string, callback: (progress: number, isSucceed: boolean) => void,
  context: common.UIAbilityContext) {
  // 获取应用文件路径
  // 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
  let url = await urlUtils.getUrl(context);
  let cacheDir = context.cacheDir;

  let attachments: request.agent.FormItem[] = [{
    name: 'test',
    value: [
      {
        filename: fileName,
        path: cacheDir + '/' + fileName,
      },
    ]
  }];
  let config: request.agent.Config = {
    action: request.agent.Action.UPLOAD,
    url: url,
    mode: request.agent.Mode.FOREGROUND,
    overwrite: true,
    method: 'POST',
    headers: {
      'key1': 'value1',
      'key2': 'value2'
    },
    data: attachments
  };
  request.agent.create(context, config).then((task: request.agent.Task) => {
    task.start((err: BusinessError) => {
      if (err) {
        logger.error(TAG, `Failed to start the upload task, code=${err.code}, message=${err.message}`);
        return;
      }
    });
    task.on('progress', async (progress) => {
      logger.info(TAG, `Request upload status ${progress.state}, uploaded ${progress.processed}`);
    })
    task.on('completed', async () => {
      logger.info(TAG, `Request upload completed`);
      callback(100, true);
      // 该方法需用户管理任务生命周期,任务结束后调用remove释放task对象
      request.agent.remove(task.tid);
    })
  }).catch((err: BusinessError) => {
    logger.error(TAG, `Failed to start the upload task, code=${err.code}, message=${err.message}`);
  });
}

代码逻辑走读:

  1. 获取应用文件路径
    • 使用urlUtils.getUrl(context)异步获取应用的文件路径url
    • 获取应用的缓存目录cacheDir
  2. 配置上传任务
    • 定义上传文件的attachments数组,其中包含文件名和路径信息。
    • 配置上传参数config,包括上传动作、目标URL、上传模式、是否覆盖、请求方法、请求头和上传数据。
  3. 创建并启动上传任务
    • 使用request.agent.create方法创建上传任务,传入上下文和配置。
    • 在任务创建成功后,调用task.start方法启动上传任务,并处理可能的错误。
  4. 处理上传进度和完成事件
    • 通过task.on('progress', callback)监听上传进度,并在回调中记录上传状态和已上传字节数。
    • 通过task.on('completed', callback)监听上传完成事件,并在回调中通过callback(100, true)通知上传成功。
    • 调用request.agent.remove(task.tid)释放上传任务对象。
  5. 错误处理
    • 在任务创建或启动过程中捕获并记录错误信息。

下载网络资源文件至应用文件目录

开发者可以使用上传下载模块(ohos.request)的下载接口将网络资源文件下载到应用文件目录。对已下载的网络资源应用文件,开发者可以使用基础文件IO接口(ohos.file.fs)对其进行访问,使用方式与应用文件访问一致。文件下载过程使用系统服务代理完成,在api12中request.agent.create接口增加了设置代理地址参数,支持用户设置自定义代理地址。

说明

当前网络资源文件仅支持下载至应用文件目录。

使用上传下载模块,需声明权限:ohos.permission.INTERNET。

以下示例代码展示了将网络资源文件下载到应用内部文件目录的两种方法(示例requestDownloadFile中的clearExistFile方法可点击代码块右下角链接查看):

async requestDownloadFile(url: string, fileName: string, callback: (progress: number, isSuccess: boolean) => void,
  context: common.UIAbilityContext) {
  // 获取应用文件路径
  // 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
  let filesDir = context.cacheDir;
  let filePath = filesDir + '/' + fileName;
  this.clearExistFile(filePath);
  try {
    await request.downloadFile(context, {
      url: url,
      filePath: filePath,
    }).then((downloadTask: request.DownloadTask) => {
      downloadTask.on('complete', () => {
        // 获取文件状态信息,其中包含大小
        let fileStat = fileIo.statSync(filePath);
        let fileSize = fileStat.size;
        logger.info(TAG, `download complete, file= ${url}, size=${fileSize}, progress = 100%`);
        callback(100, true);
      })
    }).catch((err: BusinessError) => {
      logger.error(TAG, `downloadFile error, code=${err.code}, message=${err.message}`);
    });
  } catch (error) {
    let err: BusinessError = error as BusinessError;
    logger.error(TAG, `downloadFile catch error, code=${err.code}, message=${err.message}`);
  }
}

代码逻辑走读:

  1. 定义了一个异步函数 requestDownloadFile,接收文件下载的 URL、文件名、回调函数和上下文对象作为参数。
  2. 在函数内部,首先获取应用的文件路径,并清除已存在的同名文件。
  3. 使用 request.downloadFile方法发起文件下载请求,传入上下文和文件路径信息。
  4. 通过 then方法监听下载任务的完成事件,在下载完成时获取文件状态信息,包括文件大小。
  5. 使用 fileIo.statSync方法同步获取文件状态,并从中提取文件大小。
  6. 记录下载完成的日志信息,并通过回调函数返回下载进度为100%和成功状态。
  7. 使用 catch方法捕获下载过程中可能出现的错误,并记录错误日志。
  8. 使用 try-catch结构捕获可能的异常,确保错误处理的完整性。
async requestAgentDownload(url: string, fileName: string, callback: (progress: number, isSuccess: boolean) => void,
  context: common.UIAbilityContext) {
  // 获取应用文件路径
  // 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
  let filesDir = context.cacheDir;

  let config: request.agent.Config = {
    action: request.agent.Action.DOWNLOAD,
    url: url,
    saveas: fileName,
    gauge: true,
    overwrite: true,
    network: request.agent.Network.WIFI,
  };
  await request.agent.create(context, config).then((task: request.agent.Task) => {
    task.start((error: BusinessError) => {
      if (error) {
        logger.error(TAG, `start agent download task error, code=${error.code}, message=${error.message}`);
        return;
      }
    });
    task.on('progress', async (progress) => {
      logger.info(TAG, `Request download status ${progress.state}, downloaded ${progress.processed}`);
    })
    task.on('completed', async () => {
      logger.info(TAG, `Request download completed`);
      let filePath = filesDir + '/' + fileName;
      // 获取文件状态信息,其中包含大小
      let fileStat = fileIo.statSync(filePath);
      let fileSize = fileStat.size;
      logger.info(TAG, `download complete, file= ${url}, size=${fileSize}, progress = 100%`);
      callback(100, true);
      request.agent.remove(task.tid);
    })
  }).catch((err: BusinessError) => {
    logger.error(TAG, `download agent task catch error, code=${err.code}, message=${err.message}`);
  });
}

代码逻辑走读:

  1. 获取应用文件路径
    • 从提供的 context中获取缓存目录 filesDir,用于存储下载的文件。
  2. 配置下载任务
    • 创建一个下载任务的配置对象 config,指定下载动作、目标 URL、保存文件名、是否显示下载进度、是否覆盖同名文件、以及仅在 Wi-Fi 网络下进行下载。
  3. 创建并启动下载任务
    • 使用 request.agent.create方法创建下载任务,传入上下文和配置对象。
    • 在任务创建成功后,调用 task.start方法启动下载任务。如果启动失败,记录错误信息。
  4. 处理下载进度事件
    • 通过 task.on('progress', callback)监听下载进度事件,每当下载进度更新时,记录当前进度状态和已下载字节数。
  5. 处理下载完成事件
    • 通过 task.on('completed', callback)监听下载完成事件。
    • 当下载完成时,记录完成信息,获取下载文件的路径和大小,然后通过回调函数 callback返回下载进度为 100% 和成功标志。
    • 最后,调用 request.agent.remove方法移除任务。
  6. 错误处理
    • 在任务创建或执行过程中捕获并记录任何可能的错误。

下载网络资源文件至用户文件

开发者可以使用ohos.requestrequest.agent接口下载网络资源文件到指定的用户文件目录。

说明

从API version 20开始支持下载网络资源文件至用户文件。

下载文档类文件

开发者可以通过调用DocumentViewPickersave()接口保存文件并获得用户文件的uri,将此uri作为Config的saveas字段值进行下载。

async docFileAgentTask(url: string, fileName: string, callback: (progress: number, isSuccess: boolean) => void,
  context: common.UIAbilityContext) {
  // 创建文件管理器选项实例。
  try {
    const documentSaveOptions = new picker.DocumentSaveOptions();
    // 保存文件名(可选)。 默认为空。
    documentSaveOptions.newFileNames = [fileName];
    // 保存文件类型['后缀类型描述|后缀类型'],选择所有文件:'所有文件(*.*)|.*'(可选),如果选择项存在多个后缀(最大限制100个过滤后缀),默认选择第一个。如果不传该参数,默认无过滤后缀。
    documentSaveOptions.fileSuffixChoices = ['文档|.txt', '.pdf'];
    let uri: string = '';
    // 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
    const documentViewPicker = new picker.DocumentViewPicker(context);
    await documentViewPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
      uri = documentSaveResult[0];
      logger.info(TAG, `DocumentViewPicker.save to file succeed and uri is ${uri}`);
    }).catch((err: BusinessError) => {
      logger.error(TAG, `documentViewPicker.save error, code=${err.code}, message=${err.message}`);
    })
    if (uri != '') {
      let config: request.agent.Config = {
        action: request.agent.Action.DOWNLOAD,
        url: url,
        // saveas字段是DocumentViewPicker保存的文件的uri
        saveas: uri,
        gauge: true,
        // overwrite字段必须为true
        overwrite: true,
        network: request.agent.Network.WIFI,
        // mode字段必须为request.agent.Mode.FOREGROUND
        mode: request.agent.Mode.FOREGROUND,
      };
      try {
        await request.agent.create(context, config).then((task: request.agent.Task) => {
          task.start((err: BusinessError) => {
            if (err) {
              logger.error(TAG, `start download task error, code=${err.code}, message=${err.message}`);
              return;
            }
          });
          task.on('progress', async (progress) => {
            logger.info(TAG, `download status ${progress.state}, downloaded ${progress.processed}`);
          })
          task.on('completed', async (progress) => {
            logger.info(TAG, `download completed ${JSON.stringify(progress)}`);
            callback(100, true);
            // 该方法需用户管理任务生命周期,任务结束后调用remove释放task对象
            request.agent.remove(task.tid);
          })
        }).catch((err: BusinessError) => {
          logger.error(TAG, `Failed to operate a download task, Code: ${err.code}, message: ${err.message}`);
        });
      } catch (error) {
        let err: BusinessError = error as BusinessError;
        logger.error(TAG, `Failed to create a download task, code=${err.code}, message=${err.message}`);
      }
    }
  } catch (error) {
    let err: BusinessError = error as BusinessError;
    logger.error(TAG, `Failed to create a documentSaveOptions, code=${err.code}, message=${err.message}`);
    return;
  }
}

代码逻辑走读:

  1. 初始化文件保存选项
    • 创建documentSaveOptions实例,设置文件名和文件类型过滤器。
  2. 选择文件保存位置
    • 使用DocumentViewPicker实例documentViewPicker调用save方法,保存文件并获取文件URI。
    • 如果保存成功,记录URI;否则,记录错误信息。
  3. 创建下载任务
    • 如果获取到文件URI,配置下载任务参数config,包括下载动作、URL、保存路径、网络要求、模式等。
    • 使用request.agent.create创建下载任务,并监听任务进度和完成事件。
    • 在任务开始、进度更新和完成时记录日志,并调用回调函数通知任务完成。
  4. 错误处理
    • 在每个关键步骤中捕获并记录可能的错误,确保任务流程的健壮性。

下载音频类文件

开发者可以通过调用AudioViewPickersave()接口保存文件并获得用户文件的uri,将此uri作为Config的saveas字段值进行下载。

async audioFileAgentTask(url: string, fileName: string, callback: (progress: number, isSuccess: boolean) => void,
  context: common.UIAbilityContext) {
  // 创建文件管理器选项实例。
  const audioSaveOptions = new picker.AudioSaveOptions();
  // 保存文件名(可选)。 默认为空。
  audioSaveOptions.newFileNames = [fileName];

  let uri: string = '';
  // 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
  const audioViewPicker = new picker.AudioViewPicker(context);
  await audioViewPicker.save(audioSaveOptions).then((audioSelectResult: Array<string>) => {
    uri = audioSelectResult[0];
    logger.info(TAG, `AudioViewPicker.save to file succeed and uri is ${uri}`);
  }).catch((err: BusinessError) => {
    logger.error(TAG, `Invoke audioViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
  })
  if (uri != '') {
    let config: request.agent.Config = {
      action: request.agent.Action.DOWNLOAD,
      url: url,
      // saveas字段是AudioViewPicker保存的文件的uri
      saveas: uri,
      gauge: true,
      // overwrite字段必须为true
      overwrite: true,
      network: request.agent.Network.WIFI,
      // mode字段必须为request.agent.Mode.FOREGROUND
      mode: request.agent.Mode.FOREGROUND,
    };
    try {
      request.agent.create(context, config).then((task: request.agent.Task) => {
        task.start((err: BusinessError) => {
          if (err) {
            logger.error(TAG, `Failed to start the download task, Code: ${err.code}  message: ${err.message}`);
            return;
          }
        });
        task.on('progress', async (progress) => {
          logger.info(TAG, `Request download status ${progress.state}, downloaded ${progress.processed}`);
        })
        task.on('completed', async (progress) => {
          logger.info(TAG, `Request download completed, ${JSON.stringify(progress)}`);
          callback(100, true);
          // 该方法需用户管理任务生命周期,任务结束后调用remove释放task对象.
          request.agent.remove(task.tid);
        })
      }).catch((err: BusinessError) => {
        logger.error(TAG, `Failed to create a download task, code=${err.code}, message=${err.message}`);
      });
    } catch (error) {
      let err: BusinessError = error as BusinessError;
      logger.error(TAG, `Failed to create a audio download task, code=${err.code}, message=${err.message}`);
    }
  }
}

代码逻辑走读:

  1. 初始化文件保存选项
    • 创建audioSaveOptions实例,并设置新文件名fileName
  2. 获取音频文件URI
    • 使用audioViewPicker.save(audioSaveOptions)方法保存音频文件,并将返回的URI存储在uri变量中。
    • 如果保存成功,记录日志;如果失败,记录错误日志。
  3. 检查URI有效性
    • 如果uri不为空,继续执行下载任务。
  4. 配置下载请求
    • 创建下载任务的配置对象config,指定下载动作、URL、保存路径(即uri)、进度更新开关、覆盖开关、网络要求和模式等参数。
  5. 创建并启动下载任务
    • 使用request.agent.create(context, config)方法创建下载任务。
    • 启动任务,并监听进度和完成事件。
    • 在任务进度更新时,记录当前状态和已下载字节数。
    • 在任务完成时,调用回调函数callback(100, true)通知下载完成,并释放任务资源。
  6. 错误处理
    • 在整个过程中,对可能出现的错误进行捕获和日志记录。

下载图片或视频类文件

开发者可以通过调用PhotoAccessHelper模块描述createAsset()接口创建媒体文件并获取用户文件的URI,将其作为Config的saveas字段值进行下载。

需要权限:ohos.permission.WRITE_IMAGEVIDEO

权限ohos.permission.WRITE_IMAGEVIDEO权限机制中的基本概念中system_basic(系统基础服务)级别的受限开放权限,normal等级的应用需要将自身的APL等级声明为system_basic及以上。授权方式为user_grant,需要向用户申请授权

async mediaFileAgentTask(url: string, callback: (progress: number, isSuccess: boolean) => void,
  context: common.UIAbilityContext) {
  let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION |
  bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_METADATA;
  // 获取应用程序的accessTokenID。
  let tokenID = -1;
  try {
    await bundleManager.getBundleInfoForSelf(bundleFlags).then((data) => {
      logger.info(TAG, `Request getBundleInfoForSelf successfully. Data: ${JSON.stringify(data)}`);
      tokenID = data.appInfo.accessTokenId;
    }).catch((err: BusinessError) => {
      logger.error(TAG, `GetBundleInfoForSelf failed, code=${err.code}, message=${err.message}`);
    });
  } catch (err) {
    let message = (err as BusinessError).message;
    logger.error(`GetBundleInfoForSelf failed: ${message}`);
  }

  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  let grant = true;
  // 校验应用是否授予权限。使用Promise异步回调。
  await atManager.checkAccessToken(tokenID, 'ohos.permission.WRITE_IMAGEVIDEO')
    .then((data: abilityAccessCtrl.GrantStatus) => {
      logger.info(TAG, `Request checkAccessToken success, data->${JSON.stringify(data)}`);
      if (data != abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
        grant = false;
      }
    })
    .catch((err: BusinessError) => {
      logger.error(TAG, `CheckAccessToken fail, code=${err.code}, message=${err.message}`);
    });

  if (!grant) {
    // 用于UIAbility拉起弹框请求用户授权。使用callback异步回调。
    await atManager.requestPermissionsFromUser(context, ['ohos.permission.WRITE_IMAGEVIDEO'])
      .then((data: PermissionRequestResult) => {
        logger.info(TAG, `Request grant: ${JSON.stringify(data)}`);
        logger.info(TAG, `Request grant permissions: ${data.permissions}`);
        logger.info(TAG, `Request grant authResults: ${data.authResults}`);
        logger.info(TAG, `Request grant dialogShownResults: ${data.dialogShownResults}`);
      }).catch((err: BusinessError) => {
        logger.error(TAG, `Grant error, code=${err.code}, message=${err.message}`);
      });
  }

  try {
    let photoType: photoAccessHelper.PhotoType = photoAccessHelper.PhotoType.IMAGE;
    let extension: string = 'jpg';
    let options: photoAccessHelper.CreateOptions = {
      title: 'media'
    }
    // 获取相册管理模块的实例,用于访问和修改相册中的媒体文件。
    let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
    // 指定文件类型、后缀和创建选项,创建图片或视频资源,以Promise方式返回结果。
    let uri: string = await phAccessHelper.createAsset(photoType, extension, options);
    logger.info(TAG, `Request createAsset uri ${uri}`);

    let config: request.agent.Config = {
      action: request.agent.Action.DOWNLOAD,
      url: url,
      // saveas字段是PhotoAccessHelper保存的文件的uri
      saveas: uri,
      gauge: true,
      // overwrite字段必须为true
      overwrite: true,
      network: request.agent.Network.WIFI,
      // mode字段必须为request.agent.Mode.FOREGROUND
      mode: request.agent.Mode.FOREGROUND,
    };
    request.agent.create(context, config).then((task: request.agent.Task) => {
      task.start((err: BusinessError) => {
        if (err) {
          logger.error(TAG, `Failed to start the download task, Code: ${err.code}  message: ${err.message}`);
          return;
        }
      });
      task.on('progress', async (progress) => {
        logger.info(TAG, `Request download status ${progress.state}, downloaded ${progress.processed}`);
      })
      task.on('completed', async (progress) => {
        logger.info(TAG, `Request download completed, ${JSON.stringify(progress)}`);
        callback(100, true);
        // 该方法需用户管理任务生命周期,任务结束后调用remove释放task对象
        request.agent.remove(task.tid);
      })
    }).catch((err: BusinessError) => {
      logger.error(TAG, `Failed to operate a download task, Code: ${err.code}, message: ${err.message}`);
    });
  } catch (error) {
    let err: BusinessError = error as BusinessError;
    logger.error(TAG, `Failed to create a media download task, code=${err.code}, message=${err.message}`);
  }
}

代码逻辑走读:

  1. 获取应用程序的accessTokenID
    • 使用bundleManager.getBundleInfoForSelf获取当前应用的包信息,并从中提取accessTokenId
    • 若获取失败,记录错误日志。
  2. 校验应用是否授予权限
    • 使用abilityAccessCtrl.AtManagercheckAccessToken方法检查权限状态。
    • 如果权限未授予,记录状态并准备请求用户授权。
  3. 请求用户授权
    • 如果权限未授予,通过atManager.requestPermissionsFromUser请求用户授权。
    • 请求失败时,记录错误日志。
  4. 创建媒体文件资源
    • 获取photoAccessHelper实例,指定文件类型、后缀和创建选项,创建图片或视频资源。
    • 创建失败时,记录错误日志。
  5. 启动下载任务
    • 配置下载任务的参数,包括下载动作、URL、保存路径等。
    • 使用request.agent.create创建下载任务,并启动任务。
    • 监听下载进度和完成事件,更新日志并回调下载完成状态。
    • 任务结束后调用request.agent.remove释放任务对象。
  6. 错误处理
    • 整个过程中,任何步骤出现错误都会被捕获并记录错误日志。

添加任务速度限制与超时限制

开发者可以使用ohos.request (上传下载)模块中的接口上传本地文件或下载网络资源文件。为方便对任务速度及时长进行限制,分别在API version 18中增加了setMaxSpeed接口,支持用户设置任务的最高速度限制;在API version 20的request.agent.create接口中增加了最低限速及超时参数,支持用户自定义最低速度限制以及超时时间。

以下是对下载任务进行速度限制与超时限制的方式的示例代码演示:

async speedLimitDownload(url: string, fileName: string, callback: (progress: number, isSuccess: boolean) => void,
  context: common.UIAbilityContext) {
  // 获取应用文件路径
  // 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
  let filesDir = context.cacheDir;

  let config: request.agent.Config = {
    action: request.agent.Action.DOWNLOAD,
    url: url,
    saveas: fileName,
    gauge: true,
    overwrite: true,
    network: request.agent.Network.WIFI,
    // 最低速度限制规则:
    // 1. 若任务速度持续低于设定值(如:16 * 1024 B/s)达到指定时长(如:10s),则任务失败
    // 2. 重置计时条件:
    // - 任一秒速度超过最低限速
    // - 任务暂停后恢复
    // - 任务停止后重启
    minSpeed: {
      speed: 16 * 1024,
      duration: 10
    },
    // 超时控制规则:
    // 1. 连接超时(connectionTimeout):
    // - 单次连接建立耗时超过设定值(如:60s)则任务失败
    // - 多次连接时各次独立计时(不累积)
    // 2. 总超时(totalTimeout):
    // - 任务总耗时(含连接+传输时间)超过设定值(如:120s)则失败
    // - 暂停期间不计时,恢复后累积计时
    // 3. 重置计时条件:任务失败或停止时重置计时
    timeout: {
      connectionTimeout: 60,
      totalTimeout: 120,
    }
  };
  request.agent.create(context, config).then((task: request.agent.Task) => {
    // 设置任务速度上限
    task.setMaxSpeed(10 * 1024 * 1024).then(() => {
      logger.info(TAG, `Succeeded in setting the max speed of the task. result: ${task.tid}`);
    }).catch((err: BusinessError) => {
      logger.error(TAG, `Failed to set the max speed of the task, code=${err.code}, message=${err.message}`);
    });
    task.start((err: BusinessError) => {
      if (err) {
        logger.error(TAG, `Failed to start the download task, code=${err.code}, message=${err.message}`);
        return;
      }
    });
    task.on('progress', async (progress) => {
      logger.info(TAG, `Request download status ${progress.state}, downloaded ${progress.processed}`);
    })
    task.on('completed', async () => {
      logger.info(TAG, `Request download completed`);
      // 获取文件状态信息,其中包含大小
      let filePath = filesDir + '/' + fileName;
      // 获取文件状态信息,其中包含大小
      let fileStat = fileIo.statSync(filePath);
      let fileSize = fileStat.size;
      logger.info(TAG, `download complete, file= ${url}, size=${fileSize}, progress = 100%`);
      callback(100, true);
      request.agent.remove(task.tid);
    })
  }).catch((err: BusinessError) => {
    logger.error(TAG, `Failed to create a download task, Code: ${err.code}, message: ${err.message}`);
  });
}

代码逻辑走读:

  1. 获取应用文件路径:从UIAbilityContext中获取缓存目录作为文件保存路径。
  2. 配置下载任务:定义下载任务的配置对象,包括下载动作、目标URL、保存文件名、是否显示下载进度、是否覆盖同名文件、限定网络条件、最低速度限制和超时控制规则。
  3. 创建下载任务:使用request.agent.create方法创建下载任务,并处理成功和失败的情况。
  4. 设置任务速度上限:调用task.setMaxSpeed方法设置下载速度上限,并处理成功和失败的情况。
  5. 启动下载任务:调用task.start方法启动下载任务,并处理启动失败的情况。
  6. 监听下载进度和完成事件:通过task.on方法监听下载进度和完成事件,更新日志并调用回调函数。
  7. 处理下载完成:在下载完成后,获取文件状态信息,包括文件大小,并通过回调函数返回下载进度和成功状态。
  8. 移除下载任务:下载完成后,调用request.agent.remove方法移除下载任务。

添加网络配置

HTTP拦截

开发者可以通过设置配置文件实现HTTP拦截功能。上传下载模块在应用配置文件中禁用HTTP后,无法创建明文HTTP传输的上传下载任务。配置文件在APP中的路径是:src/main/resources/base/profile/network_config.json。请参考网络管理模块配置文件网络连接安全配置,了解需要配置的具体参数。

参考配置文件如下所示:

{
  "network-security-config": {
    "base-config": {
      "cleartextTrafficPermitted": true,
      "trust-anchors": [
        {
          "certificates": "/etc/security/certificates"
        }
      ]
    },
    "domain-config": [
      {
        "cleartextTrafficPermitted": true,
        "domains": [
          {
            "include-subdomains": true,
            "name": "*.example.com"
          }
        ],
      }
    ]
  }
}

使用WantAgent实现通知栏跳转功能

从API version 22开始,开发者可以使用wantAgent接口与上传下载模块结合,实现点击任务通知后跳转至应用指定页面的功能。

功能介绍

通过在下载任务的配置request.agent.Notification中设置wantAgent参数,开发者可以指定用户点击通知后要跳转的应用页面及相关参数。当用户点击正在进行或已完成的下载任务通知时,系统会根据wantAgent参数启动指定的应用能力。

示例代码

以下示例代码展示了如何创建一个带有wantAgent功能的下载任务:

async wantAgentDownload(url: string, fileName: string, callback: (progress: number, isSuccess: boolean) => void,
  context: common.UIAbilityContext) {
  // 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext

  // 创建wantAgentInfo对象,用于定义点击通知后要执行的操作
  let wantAgentInfo: wantAgent.WantAgentInfo = {
    wants: [
      {
        deviceId: '',
        bundleName: 'com.samples.uploaddownloadguide', // 替换为实际应用的包名
        abilityName: 'EntryAbility', // 替换为实际的ability名称
        action: '',
        entities: [],
        uri: '',
        parameters: {} // 可以传递自定义参数
      }
    ],
    actionType: wantAgent.OperationType.START_ABILITY,
    requestCode: 0,
    wantAgentFlags: [wantAgent.WantAgentFlags.CONSTANT_FLAG]
  };

  // 获取WantAgent实例
  let wantAgentInstance: WantAgent;
  try {
    wantAgentInstance = await wantAgent.getWantAgent(wantAgentInfo);
  } catch (error) {
    logger.error(TAG, `Failed to get WantAgent, Code: ${error.code}  message: ${error.message}`);
    return;
  }

  let filesDir = context.cacheDir;
  // 创建下载任务配置,包含wantAgent参数
  let config: request.agent.Config = {
    action: request.agent.Action.DOWNLOAD,
    url: url, // 替换为实际的下载地址
    title: '下载任务通知标题',
    description: '下载任务通知描述',
    mode: request.agent.Mode.BACKGROUND,
    overwrite: true,
    method: 'GET',
    saveas: fileName,
    network: request.agent.Network.ANY,
    gauge: true,
    notification: {
      visibility: request.agent.VISIBILITY_COMPLETION | request.agent.VISIBILITY_PROGRESS,
      wantAgent: wantAgentInstance,
    }
  };

  // 创建并启动下载任务
  try {
    request.agent.create(context, config).then((task: request.agent.Task) => {
      task.start((err: BusinessError) => {
        if (err) {
          logger.error(TAG, `Failed to start the download task, Code: ${err.code}  message: ${err.message}`);
          return;
        }
      });
      task.on('progress', async (progress) => {
        logger.error(TAG, `Request download status ${progress.state}, downloaded ${progress.processed}`);
      })
      task.on('completed', async (progress) => {
        logger.info(TAG, `Request download completed, ${JSON.stringify(progress)}`);
        // 获取文件状态信息,其中包含大小
        let filePath = filesDir + '/' + fileName;
        // 获取文件状态信息,其中包含大小
        let fileStat = fileIo.statSync(filePath);
        let fileSize = fileStat.size;
        logger.info(TAG, `download complete, file= ${url}, size=${fileSize}, progress = 100%`);
        callback(100, true);
        // 该方法需用户管理任务生命周期,任务结束后调用remove释放task对象
        request.agent.remove(task.tid);
      })
    }).catch((err: BusinessError) => {
      logger.error(TAG, `Failed to operate a download task, Code: ${err.code}, message: ${err.message}`);
    });
  } catch (error) {
    let err: BusinessError = error as BusinessError;
    logger.error(TAG, `Failed to operate a download task, Code: ${err.code}, message: ${err.message}`);
  }
}

代码逻辑走读:

  1. 定义下载任务参数
    • 创建一个wantAgentInfo对象,用于定义点击通知后要执行的操作,包括应用包名、ability名称等信息。
    • 获取WantAgent实例,用于后续的下载任务配置。
  2. 配置下载任务
    • 定义下载任务的配置参数,包括下载地址、文件名、通知标题、通知描述等。
    • 设置下载模式为后台模式,并启用进度条和通知。
  3. 启动下载任务
    • 使用request.agent.create方法创建下载任务。
    • 启动下载任务,并监听任务进度和完成事件。
    • 在任务完成时,通过fileIo.statSync获取文件状态信息,包括文件大小。
    • 调用回调函数通知下载完成,并清理任务。
  4. 错误处理
    • 捕获并处理所有可能的错误,记录错误日志。

配置说明

在上面的示例代码中,我们主要通过以下几个步骤实现了通知栏跳转功能:

  1. 创建WantAgentInfo对象:定义点击通知后要执行的操作,包括目标应用的包名、ability名称和需要传递的具体参数。
  2. 获取WantAgent实例:通过wantAgent.getWantAgent()方法获取WantAgent实例。
  3. 配置下载任务:在request.agent.Config中设置notification属性,并将wantAgent参数设置为前面获取的WantAgent实例。
  4. 设置通知可见性:通过visibility参数可以控制通知显示的内容,例如进度、完成状态等。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐