HarmonyOS鸿蒙Next中如何访问应用沙箱、选择系统文件和管理文件权限?

HarmonyOS鸿蒙Next中如何访问应用沙箱、选择系统文件和管理文件权限? 在HarmonyOS应用中如何进行文件读写操作?如何访问应用沙箱、选择系统文件和管理文件权限?

4 回复

优秀

更多关于HarmonyOS鸿蒙Next中如何访问应用沙箱、选择系统文件和管理文件权限?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


解决方案

1. 基础文件读写

import fs from '@ohos.file.fs'
import { BusinessError } from '@ohos.base'

@Entry
@Component
struct BasicFileOperation {
  private filesDir: string = getContext(this).filesDir

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

      Button('写入文本文件')
        .width('100%')
        .onClick(() => {
          this.writeTextFile()
        })

      Button('读取文本文件')
        .width('100%')
        .onClick(() => {
          this.readTextFile()
        })

      Button('追加内容')
        .width('100%')
        .onClick(() => {
          this.appendToFile()
        })

      Button('删除文件')
        .width('100%')
        .onClick(() => {
          this.deleteFile()
        })
    }
    .padding(16)
  }

  private writeTextFile() {
    try {
      const filePath = `${this.filesDir}/test.txt`
      const content = '这是测试内容\n当前时间: ' + new Date().toLocaleString()

      // 写入文件
      const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE)
      fs.writeSync(file.fd, content)
      fs.closeSync(file.fd)

      console.log('文件写入成功:', filePath)
    } catch (error) {
      const err = error as BusinessError
      console.error('文件写入失败:', err.message)
    }
  }

  private readTextFile() {
    try {
      const filePath = `${this.filesDir}/test.txt`

      // 检查文件是否存在
      if (!fs.accessSync(filePath)) {
        console.log('文件不存在')
        return
      }

      // 读取文件
      const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY)
      const buffer = new ArrayBuffer(1024)
      const readLen = fs.readSync(file.fd, buffer)
      fs.closeSync(file.fd)

      // 转换为字符串
      const content = String.fromCharCode(...new Uint8Array(buffer.slice(0, readLen)))
      console.log('文件内容:', content)
    } catch (error) {
      const err = error as BusinessError
      console.error('文件读取失败:', err.message)
    }
  }

  private appendToFile() {
    try {
      const filePath = `${this.filesDir}/test.txt`
      const appendContent = '\n追加的内容'

      const file = fs.openSync(filePath, fs.OpenMode.APPEND | fs.OpenMode.READ_WRITE)
      fs.writeSync(file.fd, appendContent)
      fs.closeSync(file.fd)

      console.log('内容追加成功')
    } catch (error) {
      const err = error as BusinessError
      console.error('追加失败:', err.message)
    }
  }

  private deleteFile() {
    try {
      const filePath = `${this.filesDir}/test.txt`
      fs.unlinkSync(filePath)
      console.log('文件删除成功')
    } catch (error) {
      console.error('文件删除失败:', error)
    }
  }
}

2. 目录操作

import fs from '@ohos.file.fs'

@Entry
@Component
struct DirectoryOperation {
  private filesDir: string = getContext(this).filesDir

  build() {
    Column({ space: 16 }) {
      Text('目录操作')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      Button('创建目录')
        .width('100%')
        .onClick(() => {
          this.createDirectory()
        })

      Button('列出目录内容')
        .width('100%')
        .onClick(() => {
          this.listDirectory()
        })

      Button('删除目录')
        .width('100%')
        .onClick(() => {
          this.deleteDirectory()
        })

      Button('复制文件')
        .width('100%')
        .onClick(() => {
          this.copyFile()
        })
    }
    .padding(16)
  }

  private createDirectory() {
    try {
      const dirPath = `${this.filesDir}/mydir`
      
      // 创建目录
      if (!fs.accessSync(dirPath)) {
        fs.mkdirSync(dirPath)
        console.log('目录创建成功:', dirPath)
      } else {
        console.log('目录已存在')
      }
    } catch (error) {
      console.error('创建目录失败:', error)
    }
  }

  private listDirectory() {
    try {
      // 列出目录中的所有文件和子目录
      const files = fs.listFileSync(this.filesDir)
      console.log('目录内容:')
      files.forEach(file => {
        console.log('  -', file)
      })
    } catch (error) {
      console.error('列出目录失败:', error)
    }
  }

