《HarmonyOS技术精讲-Core File Kit》第15篇:错误处理与异常排查指南

在这里插入图片描述

文件操作是应用开发中最基础也最容易出问题的环节。HarmonyOS NEXT 的 Core File Kit 提供了统一文件访问接口,但很多开发者遇到文件操作异常时,只做了简单的 try-catch,错误信息直接弹 Toast 或者干脆不处理。结果是用户遇到“文件保存失败”时一脸懵,开发者拿到日志也看不出具体问题。

这篇文章不讲 API 怎么用,直接总结 Core File Kit 文件操作中最常见的错误码,给出一个完整的错误处理模板,让你能快速定位问题,而不是在日志里大海捞针。

错误码背后的真实场景

在 HarmonyOS 中,文件操作失败时会抛出 BusinessError 对象,里面包含 codemessage。这几个错误码出现频率最高:

错误码 含义 真实触发场景
13900001 操作失败(通用) 文件写入内容格式不对、路径非法
13900002 文件不存在 路径写错了、文件被删除
13900003 权限错误 没申请文件读写权限、沙箱路径访问越界
13900004 存储空间不足 设备磁盘满了、沙箱配额用完
13900005 文件已存在 create() 时文件已存在但未指定覆盖
13900006 路径不是文件 把目录当文件打开了
13900007 文件被占用 另一个进程正在写文件

这些错误码在官方文档有说明,但实际开发中容易忽略的是:同一个操作可能触发多个错误码,比如磁盘满时写文件,可能先报权限错误(13900003),再报磁盘满(13900004)。错误处理一定要按优先级走。

完整的错误处理示例

下面这个代码段覆盖了文件读写时的常见异常,并且对用户和开发者分别输出不同级别的信息:

import { fileIo } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct FileOperationExample {
  @State status: string = '等待操作';

  build() {
    Column() {
      Text('文件读写状态:')
      Text(this.status).fontSize(16)
      Button('写入文件')
        .onClick(() => this.writeTestFile())
      Button('读取文件')
        .onClick(() => this.readTestFile())
    }
    .padding(20)
    .width('100%')
    .height('100%')
  }

  // 写入文件的完整错误处理示例
  writeTestFile(): void {
    this.status = '写入中...';
    try {
      // 沙箱路径:应用自带目录,确保有权限
      let context = getContext(this);
      let filePath: string = context.cacheDir + '/test.txt';

      // 创建文件(如果已存在则覆盖)
      let file: fileIo.File = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
      // 写入数据
      fileIo.writeSync(file.fd, new ArrayBuffer(10));
      fileIo.closeSync(file);
      this.status = '写入成功';
    } catch (error) {
      let bizError = error as BusinessError;
      // 针对不同错误码做分级处理
      switch (bizError.code) {
        case 13900004: // 存储空间不足
          this.status = '存储空间不足,请清理后再试';
          console.warn(`[FileError] 存储空间不足: ${bizError.message}`);
          break;
        case 13900003: // 权限错误
          this.status = '权限不足,请在设置中允许文件读写';
          console.error(`[FileError] 权限被拒绝: ${bizError.message}`);
          break;
        case 13900002: // 文件不存在(一般不会在这出现,因为用了CREATE模式)
        case 13900001: // 通用错误
        default:
          this.status = '写入失败,请稍后重试';
          console.error(`[FileError] 写入异常 code=${bizError.code}, message=${bizError.message}`);
      }
    }
  }

  // 读取文件的错误处理
  readTestFile(): void {
    this.status = '读取中...';
    try {
      let context = getContext(this);
      let filePath: string = context.cacheDir + '/test.txt';
      let file: fileIo.File = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
      let stat = fileIo.statSync(filePath);
      let buf = new ArrayBuffer(stat.size);
      fileIo.readSync(file.fd, buf);
      fileIo.closeSync(file);
      this.status = '读取成功';
    } catch (error) {
      let bizError = error as BusinessError;
      switch (bizError.code) {
        case 13900002: // 文件不存在
          this.status = '文件不存在,请先写入';
          break;
        default:
          this.status = '读取失败';
          console.error(`[FileError] 读取异常 code=${bizError.code}, message=${bizError.message}`);
      }
    }
  }
}

