写点什么

HarmonyOS 沙箱文件管理与离线包加载机制解析

  • 2025-05-12
    北京
  • 本文字数:4952 字

    阅读完需:约 16 分钟

HarmonyOS沙箱文件管理与离线包加载机制解析

本文将深入分析一个基于 HarmonyOS 的沙箱文件管理系统和离线包加载实现,该实现允许应用在沙箱环境中管理、解压和加载离线资源包。在鸿蒙(HarmonyOS)应用开发过程中,了解如何查看和管理应用的沙箱文件是开发者必备的技能之一。沙箱文件包含了应用的私有数据、缓存、数据库等重要信息,掌握这些文件的访问方法对于调试和问题排查至关重要


应用沙箱简介

关于应用沙箱如果不了解的可以访问华为官方文档应用沙箱目录,(遇事不决,先看文档,不行就百度)

DevEco Studio 查看应用沙箱的操作:

确保模拟器已启动并在模拟器上安装你要检查的应用

点击底部工具栏的 "Device File Explorer"

根据你的应用包名,如下方图片展示就是你的应用沙箱路径

应用沙箱是 HarmonyOS 提供的一种安全机制,用于隔离应用的文件和数据。每个应用都有自己的沙箱目录,其他应用无法访问。沙箱目录通常包含以下重要目录:

  1. base/:基础 hap 资源

  2. files/:应用私有文件

    cache/:缓存文件

    database/:数据库文件

    haps/:应用包资源

  3. preferences/:首选项文件


沙箱文件工具类:OfflinePackage

OfflinePackage类主要提供以下功能:

  1. 文件存在性检查:检查沙箱目录中是否存在指定文件

  2. 添加 ZIP 文件:将资源文件添加到沙箱目录

  3. 删除 ZIP 文件:从沙箱目录移除指定文件

  4. 解压 ZIP 文件:将沙箱中的 ZIP 文件解压到指定目录

  5. 读取文件内容:读取沙箱中指定文件的内容


1. 沙箱路径获取

let boxPath = context.filesDir; // 获取沙箱路径
复制代码

HarmonyOS 通过context.filesDir获取应用的沙箱目录路径,这是应用私有的文件存储区域,其他应用无法访问。


2. 文件存在性检查

    if (fs.accessSync(unzipPath)) {      if (fs.accessSync(zipPath)) {        return true;       }    }
复制代码

该方法检查沙箱目录中是否存在指定的 zip 文件。它首先构建完整的沙箱路径,然后使用fs.accessSync同步检查文件和目录是否存在。使用accessSync方法同步检查文件或目录是否存在,这是文件操作前的重要安全检查。


3. 添加 ZIP 文件

// 获取资源文件内容let uint8Array: Uint8Array = context.resourceManager.getRawFileContentSync(fileUri);let bf = buffer.from(uint8Array).buffer;
// 创建目录和文件fs.mkdirSync(unzipPath);const fsOpen = fs.openSync(zipPath, fs.OpenMode.READ_WRITE | fs.OpenMode.READ_ONLY | fs.OpenMode.CREATE | fs.OpenMode.TRUNC)
// 写入数据let destFile = fs.writeSync(fsOpen.fd, bf);fs.close(destFile)
复制代码

此过程展示了如何从应用的rawfile目录获取资源文件,并将其写入沙箱目录。关键点包括:

  • 使用resourceManager获取资源内容

  • 使用buffer进行数据类型转换

  • 文件操作模式的选择(READ_WRITE、CREATE 等)


4. 删除 ZIP 文件

// 删除沙箱目录下zip文件if (fs.accessSync(zipPath)) {  fs.unlinkSync(zipPath)  console.log('删除沙箱目录下zip文件成功')} else {  console.log('沙箱目录下zip文件不存在')}
复制代码
  1. fs.accessSync(zipPath):这个函数用于检查当前用户是否有权限访问zipPath指定的文件。如果文件存在并且可访问,则返回true;否则,抛出错误。

  2. fs.unlinkSync(zipPath):这个函数用于同步删除指定的文件。如果文件被成功删除,则不会有返回值;如果出现错误,则会抛出错误。


5. 解压 ZIP 文件