  private deleteDirectory() {
    try {
      const dirPath = `${this.filesDir}/mydir`
      
      if (fs.accessSync(dirPath)) {
        fs.rmdirSync(dirPath)
        console.log('目录删除成功')
      }
    } catch (error) {
      console.error('删除目录失败:', error)
    }
  }

  private copyFile() {
    try {
      const sourcePath = `${this.filesDir}/test.txt`
      const destPath = `${this.filesDir}/test_copy.txt`

      if (fs.accessSync(sourcePath)) {
        fs.copyFileSync(sourcePath, destPath)
        console.log('文件复制成功')
      } else {
        console.log('源文件不存在')
      }
    } catch (error) {
      console.error('文件复制失败:', error)
    }
  }
}

3. 使用FilePicker选择文件

import picker from '@ohos.file.picker'
import fs from '@ohos.file.fs'
import { BusinessError } from '@ohos.base'

@Entry
@Component
struct FilePickerDemo {
  @State selectedFileUri: string = ''
  @State fileContent: string = ''

  build() {
    Column({ space: 16 }) {
      Text('文件选择器')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      Button('选择文档')
        .width('100%')
        .onClick(() => {
          this.selectDocument()
        })

      Button('选择图片')
        .width('100%')
        .onClick(() => {
          this.selectImage()
        })

      Button('保存文件')
        .width('100%')
        .onClick(() => {
          this.saveFile()
        })

      if (this.selectedFileUri) {
        Text('已选文件:')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
        Text(this.selectedFileUri)
          .fontSize(12)
          .fontColor('#666666')
      }

      if (this.fileContent) {
        Text('文件内容:')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
        Text(this.fileContent)
          .fontSize(12)
          .fontColor('#666666')
          .maxLines(5)
      }
    }
    .padding(16)
  }

  private async selectDocument() {
    try {
      const documentPicker = new picker.DocumentViewPicker()
      const result = await documentPicker.select({
        maxSelectNumber: 1,
        defaultFilePathUri: '',
        fileSuffixFilters: ['.txt', '.pdf', '.doc']
      })

      if (result && result.length > 0) {
        this.selectedFileUri = result[0]
        console.log('选择的文档:', this.selectedFileUri)
        
        // 读取文件内容
        await this.readSelectedFile(this.selectedFileUri)
      }
    } catch (error) {
      const err = error as BusinessError
      console.error('选择文档失败:', err.message)
    }
  }

  private async selectImage() {
    try {
      const photoPicker = new picker.PhotoViewPicker()
      const result = await photoPicker.select({
        MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE,
        maxSelectNumber: 1
      })

      if (result && result.photoUris.length > 0) {
        this.selectedFileUri = result.photoUris[0]
        console.log('选择的图片:', this.selectedFileUri)
      }
    } catch (error) {
      const err = error as BusinessError
      console.error('选择图片失败:', err.message)
    }
  }

  private async saveFile() {
    try {
      const documentPicker = new picker.DocumentViewPicker()
      const result = await documentPicker.save({
        newFileNames: ['output.txt'],
        defaultFilePathUri: '',
        fileSuffixChoices: ['.txt']
      })

      if (result && result.length > 0) {
        const saveUri = result[0]
        console.log('保存路径:', saveUri)

        // 写入内容到选择的位置
        const file = fs.openSync(saveUri, fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE)
        const content = '保存的内容\n时间: ' + new Date().toLocaleString()
        fs.writeSync(file.fd, content)
        fs.closeSync(file.fd)

        console.log('文件保存成功')
      }
    } catch (error) {
      const err = error as BusinessError
      console.error('保存文件失败:', err.message)
    }
  }

  private async readSelectedFile(uri: string) {
    try {
      const file = fs.openSync(uri, fs.OpenMode.READ_ONLY)
      const buffer = new ArrayBuffer(2048)
      const readLen = fs.readSync(file.fd, buffer)
      fs.closeSync(file.fd)

      this.fileContent = String.fromCharCode(...new Uint8Array(buffer.slice(0, readLen)))
    } catch (error) {
      console.error('读取文件失败:', error)
    }
  }
}

4. JSON文件读写

import fs from '@ohos.file.fs'

interface AppConfig {
  theme: string
  language: string
  notifications: boolean
  version: string
}

class ConfigManager {
  private configPath: string
  private defaultConfig: AppConfig = {
    theme: 'light',
    language: 'zh-CN',
    notifications: true,
    version: '1.0.0'
  }

  constructor() {
    this.configPath = `${getContext().filesDir}/config.json`
  }

