本文将深入分析一个基于 HarmonyOS 的沙箱文件管理系统和离线包加载实现,该实现允许应用在沙箱环境中管理、解压和加载离线资源包。在鸿蒙(HarmonyOS)应用开发过程中,了解如何查看和管理应用的沙箱文件是开发者必备的技能之一。沙箱文件包含了应用的私有数据、缓存、数据库等重要信息,掌握这些文件的访问方法对于调试和问题排查至关重要
应用沙箱简介
关于应用沙箱如果不了解的可以访问华为官方文档应用沙箱目录,(遇事不决,先看文档,不行就百度)
在 DevEco Studio 查看应用沙箱的操作:
确保模拟器已启动并在模拟器上安装你要检查的应用
点击底部工具栏的 "Device File Explorer"
根据你的应用包名,如下方图片展示就是你的应用沙箱路径
应用沙箱是 HarmonyOS 提供的一种安全机制,用于隔离应用的文件和数据。每个应用都有自己的沙箱目录,其他应用无法访问。沙箱目录通常包含以下重要目录:
base/
:基础 hap 资源
files/
:应用私有文件
cache/
:缓存文件
database/
:数据库文件
haps/
:应用包资源
preferences/
:首选项文件
沙箱文件工具类:OfflinePackage
OfflinePackage
类主要提供以下功能:
文件存在性检查:检查沙箱目录中是否存在指定文件
添加 ZIP 文件:将资源文件添加到沙箱目录
删除 ZIP 文件:从沙箱目录移除指定文件
解压 ZIP 文件:将沙箱中的 ZIP 文件解压到指定目录
读取文件内容:读取沙箱中指定文件的内容
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
目录获取资源文件,并将其写入沙箱目录。关键点包括:
4. 删除 ZIP 文件
// 删除沙箱目录下zip文件
if (fs.accessSync(zipPath)) {
fs.unlinkSync(zipPath)
console.log('删除沙箱目录下zip文件成功')
} else {
console.log('沙箱目录下zip文件不存在')
}
复制代码
fs.accessSync(zipPath)
:这个函数用于检查当前用户是否有权限访问zipPath
指定的文件。如果文件存在并且可访问,则返回true
;否则,抛出错误。
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
方法进行异步解压,支持设置压缩级别等选项。
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
组件主要实现以下功能:
加载沙箱 Web 资源:从应用沙箱目录加载 HTML 文件
JavaScript 与原生交互:实现 Web 与原生代码的双向通信
@Component
export 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 应用资源打包到应用中,实现离线访问,帮助开发者更好地管理和操作沙箱文件。这些功能的实现不仅确保了文件操作的安全性和正确性,还提升了应用的性能和用户体验。
评论