zlib.decompressFile(zipPath, unzipPath, options, (errData: BusinessError) => {  if (errData !== null) {    console.error('记录错误代码和消息', `errData is errCode:${errData.code}  message:${errData.message}`);  }  // 解压后操作...})
复制代码

使用zlib模块的decompressFile方法进行异步解压,支持设置压缩级别等选项。

  • 使用zlib模块进行解压,提供高效的压缩和解压功能。

  • 异步操作避免阻塞主线程,提升应用性能。

6. 文件内容读取

let fileTarget = fs.openSync(htmlPath, fs.OpenMode.READ_ONLY);let size = fs.statSync(fileTarget.fd).size;let bufferRes = new ArrayBuffer(size);let readLen = fs.readSync(fileTarget.fd,bufferRes)let hotIndexContent = buffer.from(bufferRes).toString()
复制代码

文件读取流程包括:打开文件、获取大小、创建缓冲区、读取内容和转换格式。


完整代码

import { zlib, BusinessError } from '@kit.BasicServicesKit';import { fileIo as fs } from '@kit.CoreFileKit'; import { buffer } from '@kit.ArkTS';
class OfflinePackage {
/** * 判断沙箱目录是否存在zip文件 */ isExist(context: Context, dynDir: string, dynFileName: string): boolean { let boxPath = context.filesDir; // 获取沙箱路径 let unzipPath = boxPath + "/" + dynDir; // 动态目录 let zipPath = boxPath + "/" + dynDir + "/" + dynFileName; // 动态文件名 // 判断沙箱目录是否存在 if (fs.accessSync(unzipPath)) { // 判断沙箱目录下是否存在zip文件 if (fs.accessSync(zipPath)) { return true; // 如果沙箱目录存在且包含zip文件,返回true } } return false; }
/** * 添加zip文件到沙箱目录 */ addZip(context: Context, dynDir: string, dynFileName: string, directory?: string) { let boxPath = context.filesDir; // 获取沙箱路径 let unzipPath = boxPath + "/" + dynDir; // 动态目录 let zipPath = boxPath + "/" + dynDir + "/" + dynFileName; // 动态文件名 let fileUri = directory + "/" + dynFileName; // 获取zip文件路径 // 使用上下文的 resourceManager 获取文件 Uint8Array 数据 let uint8Array: Uint8Array = context.resourceManager.getRawFileContentSync(fileUri); // 将获取的 unit8Array 数据 转换为 ArrayBuffer let bf = buffer.from(uint8Array).buffer;
// 创建沙箱目录 if (fs.accessSync(unzipPath)) { console.log('沙箱目录存在'); } else { try { fs.mkdirSync(unzipPath); } catch (e) { console.error('创建目录失败: ' + e); return; } }
// 在创建的沙箱路径下创建待拷贝数据的空文件 const fsOpen = fs.openSync(zipPath, fs.OpenMode.READ_WRITE | fs.OpenMode.READ_ONLY | fs.OpenMode.CREATE | fs.OpenMode.TRUNC)
// 写入数据 let destFile = fs.writeSync(fsOpen.fd, bf); // 写入完毕后关闭文件,因为在上一步文件在开启中 fs.close(destFile)
console.log('文件 ' + dynFileName + ' 已成功写入目录 ' + dynDir); }
/** * 删除沙箱里的zip文件 */ deleteZip(context: Context, dynDir: string, dynFileName: string) { let boxPath = context.filesDir; // 获取沙箱路径 let unzipPath = boxPath + "/" + dynDir; // 动态目录 let zipPath = boxPath + "/" + dynDir + "/" + dynFileName; // 动态文件名 // 删除沙箱目录下zip文件 if (fs.accessSync(zipPath)) { fs.unlinkSync(zipPath) } else { console.log('沙箱目录下zip文件不存在') } }
/** * 解压沙箱里的zip文件 */ unzipFile(context: Context, dynDir: string, dynFileName: string) { if (!this.isExist(context, dynDir,dynFileName)) { return console.log('沙箱目录下zip文件不存在') } let boxPath = context.filesDir; // 获取沙箱路径 let unzipPath = boxPath + "/" + dynDir; // 动态目录 let zipPath = boxPath + "/" + dynDir + "/" + dynFileName; // 动态文件名 // 配置解压选项 let options: zlib.Options = { // 设置压缩级别(此处使用默认压缩级别) level: zlib.CompressLevel.COMPRESS_LEVEL_DEFAULT_COMPRESSION }; zlib.decompressFile(zipPath, unzipPath, options, (errData: BusinessError) => { if (errData !== null) { console.error('记录错误代码和消息', `errData is errCode:${errData.code} message:${errData.message}`); } let fileTarget = fs.openSync(unzipPath + "/test.html", fs.OpenMode.READ_ONLY); let size = fs.statSync(fileTarget.fd).size; // 创建一个与文件大小相同的数据缓冲区 let bufferRes = new ArrayBuffer(size); let readLen =fs.readSync(fileTarget.fd,bufferRes) // 关闭文件 fs.closeSync(fileTarget); let result = buffer.from(bufferRes).toString() }) }
// 读取沙箱路径下指定文件内容 readFile(context: Context, dynDir: string, dynFileName: string): string { let boxPath = context.filesDir; let htmlPath = boxPath + "/" + dynDir + "/" + dynFileName; let textHtml = '' if (fs.accessSync(htmlPath)) { let fileTarget = fs.openSync(htmlPath, fs.OpenMode.READ_ONLY); let size = fs.statSync(fileTarget.fd).size; let bufferRes = new ArrayBuffer(size); let readLen =fs.readSync(fileTarget.fd,bufferRes) let hotIndexContent = buffer.from(bufferRes).toString() textHtml = hotIndexContent; } return textHtml }
}
export const offlinePackage = new OfflinePackage();
复制代码

功能展示页面

在演示前,需要提前准备好资源文件


创建 SandboxFileOperationPage组件提供操作界面,展示如何使用OfflinePackage功能:

代码就不写了,截图看吧


创建 WebLoadOfflinePackagePage 组件主要实现以下功能:

  1. 加载沙箱 Web 资源:从应用沙箱目录加载 HTML 文件

  2. JavaScript 与原生交互:实现 Web 与原生代码的双向通信

@Componentexport struct WebLoadOfflinePackagePage {  context = getContext(this) as common.UIAbilityContext  controller: webview.WebviewController = new webview.WebviewController();  private webController: web_webview.WebviewController = new web_webview.WebviewController();  url = 'file://' + this.context.filesDir + '/webSources/test.html';
build() { Navigation() { Column() { Web({ src: this.url, // 设置 Web 组件的 HTML 文件路径 controller: this.webController // 绑定 Web 组件的控制器 }) .darkMode(WebDarkMode.Auto) .fileAccess(true) .javaScriptProxy({ // 定义 JavaScript 调用原生方法的功能 object: { openCamera: () => this.openGallery(), // 当 JavaScript 调用 openCamera 时,执行原生方法 openGallery }, name: 'webJSCallObject', // 定义 JavaScript 中调用原生方法的对象名称 methodList: ['openCamera', 'dbConnection'], // 定义 JavaScript 可以调用的原生方法列表 controller: this.webController // 绑定 Web 组件的控制器,用于管理 JavaScript 和原生代码的交互 } as JavaScriptProxy) } } .title('web加载沙箱文件') .titleMode(NavigationTitleMode.Mini) .mode(NavigationMode.Stack) }
复制代码

关键配置说明:

  • src:指定要加载的 HTML 文件路径,这里使用沙箱中的文件路径

  • controller:Webview 控制器,用于管理 Webview 行为

  • fileAccess(true):启用本地文件访问权限,允许 Webview 访问设备文件


沙箱文件路径构建:url = 'file://' + this.context.filesDir + '/webSources/test.html';

路径构建要点:

  • 使用file://协议访问本地文件

  • context.filesDir获取应用沙箱目录

  • 路径指向先前解压的 Web 资源文件


通过上述代码解析,OfflinePackage类在 HarmonyOS 应用开发中提供了文件存在性检查、添加 ZIP 文件、删除 ZIP 文件、解压 ZIP 文件和读取文件内容等功能,将 Web 应用资源打包到应用中,实现离线访问,帮助开发者更好地管理和操作沙箱文件。这些功能的实现不仅确保了文件操作的安全性和正确性,还提升了应用的性能和用户体验。


发布于: 刚刚阅读数: 5
用户头像

还未添加个人签名 2025-04-29 加入

还未添加个人简介

评论

发布
暂无评论
HarmonyOS沙箱文件管理与离线包加载机制解析_鸿蒙_记忆深处的声音_InfoQ写作社区