HarmonyOS 鸿蒙Next开发者技术支持-文件操作指南

HarmonyOS 鸿蒙Next开发者技术支持-文件操作指南

问题场景

在鸿蒙应用开发中,开发者经常需要对文件系统进行各种操作,包括但不限于:

  • 创建、删除、重命名文件夹
  • 遍历文件夹内容
  • 查询文件夹属性信息
  • 跨应用文件夹访问
  • 管理应用沙箱内外部文件夹

具体表现

  1. API分散不统一:文件夹相关API分布在多个模块中(@ohos.file.fs, @ohos.file.fileuri等)
  2. 权限配置复杂:不同文件夹操作需要不同的权限声明
  3. 路径处理混乱:沙箱路径、公共路径、外部路径混合使用容易出错
  4. 异步操作回调嵌套:深层次的回调嵌套导致代码难以维护
  5. 兼容性问题:不同设备、不同版本的API差异

优化方向

  1. 统一封装:提供简洁一致的API接口
  2. 路径标准化:统一处理各种路径格式
  3. 权限管理:简化权限申请和检查逻辑
  4. 错误处理:统一错误码转换和异常抛出
  5. 异步优化:提供Promise和async/await支持

方案一:创建文件夹操作工具类

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

/**
 * 鸿蒙文件夹操作管理器
 */
export class FileDirectoryManager {
  private context: common.UIAbilityContext;

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

  /**
   * 创建文件夹
   * @param dirPath 文件夹路径
   * @param recursive 是否递归创建父目录
   */
  async createDirectory(dirPath: string, recursive: boolean = true): Promise<void> {
    try {
      // 标准化路径
      const normalizedPath = this.normalizePath(dirPath);

      // 检查文件夹是否已存在
      const isExist = await this.checkDirectoryExists(normalizedPath);
      if (isExist) {
        console.info(`Directory already exists: ${normalizedPath}`);
        return;
      }

      // 创建文件夹
      await fs.mkdir(normalizedPath, recursive);
      console.info(`Directory created successfully: ${normalizedPath}`);
    } catch (error) {
      console.error(`Failed to create directory: ${dirPath}`, error);
      throw this.wrapFileError(error, 'createDirectory');
    }
  }

  /**
   * 删除文件夹
   * @param dirPath 文件夹路径
   * @param recursive 是否递归删除
   */
  async deleteDirectory(dirPath: string, recursive: boolean = true): Promise<void> {
    try {
      const normalizedPath = this.normalizePath(dirPath);
      await fs.rmdir(normalizedPath, recursive);
      console.info(`Directory deleted successfully: ${normalizedPath}`);
    } catch (error) {
      console.error(`Failed to delete directory: ${dirPath}`, error);
      throw this.wrapFileError(error, 'deleteDirectory');
    }
  }

  /**
   * 重命名文件夹
   * @param oldPath 原路径
   * @param newPath 新路径
   */
  async renameDirectory(oldPath: string, newPath: string): Promise<void> {
    try {
      const normalizedOldPath = this.normalizePath(oldPath);
      const normalizedNewPath = this.normalizePath(newPath);

      await fs.rename(normalizedOldPath, normalizedNewPath);
      console.info(`Directory renamed from ${oldPath} to ${newPath}`);
    } catch (error) {
      console.error(`Failed to rename directory: ${oldPath} -> ${newPath}`, error);
      throw this.wrapFileError(error, 'renameDirectory');
    }
  }

  /**
   * 列出文件夹内容
   * @param dirPath 文件夹路径
   */
  async listDirectory(dirPath: string): Promise<string[]> {
    try {
      const normalizedPath = this.normalizePath(dirPath);
      const dir = await fs.opendir(normalizedPath);
      const files: string[] = [];

      let isDone = false;
      while (!isDone) {
        const result = await dir.read();
        if (result && result.name) {
          files.push(result.name);
        } else {
          isDone = true;
        }
      }

      await dir.close();
      return files;
    } catch (error) {
      console.error(`Failed to list directory: ${dirPath}`, error);
      throw this.wrapFileError(error, 'listDirectory');
    }
  }

  /**
   * 获取文件夹信息
   * @param dirPath 文件夹路径
   */
  async getDirectoryInfo(dirPath: string): Promise<fs.FileInfo> {
    try {
      const normalizedPath = this.normalizePath(dirPath);
      const stat = await fs.stat(normalizedPath);
      return stat;
    } catch (error) {
      console.error(`Failed to get directory info: ${dirPath}`, error);
      throw this.wrapFileError(error, 'getDirectoryInfo');
    }
  }

