HarmonyOS鸿蒙Next 6 如何实现沙箱目录文件的复制、移动与删除(含进度监听)?

HarmonyOS鸿蒙Next 6 如何实现沙箱目录文件的复制、移动与删除(含进度监听)?

问题描述

在鸿蒙 6(API20)Stage 模型应用中,需要对沙箱目录(如filesDircacheDir)中的大文件(500MB+)进行复制、移动和删除操作,尝试使用fs模块的copyFilerename方法时出现:1. 无进度反馈导致 UI 卡顿;2. 大文件操作超时失败;3. 删除文件后残留空目录。如何实现高效、带进度监听的文件操作,且适配鸿蒙 6 沙箱权限规范?关键字:鸿蒙 6、沙箱目录、文件操作、复制 / 移动 / 删除、进度监听、Stage 模型、API20


更多关于HarmonyOS鸿蒙Next 6 如何实现沙箱目录文件的复制、移动与删除(含进度监听)?的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

一、原理解析

鸿蒙 6 沙箱目录的文件操作依赖@ohos.file.fs模块,核心特点:

  1. 沙箱目录(filesDir/cacheDir)为应用私有,无需额外权限,操作更安全;
  2. 大文件操作需在TaskPool(非 UI 线程)执行,避免阻塞 UI;
  3. 进度监听需通过fs.createStream创建读写流,分段处理文件并计算进度。

二、完整实现代码

1. 工具类封装(文件操作 + 进度监听)

import fs from '@ohos.file.fs';
import taskpool from '@ohos.taskpool';
import common from '@ohos.app.ability.common';
import { BusinessError } from '@ohos.base';

// 文件操作进度回调类型
type ProgressCallback = (progress: number) => void; // progress:0~100

export class SandboxFileUtil {
  private context: common.UIAbilityContext;

  constructor(context: common.UIAbilityContext) {
    this.context = context;
  }

  // 1. 复制文件(带进度监听)
  async copyFileWithProgress(
    srcPath: string, // 源文件路径(沙箱内)
    destPath: string, // 目标路径(沙箱内)
    progressCallback?: ProgressCallback
  ): Promise<boolean> {
    try {
      // 检查源文件是否存在
      if (!await fs.access(srcPath)) throw new Error('源文件不存在');

      // 获取文件大小(用于计算进度)
      const fileStats = await fs.stat(srcPath);
      const fileSize = fileStats.size;
      if (fileSize === 0) {
        await fs.copyFile(srcPath, destPath);
        progressCallback?.(100);
        return true;
      }

      // 提交到TaskPool执行(非UI线程)
      const result = await taskpool.execute(
        this.doCopyFile,
        srcPath,
        destPath,
        fileSize,
        progressCallback
      );
      return result;
    } catch (err) {
      console.error(`复制文件失败:${(err as BusinessError).message}`);
      return false;
    }
  }

  // 2. 移动文件(本质:复制+删除源文件)
  async moveFileWithProgress(
    srcPath: string,
    destPath: string,
    progressCallback?: ProgressCallback
  ): Promise<boolean> {
    const copySuccess = await this.copyFileWithProgress(srcPath, destPath, progressCallback);
    if (copySuccess) {
      await this.deleteFile(srcPath);
      return true;
    }
    return false;
  }

  // 3. 删除文件(支持文件/目录)
  async deleteFile(path: string): Promise<boolean> {
    try {
      const stats = await fs.stat(path);
      if (stats.isDirectory()) {
        // 删除目录(递归删除子文件)
        await fs.rmdir(path, { recursive: true });
      } else {
        // 删除文件
        await fs.unlink(path);
      }
      return true;
    } catch (err) {
      console.error(`删除文件失败:${(err as BusinessError).message}`);
      return false;
    }
  }

  // TaskPool执行的复制逻辑(需@Concurrent装饰器)
  @Concurrent
  private async doCopyFile(
    srcPath: string,
    destPath: string,
    fileSize: number,
    progressCallback?: ProgressCallback
  ): Promise<boolean> {
    const readStream = fs.createStream(srcPath, 'r');
    const writeStream = fs.createStream(destPath, 'w');
    let processedSize = 0;

    try {
      // 分段读取文件
      for await (const chunk of readStream) {
        await writeStream.write(chunk);
        processedSize += chunk.byteLength;
        // 计算进度(0~100)
        const progress = Math.round((processedSize / fileSize) * 100);
        progressCallback?.(progress);
      }
      await writeStream.close();
      await readStream.close();
      return true;
    } catch (err) {
      // 失败时删除目标文件
      if (await fs.access(destPath)) {
        await fs.unlink(destPath);
      }
      console.error(`TaskPool复制失败:${(err as BusinessError).message}`);
      return false;
    }
  }
}

2. 组件中使用示例

import { SandboxFileUtil } from '../utils/SandboxFileUtil';
import router from '@ohos.router';

@Entry
@Component
struct FileOperationDemo {
  private context = getContext(this) as common.UIAbilityContext;
  private fileUtil = new SandboxFileUtil(this.context);
  @State copyProgress: number = 0; // 复制进度
  @State isCopying: boolean = false;

