HarmonyOS鸿蒙Next中在导出文件时,如果空间不足怎么停止导出?

HarmonyOS鸿蒙Next中在导出文件时,如果空间不足怎么停止导出? 【问题描述】:从沙箱目录通过文件保存控件导出文件时,在用户选择保存路径后,系统会生成一个临时文件,此时如果检测到剩余空间不足,想要停止导出怎么办??因为系统生成的文件貌似没有权限删除,如果此时停止导出操作,会在原目录存在一个0字节的文件,但是在用户选择目录前,好像又无法检测剩余空间,因为还不知道用户要保存到哪里

12 回复

尊敬的开发者,您好,

  1. 保存到我的云盘时,实际还是存储在手机本地,所以可以使用statfs.getFreeSize判断本地剩余空间。

  2. 保存到外置存储设备,当前无接口供三方应用获取外置存储设备的剩余空间,如果您希望提供相关功能,请提供以下信息:

请问您是在什么样的业务场景中使用该能力,交互流程是怎样的,方便说明能力不满足可能带来的影响:什么时间用到?是否高频?有无三方库可以做到?若提供该能力,是否会造成大工作量返工?请您注意提供的内容不要包含您或第三方的非公开信息,如给您带来不便,敬请谅解。

更多关于HarmonyOS鸿蒙Next中在导出文件时,如果空间不足怎么停止导出?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


  1. 生成那个0字节的文件应该是保存失败,在复制前创建的。
  2. 如果空间不足,可以复制保存前检查下,用statfs.getFreeSizeSync
  3. 如果复制保存失败,想干掉那个创建的文件,用fileManagerService.deleteToTrash

参考:

saveSandboxFile(srcPath: string, name: string) {
    const documentSaveOptions = new picker.DocumentSaveOptions();
    documentSaveOptions.newFileNames = [name];
    let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    const documentViewPicker = new picker.DocumentViewPicker(context);
    documentViewPicker.save(documentSaveOptions).then((documentSaveResult: string[]) => {
      this.copyFile(srcPath, documentSaveResult[0])
    }).catch((err: BusinessError) => {
      console.error(`Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
    });
  }

  copyFile(srcPath: string, targetFile: string): boolean {
    try {
      let freeSize = statfs.getFreeSizeSync(targetFile);
      if (freeSize < fileIo.statSync(srcPath).size) {
        return false;
      }
      let srcFile = fileIo.openSync(srcPath);
      let destFile = fileIo.openSync(targetFile, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
      fileIo.copyFile(srcFile.fd, destFile.fd).then(() => {
        //TODO 复制成功
      }).catch(() => {
        fileManagerService.deleteToTrash(targetFile).catch(() => {
          // TODO: Implement error handling.
        });
        return false;
      }).finally(() => {
        fileIo.closeSync(srcFile.fd);
        fileIo.closeSync(destFile.fd);
      })
    } catch (error) {
      return false;
    }
    return true;
  }

开发者您好,文件管理服务File Manager Service Kit提供了deleteToTrash删除文件到回收站接口,这边如果在调用保存接口因为内存不足产生了0字节的文件,可以判断不满足后直接删除该文件路径,看下这个方式是否可以满足您的需求?

参考代码:

import { picker, storageStatistics } from '@kit.CoreFileKit';
import { fileManagerService } from '@kit.FileManagerServiceKit';
import fs from '@ohos.file.fs';

@Entry
@Component
struct Index {
  build() {
    Column() {
      Button('保存文件到公共目录模拟内存满的情况下并删除').onClick(async () => {
        const documentSaveOptions = new picker.DocumentSaveOptions(); // 创建文件管理器选项实例
        documentSaveOptions.newFileNames = ["test.db"]; // 保存文件名(可选)
        documentSaveOptions.fileSuffixChoices = ['']; // 保存文件类型(可选)
        let uris: Array<string> = [];
        const documentViewPicker = new picker.DocumentViewPicker(); // 创建文件选择器实例
        documentViewPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
          uris = documentSaveResult;
          let context = this.getUIContext().getHostContext();
          let fileName: string = 'xxxx'; //
          //判断剩余存储空间大小
          try {
            let number = storageStatistics.getFreeSizeSync();
            if (number > 0) {
              fileManagerService.deleteToTrash(uris[0]);
            } else {
              context?.resourceManager.getRawFileContent(fileName, (error, value) => {
                if (error) {
                  console.log(`rawfile文件保存到用户目录 error Code: ${error.code} error Msg: ${error.message}`);
                  return;
                }
                // 这里读取到文件内容可以进行下一步操作
                let myBuffer: ArrayBufferLike = value.buffer;
                console.info('documentViewPicker.save to file succeed and uris are:' + uris);
                let uri = uris[0];
                let file = fs.openSync(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
                let writeLen2 = fs.writeSync(file.fd, myBuffer);
                fs.closeSync(file);
              });
            }
            console.info(`getFreeSizeSync successfully, number is ${number}`);
          } catch (err) {
            let error: BusinessError = err as BusinessError;
            console.error(`getFreeSizeSync failed with error, code is ${error.code}, message is ${error.message}`);
          }

        }).catch((err: BusinessError) => {
          console.error(`Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
        })
      })
    }
    .height('100%')
    .width('100%')
  }
}