  /**
   * 读取配置
   */
  readConfig(): AppConfig {
    try {
      if (!fs.accessSync(this.configPath)) {
        return this.defaultConfig
      }

      const file = fs.openSync(this.configPath, fs.OpenMode.READ_ONLY)
      const buffer = new ArrayBuffer(4096)
      const readLen = fs.readSync(file.fd, buffer)
      fs.closeSync(file.fd)

      const jsonStr = String.fromCharCode(...new Uint8Array(buffer.slice(0, readLen)))
      return JSON.parse(jsonStr) as AppConfig
    } catch (error) {
      console.error('读取配置失败:', error)
      return this.defaultConfig
    }
  }

  /**
   * 保存配置
   */
  saveConfig(config: AppConfig): boolean {
    try {
      const jsonStr = JSON.stringify(config, null, 2)
      const file = fs.openSync(
        this.configPath,
        fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY | fs.OpenMode.TRUNC
      )
      fs.writeSync(file.fd, jsonStr)
      fs.closeSync(file.fd)
      console.log('配置保存成功')
      return true
    } catch (error) {
      console.error('保存配置失败:', error)
      return false
    }
  }

  /**
   * 更新配置项
   */
  updateConfig(key: keyof AppConfig, value: string | boolean): boolean {
    const config = this.readConfig()
    config[key] = value as never
    return this.saveConfig(config)
  }
}

// 使用示例
@Entry
@Component
struct ConfigDemo {
  private configManager = new ConfigManager()
  @State config: AppConfig = this.configManager.readConfig()

  build() {
    Column({ space: 16 }) {
      Text('应用配置')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      Row() {
        Text('主题:')
          .layoutWeight(1)
        Text(this.config.theme)
          .fontColor('#666666')
      }
      .width('100%')

      Row() {
        Text('语言:')
          .layoutWeight(1)
        Text(this.config.language)
          .fontColor('#666666')
      }
      .width('100%')

      Row() {
        Text('通知:')
          .layoutWeight(1)
        Toggle({ type: ToggleType.Switch, isOn: this.config.notifications })
          .onChange((isOn) => {
            this.config.notifications = isOn
            this.configManager.saveConfig(this.config)
          })
      }
      .width('100%')

      Button('切换主题')
        .width('100%')
        .onClick(() => {
          this.config.theme = this.config.theme === 'light' ? 'dark' : 'light'
          this.configManager.saveConfig(this.config)
        })
    }
    .padding(16)
  }
}

5. 文件工具类

// utils/FileUtil.ets
import fs from '@ohos.file.fs'
import { BusinessError } from '@ohos.base'

export class FileUtil {
  /**
   * 读取文本文件
   */
  static readText(filePath: string): string | null {
    try {
      if (!fs.accessSync(filePath)) {
        return null
      }

      const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY)
      const stat = fs.statSync(filePath)
      const buffer = new ArrayBuffer(stat.size)
      fs.readSync(file.fd, buffer)
      fs.closeSync(file.fd)

      return String.fromCharCode(...new Uint8Array(buffer))
    } catch (error) {
      console.error('读取文件失败:', error)
      return null
    }
  }

  /**
   * 写入文本文件
   */
  static writeText(filePath: string, content: string): boolean {
    try {
      const file = fs.openSync(
        filePath,
        fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY | fs.OpenMode.TRUNC
      )
      fs.writeSync(file.fd, content)
      fs.closeSync(file.fd)
      return true
    } catch (error) {
      console.error('写入文件失败:', error)
      return false
    }
  }

  /**
   * 读取JSON文件
   */
  static readJson<T>(filePath: string): T | null {
    const content = this.readText(filePath)
    if (!content) {
      return null
    }

    try {
      return JSON.parse(content) as T
    } catch (error) {
      console.error('解析JSON失败:', error)
      return null
    }
  }

  /**
   * 写入JSON文件
   */
  static writeJson<T>(filePath: string, data: T): boolean {
    try {
      const jsonStr = JSON.stringify(data, null, 2)
      return this.writeText(filePath, jsonStr)
    } catch (error) {
      console.error('序列化JSON失败:', error)
      return false
    }
  }

  /**
   * 检查文件是否存在
   */
  static exists(filePath: string): boolean {
    try {
      return fs.accessSync(filePath)
    } catch {
      return false
    }
  }

  /**
   * 获取文件大小(字节)
   */
  static getSize(filePath: string): number {
    try {
      const stat = fs.statSync(filePath)
      return stat.size
    } catch {
      return 0
    }
  }

  /**
   * 删除文件
   */
  static delete(filePath: string): boolean {
    try {
      if (this.exists(filePath)) {
        fs.unlinkSync(filePath)
        return true
      }
      return false
    } catch (error) {
      console.error('删除文件失败:', error)
      return false
    }
  }

  /**
   * 复制文件
   */
  static copy(sourcePath: string, destPath: string): boolean {
    try {
      fs.copyFileSync(sourcePath, destPath)
      return true
    } catch (error) {
      console.error('复制文件失败:', error)
      return false
    }
  }

  /**
   * 移动文件
   */
  static move(sourcePath: string, destPath: string): boolean {
    try {
      fs.moveFileSync(sourcePath, destPath)
      return true
    } catch (error) {
      console.error('移动文件失败:', error)
      return false
    }
  }
}

