鸿蒙沙箱机制

350 阅读3分钟

应用沙箱是一种以安全防护为目的的隔离机制,避免数据受到恶意路径穿越访问。在这种沙箱的保护机制下,应用可见的目录范围即为“应用沙箱目录”。

  • 对于每个应用,系统会在内部存储空间映射出一个专属的“应用沙箱目录”,它是“应用文件目录”与一部分系统文件(应用运行必需的少量系统文件)所在的目录组成的集合。
  • 应用沙箱限制了应用可见的数据的最小范围。在“应用沙箱目录”中,应用仅能看到自己的应用文件以及少量的系统文件(应用运行必需的少量系统文件)。因此,本应用的文件也不为其他应用可见,从而保护了应用文件的安全。
  • 应用可以在“应用文件目录”下保存和处理自己的应用文件;系统文件及其目录对于应用是只读的;而应用若需访问用户文件,则需要通过特定API同时经过用户的相应授权才能进行。

image.png

应用文件目录与应用文件路径

如前文所述,“应用沙箱目录”内分为两类:应用文件目录和系统文件目录。

系统文件目录对应用的可见范围由系统预置,开发者无需关注。

在此主要介绍应用文件目录,如下图所示。应用文件目录下某个文件或某个具体目录的路径称为应用文件路径。应用文件目录下的各个文件路径,具备不同的属性和特征。

应用文件目录结构图

image.png

  • 获取沙箱目录

    getContext().cacheDir
    getContext().fileDir
    getContext().tempDir
    
  • 文件操作

    // harmonyOS提供文件操作的API,相当于nodejs的中的fs操作
    // 注意: 在API9中 使用fs
    // 在API11和API12中官方又提供了 fileIO的基础方法,用法和fs基本一致,实际导出的就是API9中的fs
    
    open 打开文件
    close 关闭文件
    write写入文件
    copy 复制文件
    unlink 删除文件
    mkdir 创建文件夹
    
    // 上述方法均支持promise并提供有对应的同步方法
    // 想要操作一个文件,首先要打开一个文件,读取一个文件的buffer或者fd,通过fd进行文件的buffer进行相应的操作
    
  • 沙箱目录的内容 图片或者web组件要去访问的,需要使用文件协议

    file:// 
    
  • 代码演示下载网络图片

  async downLoad () {
   let path = getContext().cacheDir + '/' + Date.now() + ".jpg"
   const task = await request.downloadFile(getContext(),{
     url: 'http://dingyue.ws.126.net/2024/0209/8ec94ea3g00s8l8ki00ytd200f0008cg00it00ag.gif',
     filePath:  path 
   })
   task.on("progress", (process,total) => {
      promptAction.showToast({
        message:  process + '-' + total
      })
   })
   task.on("complete", () => {
     this.tempPath = path
     AlertDialog.show({
       message: '下载成功'
     })
   })

 }
  • 代码演示从相册选择图片、保存在沙箱并上传
// 导入图片选择器
import { picker } from '@kit.CoreFileKit'
// 导入文件操作库
import { fileIo } from '@kit.CoreFileKit'

// 选择图片
async selectImage() {
  const photoPicker = new picker.PhotoViewPicker()
  const result = await photoPicker.select({
    // 图片类型
    MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE,
    // 最多张数
    maxSelectNumber: 9
  })
  
  // 选择的图片数组
  if (!result.photoUris?.length) { return }
  
  // 保存到沙箱cache目录
  // 获取cache目录
  const saveDir = getContext().cacheDir
  
  // 构造上传文件需要的参数
  const files: request.File[] = []
 
  // 读取相册文件
  result.photoUris.forEach(url => {
      const file = fileIo.openSync(url, fileIo.OpenMode.READ_ONLY)
      // 将文件拷贝到沙箱目录
      const uniqueName = util.generateRandomUUID() + ".jpg"
      fileIo.copyFileSync(file.fd, saveDir + "/" + uniqueName)
      // 生成参数
      files.push({
        filename: uniqueName, // 文件名称
        name: 'file', // 接口的参数名称
        type: 'jpg', // 文件后缀
        uri: `internal://cache/${uniqueName}` // 文件的路径,internal: 是固定写法
      })
      fileIo.close(file.fd)
    })  
    
    // 上传
    // 构造上传需要的参数
    let config: request.UploadConfig = {
      url: "", // 服务器上传图片的接口地址
      method: 'POST',
      header: {
        "Authorization": "", // 身份验证标识,以服务器要求为准
        "Content-Type": "multipart/form-data" // 请求体类型,表单类型
      },
      files,
      data: [] // 批量上传需要的参数
    }

    // 上传完成,结果保存在list
    try {
        let list: list[] = []
        const task = await request.uploadFile(context, config)
        // 成功执行意味着任务创建成功,而不是上传成功
        task.on("fail", () => {
          // 上传失败
          promptAction.showToast({ message: "上传失败"})
        })

        task.on("complete", () => {
          // 上传成功
        })

        // 每上传成功一次就会进来,可以获取到上传成功的url
        task.on("headerReceive", (header: object) => {
          if (header["body"]) {
            const result = JSON.parse(header["body"]) as ResponseData<string>
            if (result.code === 200 && result.data) {
              list.push(result.data)
            }
          }
        }) 
     } catch (error) {
        promptAction.showToast({ message: error.message})
     }

}