在Harmony系统中,为了保障用户数据安全与应用之间的隔离,引入了应用沙箱的概念。本文将深入探讨应用沙箱机制、应用文件目录管理以及与之相关的文件访问与管理实践,旨在帮助开发者更好地理解与利用这些特性。

应用沙箱:安全隔离的核心

HarmonyOS入门之应用文件管理_Harmony

应用沙箱是一种以安全防护为目的的隔离机制,它确保每个应用运行在自己的封闭环境中,限制应用访问其他应用或系统核心数据的能力。这种机制有效地防止了恶意路径穿越访问,增强了数据安全性。

沙箱目录组成

  • 应用文件目录:应用专属的文件存储区域,包括安装文件、资源、缓存等。
  • 系统文件目录:应用运行所需的系统文件,对应用而言只读。

目录范围

HarmonyOS入门之应用文件管理_应用文件_02

应用沙箱目录代表应用可见的所有目录范围,其中应用文件目录位于沙箱目录内。系统文件目录对应用是只读的,而应用文件目录则允许应用进行读写操作。

应用文件目录结构与管理

HarmonyOS入门之应用文件管理_文件目录_03

应用文件目录遵循严格的层次结构,不同层级的目录具有不同的用途:

  • 一级目录data/:代表应用文件的根目录。
  • 二级目录storage/:本应用持久化文件的存储区域。
  • 三级目录el1/el2/:分别表示设备级和用户级加密区,用于存储不同安全等级的数据。
  • 四级与五级目录:通过ApplicationContext等接口获取的应用特定目录,如filescachepreferences等。

文件路径获取

  • 开发者应当通过Context属性获取应用文件路径,而非直接构造路径字符串,以避免兼容性问题。

生命周期与说明

每种类型的文件路径都有其特定的生命周期和使用场景。例如,cacheDir中的文件可能在系统空间不足时被自动清理,而filesDir则用于长期保存的数据。

文件访问与管理接口

开发者可以通过ohos.file.fs模块实现对应用文件的访问能力,包括创建、读写、删除文件等基本操作。

示例代码

创建并读写文件
// pages/xxx.ets
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';

function createFile() {
  let context = getContext(this) as common.UIAbilityContext;
  let filesDir = context.filesDir;

  let file = fs.openSync(filesDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  fs.writeSync(file.fd, "Try to write str.");
  let buf = new ArrayBuffer(1024);
  let readLen = fs.readSync(file.fd, buf);
  console.info("the content of file: " + String.fromCharCode.apply(null, new Uint8Array(buf.slice(0, readLen))));
  fs.closeSync(file);
}
读取文件并写入另一文件
// pages/xxx.ets
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';

function readWriteFile() {
  let context = getContext(this) as common.UIAbilityContext;
  let filesDir = context.filesDir;

  let srcFile = fs.openSync(filesDir + '/test.txt', fs.OpenMode.READ_WRITE);
  let destFile = fs.openSync(filesDir + '/destFile.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  
  let bufSize = 4096;
  let readSize = 0;
  let buf = new ArrayBuffer(bufSize);
  let readLen = fs.readSync(srcFile.fd, buf, { offset: readSize });
  while (readLen > 0) {
    readSize += readLen;
    fs.writeSync(destFile.fd, buf);
    readLen = fs.readSync(srcFile.fd, buf, { offset: readSize });
  }
  
  fs.closeSync(srcFile);
  fs.closeSync(destFile);
}
使用流接口读写文件
// pages/xxx.ets
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';

async function readWriteFileWithStream() {
  let context = getContext(this) as common.UIAbilityContext;
  let filesDir = context.filesDir;

  let inputStream = fs.createStreamSync(filesDir + '/test.txt', 'r+');
  let outputStream = fs.createStreamSync(filesDir + '/destFile.txt', "w+");
  
  let bufSize = 4096;
  let readSize = 0;
  let buf = new ArrayBuffer(bufSize);
  let readLen = await inputStream.read(buf, { offset: readSize });
  readSize += readLen;
  while (readLen > 0) {
    await outputStream.write(buf);
    readLen = await inputStream.read(buf, { offset: readSize });
    readSize += readLen;
  }
  
  inputStream.closeSync();
  outputStream.closeSync();
}

文件分享与网络交互

应用还可以使用ohos.request模块将本地文件上传至网络服务器,或从网络下载资源文件到应用文件目录。

上传文件

// pages/xxx.ets
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import request from '@ohos.request';

let context = getContext(this) as common.UIAbilityContext;
let cacheDir = context.cacheDir;

let file = fs.openSync(cacheDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
fs.writeSync(file.fd, 'upload file test');
fs.closeSync(file);

let uploadConfig = {
  url: 'https://xxx',
  header: { key1: 'value1', key2: 'value2' },
  method: 'POST',
  files: [{ filename: 'test.txt', name: 'test', uri: 'internal://cache/test.txt', type: 'txt' }],
  data: [{ name: 'name', value: 'value' }]
};

request.uploadFile(context, uploadConfig)
  .then((uploadTask) => {
    uploadTask.on('complete', (taskStates) => {
      for (let i = 0; i < taskStates.length; i++) {
        console.info(`upload complete taskState: ${JSON.stringify(taskStates[i])}`);
      }
    });
  })
  .catch((err) => {
    console.error(`Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
  });

下载文件

// pages/xxx.ets
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import request from '@ohos.request';

let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;

request.downloadFile(context, {
  url: 'https://xxxx/xxxx.txt',
  filePath: filesDir + '/xxxx.txt'
}).then((downloadTask) => {
  downloadTask.on('complete', () => {
    console.info('download complete');
    let file = fs.openSync(filesDir + '/xxxx.txt', fs.OpenMode.READ_WRITE);
    let buf = new ArrayBuffer(1024);
    let readLen = fs.readSync(file.fd, buf);
    console.info(`The content of file: ${String.fromCharCode.apply(null, new Uint8Array(buf.slice(0, readLen)))}`);
    fs.closeSync(file);
  })
}).catch((err) => {
  console.error(`Invoke downloadTask failed, code is ${err.code}, message is ${err.message}`);
});

应用及文件系统空间统计

在系统中,可能出现系统空间不够或者cacheDir等目录受系统配额限制等情况,需要应用开发者关注系统剩余空间,同时控制应用自身占用的空间大小。

获取文件系统数据分区剩余空间大小

import statvfs from '@ohos.file.statvfs';
 

 let path = "/data";
 statvfs.getFreeSize(path, (err, 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}`);
   }
 });

获取当前应用的存储空间大小

import storageStatistics from "@ohos.file.storageStatistics";
 

 storageStatistics.getCurrentBundleStats((err, bundleStats) => {
   if (err) {
     console.error(`Invoke getCurrentBundleStats failed, code is ${err.code}, message is ${err.message}`);
   } else {
     console.info(`Invoke getCurrentBundleStats succeeded, appsize is ${bundleStats.appSize}`);
   }
 });

通过以上实践,开发者可以更好地理解和利用应用沙箱机制,有效管理和保护应用数据,同时提升应用在网络交互中的性能与安全性。