第96篇 | HarmonyOS 异常合集:权限拒绝、网络失败、模型失败、相机失败

第96篇 | HarmonyOS 异常合集:权限拒绝、网络失败、模型失败、相机失败

发布前最容易漏掉的是异常路径。双镜记忆相机串了相机、定位、相册、ARK、视频下载、系统分享和保险箱,只要其中一环失败,页面都应该给用户明确反馈。异常处理不是堆 catch,而是把错误变成可以继续操作的状态。

这一篇按四类异常整理:权限拒绝、网络/模型失败、文件下载和导出失败、系统分享失败。我们重点看服务层如何抛出可读错误,页面层如何把错误写回状态文案。

本篇目标

  • 梳理服务层和页面层的错误边界。
  • 确认 ARK 请求失败、空响应、网络异常都有明确错误。
  • 检查图生图、图生视频、导出系统相册、系统分享的失败文案。
  • 建立发布前异常测试矩阵。

对应源码位置

  • superImage/entry/src/main/ets/services/VolcengineArkService.ets
  • superImage/entry/src/main/ets/pages/Index.ets

服务层错误要保留上下文

requestJson 统一处理在线请求:非 2xx 状态抛出带状态码的错误,空响应也单独处理,传输失败再包装成 transport failed。这样页面拿到的不是模糊失败,而是能判断方向的错误。

服务层不直接弹 UI,这是边界。它负责把失败说清楚,页面负责决定给用户展示什么文案、保留哪些按钮。

异常处理的目标是让用户知道发生了什么,以及下一步能做什么