  build() {
    Column({ space: 20 }) {
      Text('沙箱文件操作示例')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      Progress({
        value: this.copyProgress,
        total: 100,
        type: ProgressType.Linear
      })
        .width('80%')
        .text(`${this.copyProgress}%`)

      Button('复制大文件')
        .onClick(async () => {
          if (this.isCopying) return;
          this.isCopying = true;
          this.copyProgress = 0;

          // 源文件路径(沙箱filesDir下的test.zip)
          const srcPath = `${this.context.filesDir}/test.zip`;
          // 目标文件路径
          const destPath = `${this.context.filesDir}/test_copy.zip`;

          // 执行复制(带进度监听)
          const success = await this.fileUtil.copyFileWithProgress(
            srcPath,
            destPath,
            (progress) => {
              // 更新进度(UI线程)
              this.copyProgress = progress;
            }
          );

          this.isCopying = false;
          if (success) {
            promptAction.showToast({ message: '复制成功' });
          } else {
            promptAction.showToast({ message: '复制失败' });
          }
        })
        .width('80%')
        .enabled(!this.isCopying)
    }
    .width('100%')
    .padding(20)
    .justifyContent(FlexAlign.Center)
  }
}

三、避坑点

  1. 沙箱路径规范:必须通过context.filesDir/context.cacheDir获取路径,不可硬编码;
  2. 大文件处理:超过 100MB 的文件必须用流(createStream)分段处理,避免内存溢出;
  3. TaskPool 限制:@Concurrent装饰的方法不可直接访问组件状态,需通过回调更新 UI;
  4. 权限问题:沙箱内文件操作无需额外权限,若操作公共目录需申请ohos.permission.READ_EXTERNAL_STORAGE
  5. 异常处理:复制失败时需删除目标文件,避免残留无效文件。

更多关于HarmonyOS鸿蒙Next 6 如何实现沙箱目录文件的复制、移动与删除(含进度监听)?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next 6中,可通过@ohos.file.fs@ohos.file.fileAccess模块操作沙箱目录文件。使用fs.copyFilefs.moveFile进行复制或移动,使用fs.unlink删除文件。进度监听需结合fs.createStreamfs.Streamon('progress')事件实现,通过计算已传输字节与总字节的比例获取进度。

在HarmonyOS Next(API 20)Stage模型中,处理沙箱目录大文件操作并实现进度监听,核心在于使用fs模块的流式API和异步任务管理。以下是具体方案:

1. 复制文件(带进度监听)

使用fs.createStream创建读写流,分块传输以实现进度反馈。

import fs from '[@ohos](/user/ohos).file.fs';
import common from '[@ohos](/user/ohos).app.ability.common';

async function copyFileWithProgress(srcPath: string, destPath: string, context: common.UIAbilityContext): Promise<void> {
  const srcFile = fs.openSync(srcPath, fs.OpenMode.READ_ONLY);
  const destFile = fs.openSync(destPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
  
  const stats = fs.statSync(srcPath);
  const totalSize = stats.size;
  let copiedSize = 0;
  const bufferSize = 1024 * 1024; // 1MB分块
  const buffer = new ArrayBuffer(bufferSize);

  while (copiedSize < totalSize) {
    const readSize = fs.readSync(srcFile.fd, buffer, { offset: copiedSize });
    fs.writeSync(destFile.fd, buffer, { offset: copiedSize });
    copiedSize += readSize;
    
    // 计算进度(主线程需通过TaskPool或Worker更新UI)
    const progress = Math.floor((copiedSize / totalSize) * 100);
    // 通过Emitter或Context状态管理传递进度值
  }
  
  fs.closeSync(srcFile);
  fs.closeSync(destFile);
}

2. 移动文件

移动操作优先使用fs.rename(原子操作),失败时回退到“复制+删除”:

async function moveFileWithProgress(srcPath: string, destPath: string): Promise<void> {
  try {
    fs.renameSync(srcPath, destPath); // 同分区快速移动
  } catch (e) {
    // 跨分区时复制后删除
    await copyFileWithProgress(srcPath, destPath);
    fs.unlinkSync(srcPath); // 同步删除原文件
  }
}

3. 删除文件及目录

使用递归删除确保清理空目录:

function deleteFileOrDir(path: string): void {
  const stats = fs.statSync(path);
  if (stats.isDirectory()) {
    const files = fs.listFileSync(path);
    files.forEach(file => deleteFileOrDir(`${path}/${file}`));
    fs.rmdirSync(path); // 删除空目录
  } else {
    fs.unlinkSync(path);
  }
}

4. 关键优化点

  • 进度通知:在分块循环中通过TaskPoolWorker将进度抛到主线程,避免阻塞UI:
    import taskpool from '[@ohos](/user/ohos).taskpool';
    // 将复制任务放入TaskPool执行,通过PostMessage更新进度
    
  • 超时处理:为长时间操作添加超时控制,使用Promise.race包装异步操作。
  • 权限适配:仅能操作应用沙箱路径(context.filesDircontext.cacheDir),无需申请额外权限。

5. 注意事项

  • 大文件操作必须异步化,防止阻塞ArkTS UI线程。
  • 流式读写时需根据设备性能调整bufferSize(建议512KB-2MB)。
  • 删除目录前需递归清空内容,否则rmdirSync会失败。

此方案通过流式分块、异步任务和递归删除,解决了进度监听、超时和目录残留问题,符合HarmonyOS Next沙箱规范。

回到顶部