  /**
   * 检查文件夹是否存在
   */
  async checkDirectoryExists(dirPath: string): Promise<boolean> {
    try {
      const normalizedPath = this.normalizePath(dirPath);
      await fs.access(normalizedPath);
      return true;
    } catch {
      return false;
    }
  }

  /**
   * 复制文件夹
   * @param sourcePath 源路径
   * @param targetPath 目标路径
   */
  async copyDirectory(sourcePath: string, targetPath: string): Promise<void> {
    try {
      const normalizedSource = this.normalizePath(sourcePath);
      const normalizedTarget = this.normalizePath(targetPath);

      // 创建目标文件夹
      await this.createDirectory(normalizedTarget);

      // 获取源文件夹内容
      const files = await this.listDirectory(normalizedSource);

      // 复制每个文件/子文件夹
      for (const file of files) {
        const sourceFile = `${normalizedSource}/${file}`;
        const targetFile = `${normalizedTarget}/${file}`;

        const stat = await fs.stat(sourceFile);
        if (stat.isDirectory()) {
          // 递归复制子文件夹
          await this.copyDirectory(sourceFile, targetFile);
        } else {
          // 复制文件
          await fs.copyFile(sourceFile, targetFile);
        }
      }
    } catch (error) {
      console.error(`Failed to copy directory: ${sourcePath} -> ${targetPath}`, error);
      throw this.wrapFileError(error, 'copyDirectory');
    }
  }

  /**
   * 获取应用沙箱目录
   */
  getSandboxDir(type: 'files' | 'cache' | 'temp' | 'preferences' = 'files'): string {
    const dirs = this.context.filesDir;
    switch (type) {
      case 'cache':
        return this.context.cacheDir;
      case 'temp':
        return this.context.tempDir;
      case 'preferences':
        return this.context.preferencesDir;
      case 'files':
      default:
        return dirs;
    }
  }

  /**
   * 标准化路径
   */
  private normalizePath(path: string): string {
    // 处理相对路径
    if (path.startsWith('./') || path.startsWith('../')) {
      return this.getSandboxDir('files') + '/' + path;
    }

    // 处理沙箱路径简写
    if (path.startsWith('sandbox://')) {
      const relativePath = path.replace('sandbox://', '');
      return this.getSandboxDir('files') + '/' + relativePath;
    }

    return path;
  }

  /**
   * 包装文件错误
   */
  private wrapFileError(error: any, operation: string): Error {
    const errorCode = error.code || -1;
    const errorMessage = this.getErrorMessage(errorCode, operation);
    return new Error(`${operation} failed: ${errorMessage} (Code: ${errorCode})`);
  }

  /**
   * 获取错误信息
   */
  private getErrorMessage(code: number, operation: string): string {
    const errorMap: Record<number, string> = {
      13900001: '参数检查失败',
      13900002: '路径超出最大长度限制',
      13900003: '路径中不允许出现特殊字符',
      13900004: '文件或目录不存在',
      13900005: '没有访问权限',
      13900006: '文件或目录已存在',
      13900007: '磁盘空间不足',
      13900008: '输入输出错误',
      13900009: '网络错误',
      13900010: '不支持的操作',
    };

    return errorMap[code] || `未知错误,操作: ${operation}`;
  }
}

方案二:权限配置模板

// module.json5
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_MEDIA",
        "reason": "需要读取媒体文件",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.WRITE_MEDIA",
        "reason": "需要保存文件到媒体目录",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.MEDIA_LOCATION",
        "reason": "需要访问媒体文件的位置信息",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      }
    ]
  }
}

方案三:使用示例

// 使用示例
import { FileDirectoryManager } from './FileDirectoryManager';
import common from '[@ohos](/user/ohos).app.ability.common';

class DirectoryExample {
  private fileManager: FileDirectoryManager;

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

  // 示例1:创建应用数据文件夹
  async setupAppDirectories() {
    try {
      // 创建主数据目录
      await this.fileManager.createDirectory('data');

      // 创建子目录
      await this.fileManager.createDirectory('data/images');
      await this.fileManager.createDirectory('data/documents');
      await this.fileManager.createDirectory('data/cache');

      console.info('App directories created successfully');
    } catch (error) {
      console.error('Failed to setup app directories', error);
    }
  }