现在用的就是这个方案,一般可以检测手机内置空间,但是如果在用户选择目录前判断的话,如果用户选择的保存到云盘或者外接存储就没用了,所以想看看又没有新的优化方案

这个场景下,比较稳妥的处理方式是:不要等系统创建目标文件后再判断空间,而是导出前先预估文件大小。

因为 FilePicker 返回用户选定 URI 后,系统确实可能已经创建目标文件。

如果这时再发现空间不足直接中断,往往会留下 0 字节占位文件,而且应用侧通常没有权限直接删掉这个由系统创建的目标文件。

建议这样处理:

方案1(推荐):导出前预估大小

如果能提前拿到导出文件大小(或压缩后预估值),在拉起保存前先判断:

  • 源文件大小
  • 编码/压缩后的预计大小
  • 预留一定安全余量(比如 +10%)

空间不足直接提示,不进入保存流程。


方案2:分块写入 + 捕获写入异常

执行写入时监听异常:

try {
  // write
} catch (err) {
  // ENOSPC
}

捕获到空间不足(ENOSPC / no space left)后:

  • 立即停止写入
  • 提示“存储空间不足,导出失败”

但残留 0 字节文件通常只能接受,交给用户手动删除。


方案3:先导出到应用沙箱,再让用户另存

流程:

沙箱生成完整文件

→ 校验大小成功

→ 再调用保存/分享

这样至少能确保不会在目标目录留下空文件。

一句话:

HarmonyOS 当前这个流程下,用户选路径后系统创建的目标文件通常不能由应用主动删除;要避免 0 字节文件,核心是“导出前预估空间”,而不是失败后回滚删除。

用户通过保存控件选择路径前,应用确实不知道最终目标卷和目录,所以很难提前精确判断“目标位置剩余空间”。更稳妥的做法是把流程拆成两段:先在应用沙箱内生成或预估待导出文件大小,再拉起保存控件让用户选择目标位置,拿到 URI 后按块写入。

空间判断上,可以用 file.statvfs/statfs 类能力检查你可访问路径的剩余空间;但对用户选择的外部 URI,不建议按普通路径思路处理,也不要尝试删除系统/文件管理器生成的临时 0 字节文件,因为那不是应用直接拥有的普通沙箱文件。

实际实现建议:导出前先提示预计文件大小;写入时分块写并捕获写入失败/空间不足错误;失败后给用户明确提示“目标位置空间不足,请更换目录或清理空间后重试”。如果业务强依赖“失败不留下 0 字节文件”,需要看 FilePicker/文件管理器当前实现是否支持事务式保存;应用侧通常只能避免继续写入,不能保证删除用户目标目录里的占位文件。

能不能考虑,在目录检测后尝试删除或者引导用户手动清理呢?

比如拿到目录URI后。立即查该文件所在目录剩余空间。

如果空间不足:

尝试 fs.unlink(uri),权限足够应该可删。

如果删不掉:可以弹窗提示 “空间不足,已生成空文件,请手动删除”。

类似这样

async function saveWithPicker() {
  const savePicker = new picker.SaveFilePicker();
  const result = await savePicker.save({
    saveOptions: [{ fileName: 'test.txt', mimeType: 'text/plain' }]
  });
  const fileUri = result.uriList[0];

  // 查所在目录空间
  const dirUri = fileUri.substring(0, fileUri.lastIndexOf('/'));
  const freeBytes = await statvfs.getFreeSize(dirUri);
  const needSize = 1024 * 1024 * 10;
  if (freeBytes < needSize) {
    // 尝试删除占位文件
    await fs.unlink(fileUri).catch((e: BusinessError) => {
      // 删不掉:提示用户
      AlertDialog.show({ message: '空间不足,导出失败。已生成空文件,请手动删除:' + fileUri });
    });
    return;
  }
  // 空间足够:继续写入...
}

建议导出前先检测一下可用空间再导出吧

通过 应用及文件系统空间统计 去获取剩余空间存储

1、获取当前文件大小

2、获取剩余空间大小

3、在用户选择目录前比对

let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
let path = context.filesDir;
statfs.getFreeSize(path, (err: BusinessError, number: number) => {
  if (err) {
    console.error(`Invoke getFreeSize failed, code is ${err.code}, message is ${err.message}`);
  } else {
    console.info(`Invoke getFreeSize succeeded, size is ${number}`);
  }
});

在HarmonyOS NEXT中,导出文件前可使用FileAccessHelpergetFreeSize()接口检查目标路径剩余空间。若空间不足,主动中断写入流程并返回错误码(如ERROR_NO_SPACE)。也可在写入时监听IOException(如DiskFullException),捕获后立即结束导出操作。

当使用SaveButton(文件保存控件)导出时,用户选择路径后系统会预创建一个0字节占位文件并返回URI。此时可在onCreate回调中先获取该文件描述符,通过fs.fstatvfs(fd)获取目标存储卷剩余空间,与待导出文件大小比较。若空间不足,不执行写入操作并直接关闭描述符,同时可提示用户空间不足。此时占位文件无法删除(应用无外部存储删除权限),会残留0字节文件,但这是系统设计的正常行为,无法避免。提前检查空间只能在拿到URI后进行,因为选择路径前无法预知目标卷。

回到顶部