摘要:LocalStorage 是 Web 前端最常用的轻量级存储。但在鸿蒙混合开发中,我们遭遇了一次严重的"数据丢失"事故:应用更新或重新签名打包后,用户的游戏最高分和设置项全部清零。本文揭示了 ArkWeb 数据存储的目录机制,并给出了基于 Native Preferences 的持久化替代方案。
😱 1. 数据去哪了?
事故复现:
- 用户安装 v1.0 版本,玩到了 20480 分。
- 我们发布 v1.1 版本(可能更换了签名证书或 Bundle Name 微调)。
- 用户覆盖安装。
- 打开游戏,最高分变成 0,用户炸毛。
原因深究:
Android WebView 和鸿蒙 ArkWeb 的 LocalStorage 数据是存储在应用沙箱下的特定目录中的。
路径通常包含 Bundle Name 和 Webview ID。
如果应用的签名变更,或者系统认为这是两个不同的应用实例,系统出于安全考虑,可能会重建沙箱,导致旧的 app_webview 目录被清空或无法访问。
对于 Web App 来说,LocalStorage 被设计为"浏览器缓存"的一部分,它并不像文件系统那样具备"持久化承诺"。系统空间不足时,甚至可能自动清理。
🛡️ 2. 救援行动:Native 存储
为了保证数据绝对安全,关键数据(如用户存档、Token、设置)绝不能只存放在 LocalStorage。
必须存储在 Native 层的 Preferences (首选项) 或 Database (数据库) 中。
2.1 构建 NativeStorage 插件
我们封装一个 NativeStorage 类,通过 JSBridge 暴露给前端。
ArkTS 实现:
import dataPreferences from '@ohos.data.preferences';
class NativeStorageProxy {
private prefs: dataPreferences.Preferences;
async init(context) {
// getPreferences 是持久化的,除非卸载应用,否则数据一直存在
this.prefs = await dataPreferences.getPreferences(context, 'game_data');
}
save(key: string, value: string) {
this.prefs.put(key, value).then(() => {
this.prefs.flush(); // 关键:立即写入磁盘
});
}
async get(key: string) {
return await this.prefs.get(key, '');
}
}
2.2 前端无感替换
为了不修改大量业务代码,我们在前端劫持 localStorage 对象(或者封装一个兼容层)。
const StorageAdapter = {
setItem: async (key, value) => {
// 双写策略:既存 LocalStorage (快),也存 Native (稳)
localStorage.setItem(key, value);
if (window.gameNative) {
window.gameNative.saveData(key, value);
}
},
getItem: async (key) => {
// 优先读 LocalStorage(同步,速度快)
let val = localStorage.getItem(key);
if (!val && window.gameNative) {
// 如果 LocalStorage 丢了,尝试从 Native 捞回
val = await window.gameNative.getData(key);
if (val) {
// 修复 LocalStorage
localStorage.setItem(key, val);
}
}
return val;
}
};
🔄 3. 数据迁移策略
应用启动时的**“数据同步仪式”**:
在 index.html 加载完成后:
- 检查
localStorage是否为空。 - 如果为空,调用
NativeStorage.getAll()。 - 将 Native 数据全量灌入
localStorage。 - 游戏开始。
这样,业务逻辑依然可以同步读取 localStorage,无需把所有代码都改成 await 异步模式,既保留了开发便利性,又保证了数据安全性。
⚠️ 4. 避坑指南
- 不要存大对象:Preferences 适合存 Key-Value 小数据。如果你要存几兆的 JSON,请改用文件存储(参考第 8 篇博文)。
- 签名必须一致:即使是 Native 存储,如果应用签名变了,鸿蒙系统依然会视为新应用,沙箱数据依然会隔离。这是系统级安全机制,无解(除非使用云同步)。
- Flush 的时机:
preferences.flush()是耗时 IO 操作,不要在每次按键时都调用。建议使用防抖(Debounce)策略,或者在onBackground时统一保存。
📚 5. 总结
Web 的 LocalStorage 就像便利贴,方便但易丢。
Native 的 Preferences 就像保险箱,繁琐但安全。
“便利贴 + 保险箱” 的组合方案,是混合应用数据存储的最佳实践。
1338

被折叠的 条评论
为什么被折叠?