  // 示例2:清理缓存文件夹
  async clearCache() {
    try {
      const cacheDir = this.fileManager.getSandboxDir('cache');
      const files = await this.fileManager.listDirectory(cacheDir);

      for (const file of files) {
        const filePath = `${cacheDir}/${file}`;
        const stat = await this.fileManager.getDirectoryInfo(filePath);

        if (stat.isDirectory()) {
          await this.fileManager.deleteDirectory(filePath);
        } else {
          // 如果是文件,使用fs.unlink删除
          // 这里可以扩展FileDirectoryManager支持文件删除
        }
      }

      console.info('Cache cleared successfully');
    } catch (error) {
      console.error('Failed to clear cache', error);
    }
  }

  // 示例3:备份数据
  async backupData() {
    try {
      const sourceDir = 'sandbox://data';
      const backupDir = `backup_${new Date().getTime()}`;

      await this.fileManager.createDirectory(backupDir);
      await this.fileManager.copyDirectory(sourceDir, backupDir);

      console.info(`Data backed up to: ${backupDir}`);
    } catch (error) {
      console.error('Failed to backup data', error);
    }
  }
}

方案四:路径处理工具

// PathUtils.ts
export class PathUtils {
  /**
   * 获取路径的目录部分
   */
  static getDirectory(path: string): string {
    const lastSlashIndex = path.lastIndexOf('/');
    if (lastSlashIndex === -1) return '.';
    return path.substring(0, lastSlashIndex);
  }

  /**
   * 获取文件名
   */
  static getFileName(path: string): string {
    const lastSlashIndex = path.lastIndexOf('/');
    if (lastSlashIndex === -1) return path;
    return path.substring(lastSlashIndex + 1);
  }

  /**
   * 获取文件扩展名
   */
  static getFileExtension(path: string): string {
    const fileName = this.getFileName(path);
    const lastDotIndex = fileName.lastIndexOf('.');
    if (lastDotIndex === -1) return '';
    return fileName.substring(lastDotIndex + 1);
  }

  /**
   * 连接路径
   */
  static join(...paths: string[]): string {
    return paths.join('/').replace(/\/+/g, '/');
  }

  /**
   * 检查是否是绝对路径
   */
  static isAbsolutePath(path: string): boolean {
    return path.startsWith('/') ||
           path.startsWith('bundle://') ||
           path.startsWith('internal://');
  }
}

结果展示:开发效率提升或为后续同类问题提供参考

质量改善

  1. 统一性:所有文件夹操作使用统一接口
  2. 可读性:方法命名清晰,参数明确
  3. 可扩展性:易于添加新的文件夹操作方法
  4. 错误处理:统一的错误处理机制,便于问题定位

复用价值

  1. 跨项目使用:工具类可直接复制到其他鸿蒙项目
  2. 团队规范:建立团队内文件夹操作的最佳实践
  3. 新人上手:新开发者可快速掌握文件夹操作
  4. 文档补充:为官方文档提供实际使用案例参考

更多关于HarmonyOS 鸿蒙Next开发者技术支持-文件操作指南的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

鸿蒙Next文件操作

鸿蒙Next文件操作使用ArkTS语言,通过@ohos.file.fs等API实现。

主要接口

  • fs.access:检查文件存在性
  • fs.copyFile:复制文件
  • fs.mkdir:创建目录
  • fs.open:打开文件流
  • fs.read:读取数据
  • fs.write:写入数据
  • fs.list:获取目录列表

使用说明

  1. 文件路径:需使用应用沙箱路径,通过Context获取。
  2. 权限声明:操作需声明ohos.permission.READ_USER_STORAGEWRITE_USER_STORAGE权限。

更多关于HarmonyOS 鸿蒙Next开发者技术支持-文件操作指南的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这个文件操作工具类封装得非常全面,针对HarmonyOS Next开发中文件API分散、路径混乱等痛点提供了很好的解决方案。

核心优势在于:

  1. 统一异步接口:所有方法都使用async/await,避免了回调嵌套问题。
  2. 路径标准化:通过normalizePath方法统一处理沙箱路径、相对路径等,简化了开发者的路径处理逻辑。
  3. 完整的错误处理:wrapFileError方法将系统错误码转换为可读的中文错误信息,便于调试。
  4. 沙箱目录管理:getSandboxDir方法提供了便捷的沙箱目录访问方式。

需要注意的几个关键点:

  • 权限配置需要根据实际使用场景调整,特别是访问公共目录时需要申请相应权限。
  • copyDirectory方法实现了递归复制,但需要注意大文件夹复制时的性能问题。
  • 对于文件操作,可以进一步扩展该类,增加文件读写、移动等常用操作。

这个方案确实能显著提升开发效率,特别是在需要频繁操作文件系统的应用中。路径处理工具类的补充也让整个方案更加完整。

回到顶部