ts 复制代码
  private static async requestJson<T>(
    url: string,
    method: http.RequestMethod,
    apiKey: string,
    bodyText?: string
  ): Promise<ArkHttpJsonResponse<T>> {
    const request = http.createHttp();
    const header: ArkRequestHeaders = {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey}`
    };
    const requestOptions: http.HttpRequestOptions = {
      method: method,
      header: header as Object,
      expectDataType: http.HttpDataType.STRING,
      readTimeout: 60000,
      connectTimeout: 60000
    };
    if (bodyText) {
      requestOptions.extraData = bodyText;
    }

    try {
      const response = await request.request(url, requestOptions);
      const responseText = typeof response.result === 'string'
        ? response.result
        : JSON.stringify(response.result);
      if ((response.responseCode as number) < 200 || (response.responseCode as number) >= 300) {
        throw new Error(`Ark request failed (${response.responseCode}): ${responseText}`);
      }
      if (!responseText || responseText.length === 0) {
        throw new Error('Ark response body is empty.');
      }
      const parsedData = JSON.parse(responseText) as T;
      return {
        data: parsedData,
        rawText: responseText
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : JSON.stringify(error);
      throw new Error(`Ark request transport failed: ${message}`);
    } finally {

上传图片前先检查文件边界

在线图解会把本地图片转成 data URL。toDataUrl 里先读取文件大小,空文件直接报错,超过 10MB 也报错。这些检查能在请求发出前拦住明显失败,减少无意义网络调用。

这段代码也提醒我们:异常路径要尽量靠近问题发生的位置。文件为空就是文件问题,不应该等远端模型返回失败才提示。

图片上传前先检查空文件和 10MB 限制,减少无效请求

ts 复制代码
  private static toDataUrl(filePath: string): string {
    const bytes = VolcengineArkService.readBinaryFile(filePath);
    const base64Text = new util.Base64Helper().encodeToStringSync(bytes, util.Type.BASIC);
    return `data:${VolcengineArkService.inferMimeType(filePath)};base64,${base64Text}`;
  }

  private static readBinaryFile(filePath: string): Uint8Array {
    let file: fs.File | undefined = undefined;
    try {
      const stat = fs.statSync(filePath);
      if (stat.size <= 0) {
        throw new Error(`Image file is empty: ${filePath}`);
      }
      if (stat.size > VolcengineArkService.DATA_URL_IMAGE_LIMIT_BYTES) {
        throw new Error(`Image exceeds 10 MB data-url limit: ${filePath}`);
      }

      file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
      const buffer = new ArrayBuffer(stat.size);
      const bytesRead = fs.readSync(file.fd, buffer);
      if (bytesRead <= 0) {
        throw new Error(`Failed to read image bytes: ${filePath}`);
      }
      return new Uint8Array(buffer, 0, bytesRead);
    } catch (error) {
      const message = error instanceof Error ? error.message : JSON.stringify(error);
      throw new Error(`Failed to prepare image upload payload: ${message}`);
    } finally {

模型失败要写成用户能读懂的文案

页面层会把部分模型权限问题转成更具体的文案,例如未开通 Seedream、没有图像生成模型权限、Seedance 参数不可用等。这比统一显示"调用失败"更利于用户排查。

对于训练营文章,异常文案也能帮助读者理解功能边界:不是所有失败都来自代码错误,账号权限、模型开通和网络状态都可能影响结果。

模型相关失败要区分 Key、选择记录、模型权限和参数错误

ts 复制代码
    if (this.arkApiKey.trim().length === 0) {
      this.videoTaskStatusText = '请先配置火山方舟 API Key';
      return;
    }

    const sourceRecords = this.getSelectedVideoRecords();
    if (sourceRecords.length < 1) {
      this.videoTaskStatusText = '请先选择照片';
      return;
    }

    this.videoTaskBusy = true;
    this.videoTaskStatusText = `正在提交 ${sourceRecords.length} 张照片的图生视频任务...`;
    try {
      const task = await VolcengineArkService.createVideoTask(
        this.getAbilityContext(),
        sourceRecords,
        this.arkApiKey.trim()
      );
      this.videoTaskId = task.id;
      this.videoUrl = task.videoUrl;
      this.videoTaskStatusText = this.getVolcengineVideoRecordStatusText(task);
      let syncedRecord = await this.syncVolcengineVideoManagerRecord(task, sourceRecords);
      this.galleryMediaTab = 'video';
      this.galleryViewMode = 'album';
      const latestTask = await this.pollVolcengineVideoTask(task, sourceRecords);
      this.videoUrl = latestTask.videoUrl;
      this.videoTaskStatusText = this.getVolcengineVideoRecordStatusText(latestTask);
      syncedRecord = await this.syncVolcengineVideoManagerRecord(latestTask, sourceRecords);
      if (latestTask.status === 'succeeded' && latestTask.videoUrl.length > 0) {
        this.galleryNoticeText = '';
        this.regeneratingVideoManagerRecordId = '';
        this.openVideoManagerRecordPreview(syncedRecord);
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : JSON.stringify(error);
      if (message.includes('ModelNotOpen')) {
        this.videoTaskStatusText = '图生视频提交未成功,请确认方舟 API Key 与 Seedance 1.0 Pro Fast 权限后重试。';
      } else if (message.includes('InvalidEndpointOrModel')) {
        this.videoTaskStatusText = '图生视频模型参数不可用,已切换为 Seedance 1.0 Pro Fast 首帧模式,请重试。';
      } else {
        this.videoTaskStatusText = `智能介绍片提交失败:${message}`;
      }
    } finally {

导出和分享失败不能吞掉

导出系统相册和系统分享都是跨应用能力,失败原因可能来自文件不存在、系统取消、权限、目标应用异常。项目里会把错误写回 videoTaskStatusText 或对应状态,用户至少知道操作没有完成。

发布前异常测试可以故意清空视频路径、断网、取消系统弹窗、连续点击按钮。目标不是让所有异常都成功,而是每个异常都能被解释和恢复。

导出失败要回到页面状态,不能只在日志里出现

异常矩阵建议至少包含:拒绝相机权限、拒绝定位权限、ARK Key 为空、模型未开通、视频 URL 为空、系统分享取消、导出系统相册取消。

工程验收表

检查项 通过标准
服务层错误 HTTP 状态码、空响应、传输失败都有明确错误。
文件边界 空文件、超大图片、空视频 URL 会提前失败。
用户文案 模型权限、Key 缺失、网络失败能被用户理解。
可恢复 失败后按钮状态恢复,用户可以修改条件后重试。

真机复测口令

先断网,再清空 ARK API Key,最后传入一个空视频 URL,分别触发图解、视频任务和导出流程。每一次失败都应该回到可读状态:用户知道哪里失败、能不能重试、下一步要改什么条件。

异常合集的重点是分层。服务层负责抛出带上下文的错误,页面层负责把错误翻译成用户文案,按钮状态负责恢复可操作。三层缺一层,用户看到的都会是模糊的"失败了"。

今日练习

  1. 模拟 HTTP 非 2xx 响应,确认 requestJson 抛出的错误包含状态码。
  2. 清空 API Key 后点击图生视频,确认不会发起无意义请求。
  3. 取消系统分享面板,确认页面不会误提示分享成功。
相关推荐
Swift社区1 小时前
AI Native 鸿蒙 App:从页面驱动到智能驱动的架构革命
人工智能·架构·harmonyos
木咺吟1 小时前
鸿蒙原生应用实战(五):数据统计与个人中心——柱状图实现、统计计算与设置面板
harmonyos
浮芷.2 小时前
鸿蒙PC-HarmonyOS 6.1 60fps 流畅动画实现与 ArkTS 常见错误深度剖析
华为·harmonyos·鸿蒙
风满城332 小时前
鸿蒙原生应用实战(四):成就系统与排行榜开发 — 数据展示与交互进阶
harmonyos
伶俜662 小时前
鸿蒙原生应用实战(五)ArkUI 图片拼接/长图生成:多图合并 + Canvas 绘制 + 导出分享
华为·harmonyos
前端不太难2 小时前
当 AI 接管 Workspace:鸿蒙 PC Agent 架构设计实践
人工智能·状态模式·harmonyos
伶俜662 小时前
鸿蒙原生应用实战(六)ArkUI 屏幕录制 + GIF 截取:录屏 + 裁剪关键帧 + 转 GIF
华为·harmonyos
祭曦念2 小时前
【共创季稿事节】谁是卧底词语生成器_鸿蒙开发实战
华为·harmonyos
YM52e2 小时前
鸿蒙PC ArkTS 死亡轮循深度解析与解决方案
学习·华为·harmonyos·鸿蒙·鸿蒙系统