在类Web开发范式中,使用File存储方式进行状态持久化适合处理大量数据(如离线缓存、复杂JSON、二进制文件等)。鸿蒙提供了@ohos.fileio
和应用上下文(Context)API用于文件操作,以下是详细实现步骤和示例:
一、核心API与前置知识
关键模块:
@ohos.fileio
:提供文件读写、创建、删除等操作。@ohos.ability.featureAbility
:获取应用沙箱目录(文件只能存储在应用私有目录,保障安全性)。
应用沙箱目录:
getFilesDir()
:获取应用私有文档目录(数据会持久化,卸载应用后删除)。- 路径格式:
/data/data/<应用包名>/files/
(不同设备可能略有差异,无需硬编码)。
文件操作权限:
- 类Web范式中无需额外声明权限,应用默认拥有私有目录的读写权限。
二、完整实现步骤
1. 初始化:获取文件存储目录
首先需要获取应用的私有文件目录,所有文件操作都基于此目录:
// 导入必要模块
import featureAbility from '@ohos.ability.featureAbility';
// 获取应用私有文件目录(文档目录)
async function getAppFilesDir() {
try {
const context = featureAbility.getContext(); // 获取应用上下文
const filesDir = await context.getFilesDir(); // 获取files目录路径
return filesDir;
} catch (error) {
console.error('获取文件目录失败:', error.message);
return null;
}
}
2. 存储数据到文件(状态持久化)
将需要持久化的状态(如对象、数组)通过JSON.stringify()
转为字符串,再写入文件:
import fileio from '@ohos.fileio';
/**
* 保存数据到文件
* @param {string} fileName 文件名(如"user_data.json")
* @param {any} data 要保存的数据(对象、数组等)
* @returns {Promise<boolean>} 是否保存成功
*/
async function saveDataToFile(fileName, data) {
try {
const filesDir = await getAppFilesDir();
if (!filesDir) return false;
// 拼接完整文件路径
const filePath = `${
filesDir}/${
fileName}`;
// 将数据转为字符串(二进制数据可直接传入Buffer)
const content = typeof data === 'string' ? data : JSON.stringify(data);
// 打开文件(0o2:写入权限;0o100:若文件不存在则创建)
const fd = fileio.openSync(filePath, 0o2 | 0o100, 0o666);
// 0o666:文件权限(可读可写)
// 写入内容
fileio.writeSync(fd, content);
// 关闭文件描述符(必须执行,否则会导致资源泄漏)
fileio.closeSync(fd);
console.log(`数据已保存到:${
filePath}`);
return true;
} catch (error) {
console.error('保存文件失败:', error.message);
return false;
}
}
使用示例:保存用户信息和商品列表
// 要持久化的状态数据
const appState = {
userInfo: {
id: 1, name: "鸿蒙用户", token: "xxx" },
favoriteGoods: [ {
id: 101, name: "手机" }, {
id: 102, name: "平板" } ],
lastLoginTime: new Date().toISOString()
};
// 调用保存方法
saveDataToFile('app_state.json', appState)
.then(success => {
if (success) console.log('状态保存成功');
});
3. 从文件读取数据(恢复状态)
应用启动或需要时,从文件读取数据并解析为原始格式:
/**
* 从文件读取数据
* @param {string} fileName 文件名
* @returns {Promise<any>} 读取的数据(对象/字符串等)
*/
async function readDataFromFile(fileName) {
try {
const filesDir = await getAppFilesDir();
if (!filesDir) return null;
const filePath = `${
filesDir}/${
fileName}`;
// 打开文件(0o1:只读权限)
const fd = fileio.openSync(filePath, 0o1);
// 获取文件大小(用于创建合适的缓冲区)
const stat = fileio.fstatSync(fd);
const buffer = new ArrayBuffer(stat.size);
// 读取文件内容到缓冲区
const readSize = fileio.readSync(fd, buffer);
// 关闭文件
fileio.closeSync(fd);
// 将缓冲区转为字符串并解析(二进制数据可直接返回Buffer)
const content = String.fromCharCode.apply(null, new Uint8Array(buffer.slice(0, readSize)));
// 尝试解析为JSON(非JSON数据直接返回字符串)
try {
return JSON.parse(content);
} catch (e) {
return content; // 非JSON格式直接返回字符串
}
} catch (error) {
// 文件不存在时返回null(首次启动场景)
if (error.code === 2) {
// 2:文件不存在错误码
console.log(`文件不存在:${
fileName}`);
return null;
}
console.error('读取文件失败:', error.message);
return null;
}
}
使用示例:应用启动时恢复状态
// 应用初始化时读取保存的状态
async function initAppState() {
const savedState = await readDataFromFile('app_state.json');
if (savedState) {
console.log('恢复状态成功:', savedState);
// 恢复到应用状态(如更新页面数据)
this.setData({
userInfo: savedState.userInfo,
favorites: savedState.favoriteGoods
});
} else {
console.log('无保存的状态,使用默认值');
// 初始化默认状态
}
}
// 在页面onInit生命周期调用
export default {
onInit() {
initAppState.call(this); // 注意绑定this上下文
}
};
4. 其他常用操作(删除/更新文件)
(1)删除文件
/**
* 删除文件
* @param {string} fileName 文件名
* @returns {Promise<boolean>} 是否删除成功
*/
async function deleteFile(fileName) {
try {
const filesDir = await getAppFilesDir();
if (!filesDir) return false;
const filePath = `${
filesDir}/${
fileName}`;
fileio.unlinkSync(filePath); // 删除文件
console.log(`文件已删除:${
filePath}`);
return true;
} catch (error) {
console.error('删除文件失败:', error.message);
return false;
}
}
(2)更新文件内容(覆盖写入)
更新操作与保存操作相同,fileio.openSync
使用0o2
权限会覆盖原有内容:
// 更新用户信息(覆盖原文件)
async function updateUserInfo(newUserInfo) {
// 先读取现有状态
const currentState = await readDataFromFile('app_state.json') || {
};
// 更新部分数据
currentState.userInfo = newUserInfo;
currentState.lastLoginTime = new Date().toISOString();
// 覆盖写入
return saveDataToFile('app_state.json', currentState);
}
三、最佳实践与注意事项
数据格式处理:
- 复杂对象/数组必须通过
JSON.stringify()
序列化,读取时用JSON.parse()
还原。 - 二进制数据(如图片、音频)可直接写入
ArrayBuffer
,无需序列化。
- 复杂对象/数组必须通过
避免频繁写入:
- 高频更新的状态(如滚动位置)建议防抖处理(例如500ms内只写入一次),减少磁盘IO开销。
错误处理:
- 必须捕获文件操作异常(如磁盘满、文件权限不足),避免应用崩溃。
- 文件不存在是正常场景(如首次启动),需返回默认状态。
清理过期数据:
- 定期删除不再需要的文件(如超过30天的缓存),可在应用启动时检查文件创建时间:
// 检查文件是否过期(示例:7天有效期) async function isFileExpired(fileName, days = 7) { const filesDir = await getAppFilesDir(); const fd = fileio.openSync(`${ filesDir}/${ fileName}`, 0o1); const stat = fileio.fstatSync(fd); fileio.closeSync(fd); const expireTime = new Date().getTime() - days * 24 * 60 * 60 * 1000; return stat.mtime.getTime() < expireTime; // mtime:最后修改时间 }
- 定期删除不再需要的文件(如超过30天的缓存),可在应用启动时检查文件创建时间:
大文件处理:
- 超过10MB的文件建议使用流式读写(
fileio.createReadStream
/createWriteStream
),避免占用过多内存。
- 超过10MB的文件建议使用流式读写(
总结
使用File存储进行状态持久化的核心流程是:获取应用目录→序列化数据→写入文件,读取时则反向解析。这种方式适合存储大量或复杂状态,配合适当的错误处理和性能优化,可满足类Web开发范式中大部分持久化需求。相比Preferences,File存储更灵活但代码量略多,需根据实际数据规模选择。