关键点说明:

  • fileIo.openSync() 时,建议显式指定 OpenMode.CREATE | OpenMode.WRITE_ONLY,避免文件已存在时直接报错。
  • BusinessErrormessage 字段内容是可读的,但生产环境中建议只对用户显示预定义中文提示,把原始 message 写到日志。
  • switch-case 不要写死所有错误码,留一个 default 兜底,因为后续 SDK 更新可能新增错误码。

常见问题 1:权限被拒绝时为什么没有弹窗?

现象:调用 writeSync() 时捕获到 13900003,但用户手机没有弹出文件权限授权弹窗。

原因:HarmonyOS 的文件权限分为系统级和沙箱级。沙箱路径下的文件操作不需要额外权限(比如 context.cacheDir),但如果访问外部存储(比如 /data/storage/el2/base 外的路径),就需要在 module.json5 中申请 ohos.permission.READ_MEDIAohos.permission.WRITE_MEDIA。没有申请权限时,错误码都是 13900003,但系统不会自动弹窗。

解法:先确认文件路径是否在沙箱范围内。如果需要访问外部存储,必须在 module.json5 中添加 requestPermissions,并调用 abilityAccessCtrl.requestPermissionsFromUser() 主动请求授权。不要在 try-catch 里期待系统帮你弹窗。

常见问题 2:磁盘空间足够,但写入仍然报 13900004

现象:设备存储还有十几个 G,但写入小于 1KB 的文件时抛出 13900004。

原因:HarmonyOS 的沙箱不仅有设备层面的空间限制,还有更细粒度的配额管理,尤其是应用缓存目录下的文件有上限。文档没说具体数值,但实测超过 100MB 时触发概率很高。

解法:写文件前先用 fileIo.statSync() 检查目录已用空间,超过配额限制时通知用户清理,或者改用 context.filesDir 替代 context.cacheDir,因为缓存目录的配额通常更严格。

最佳实践

  1. 分级处理错误码,不要全部 toast 给用户。磁盘满或权限问题用户能理解,但“通用错误 13900001”要么是路径非法要么是 SDK 缺陷,用户看到了也解决不了,反而增加困惑。

  2. 所有文件操作都放在 try-catch 里。不要觉得“写入缓存目录不会失败”,实际的失败场景多到你想不到:系统清理缓存、目录被删除、并发写入导致 FD 耗尽,每一种都要捕获并降级处理。

  3. 日志里永远带 code 和 message。只记录“文件写入失败”对排查问题毫无帮助,必须把 BusinessError.codeBusinessError.message 一起打出来。不同版本的 Core File Kit 对错误码的描述可能微调,只看数字更可靠。

FAQ

Q:为什么捕获到的异常对象不是 BusinessError 类型?
A:部分异步操作(比如 write() 的回调模式)抛出的异常可能是普通 Error,需要用 instanceof 判断,然后手动转换 code 和 message。建议统一用 error as BusinessError 强转,然后判断 code 是否在已知范围内。

Q:有的错误明明发生了,为什么没走到 catch 里?
A:检查是否用了异步函数但没有 awaitfileIo.openSync() 是同步的,但 fileIo.open() 是异步的,如果写成了 try { fileIo.open(...) } 但不 await,异常会在 Promise 里被吞掉。除非你显式 .catch()

Q:真机上同一个错误码,不同版本系统显示不一样?
A:遇到过。HarmonyOS 4.0 和 4.1 对“文件被占用”返回的都是 13900007,但 4.2 之后被拆分成了 13900007(被占用)和 13900008(正在被写入)。所以兜底逻辑一定要保留。

Logo

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

更多推荐