关键要点

  1. 沙箱目录: 应用只能访问自己的沙箱目录(filesDir、cacheDir等)
  2. 文件描述符: 使用openSync打开文件,操作完成后必须closeSync
  3. FilePicker: 访问系统文件需要使用选择器,避免直接路径访问
  4. 权限管理: 访问公共目录需要申请相应权限
  5. 异常处理: 所有文件操作都应该捕获异常

最佳实践

  1. 资源释放: 及时关闭文件描述符,避免资源泄漏
  2. 错误处理: 检查文件是否存在,捕获IO异常
  3. 性能优化: 大文件使用流式读写,避免一次性加载
  4. 缓存管理: 临时文件使用cacheDir,会被系统自动清理
  5. 数据安全: 敏感数据加密存储

在HarmonyOS Next中,应用沙箱路径可通过context.filesDir获取。选择系统文件使用PhotoViewPickerDocumentViewPicker。文件权限需在module.json5中声明ohos.permission.READ_MEDIAohos.permission.WRITE_MEDIA,并在运行时通过requestPermissionsFromUser动态申请。

在HarmonyOS Next中,文件访问、沙箱管理和权限控制主要通过[@ohos](/user/ohos).file.fs(文件系统)、[@ohos](/user/ohos).file.picker(文件选择器)和[@ohos](/user/ohos).ability.accessCtrl(权限管理)等模块实现。以下是关键操作指南:

1. 应用沙箱访问

每个应用都有独立的沙箱目录,可通过context获取:

import UIAbility from '[@ohos](/user/ohos).app.ability.UIAbility';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage) {
    let context = this.context;
    let filesDir = context.filesDir; // 应用文件存储路径
    let cacheDir = context.cacheDir; // 缓存路径
  }
}

沙箱路径无需权限即可直接读写。

2. 系统文件选择

使用[@ohos](/user/ohos).file.picker选择用户文件:

import picker from '[@ohos](/user/ohos).file.picker';

// 选择单个文件
let photoPicker = new picker.PhotoViewPicker();
photoPicker.select()
  .then(photoSelectResult => {
    let uri = photoSelectResult[0]; // 获取文件uri
  })
  .catch(err => console.error('选择失败'));

选择器返回文件URI,后续通过fs.open()等接口操作。

3. 文件读写操作

使用[@ohos](/user/ohos).file.fs进行文件操作:

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

// 写入沙箱文件
let filePath = context.filesDir + '/test.txt';
fs.writeText(filePath, 'Hello HarmonyOS')
  .then(() => console.info('写入成功'))
  .catch(err => console.error('写入失败'));

// 读取URI文件(如选择器返回的URI)
let file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
let content = fs.readTextSync(file.fd);
fs.closeSync(file.fd);

4. 文件权限管理

  • 沙箱文件:无需声明权限。
  • 公共目录访问:需要声明对应权限并在动态权限弹窗中授权:
    // module.json5
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_MEDIA",
        "reason": "$string:reason_desc" // 需说明用途
      }
    ]
    
    // 动态申请
    import abilityAccessCtrl from '[@ohos](/user/ohos).ability.accessCtrl';
    
    let atManager = abilityAccessCtrl.createAtManager();
    atManager.requestPermissionsFromUser(this.context, ['ohos.permission.READ_MEDIA'])
      .then(result => {
        if (result.authResults[0] === 0) {
          // 授权成功
        }
      });
    

关键注意事项

  • 应用卸载时沙箱文件会自动清除,持久化数据需存至公共目录。
  • 操作非沙箱文件必须通过picker或声明权限,禁止直接路径访问。
  • 文件URI权限具有临时性,建议立即操作或使用persistentPermission接口申请持久化授权。

以上流程遵循HarmonyOS Next的安全设计,确保用户数据可控。

回到顶部