文章目录
-
-
- [Preferences 是什么](#Preferences 是什么)
- 单例封装:PreferencesUtil
- [管理多张卡片的 formId](#管理多张卡片的 formId)
- [在生命周期里正确使用 Preferences](#在生命周期里正确使用 Preferences)
- 完整的数据流转图
- [Preferences 的几个注意事项](#Preferences 的几个注意事项)
-
做卡片开发的时候,有个问题早晚会遇到:App 重启之后,卡片的数据为什么全没了?
问题根源很简单:FormExtensionAbility 是无状态的,每次调用都是全新实例,内存里的数据下次就没了。要保住数据,必须用持久化存储。HarmonyOS 提供了几种选择,其中 Preferences(轻量级键值存储)是 FormKit 场景用得最多的。
本文把 Preferences 在卡片开发里的所有典型用法全部覆盖,包括封装技巧、多卡片管理、数据恢复等。
Preferences 是什么
类比 Android 的 SharedPreferences,或者 Web 里的 localStorage。
用大白话说:一个持久化的键值对字典,写入后重启也不会丢,App 更新也不会丢(除非手动清除数据或卸载 App)。
适合存储:
- 卡片的
formId列表 - 用户在编辑页面做的选择
- 卡片的状态(比如"展示步数"还是"展示天气")
不适合存储:
- 大量数据(Preferences 不是数据库,存太多会有性能问题)
- 频繁写入的数据(每次 flush 都有 I/O 开销)
单例封装:PreferencesUtil
FormEditDemo 和 FormEditUIAbility 都用到了一个 PreferencesUtil 工具类,这是一个很好的封装模式,直接拿来用:
typescript
// common/PreferencesUtil.ets
import { preferences } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
const MY_STORE = 'formKitStore'; // Preferences 文件名,同一应用内唯一
export class PreferencesUtil {
private static instance: PreferencesUtil;
// 单例模式
static getInstance(): PreferencesUtil {
if (!PreferencesUtil.instance) {
PreferencesUtil.instance = new PreferencesUtil();
}
return PreferencesUtil.instance;
}
/**
* 获取 Preferences 实例
* 注意:每次获取前先清缓存,保证读到磁盘上最新的数据
*/
getPreferences(context: Context): preferences.Preferences | undefined {
try {
// 清除内存缓存,防止跨进程写入后读不到最新值
preferences.removePreferencesFromCacheSync(context, MY_STORE);
return preferences.getPreferencesSync(context, { name: MY_STORE });
} catch (error) {
console.error(`getPreferences 失败: ${(error as BusinessError).message}`);
return undefined;
}
}
/**
* 写入一个键值对
*/
put(context: Context, key: string, value: string | number | boolean): void {
const pref = this.getPreferences(context);
if (!pref) return;
try {
pref.putSync(key, value);
// flush 将内存数据持久化到磁盘
pref.flush((err) => {
if (err) console.error(`flush 失败: ${err.message}`);
});
} catch (error) {
console.error(`put 失败: ${(error as BusinessError).message}`);
}
}
/**
* 读取一个值
*/
get<T>(context: Context, key: string, defaultValue: T): T {
const pref = this.getPreferences(context);
if (!pref) return defaultValue;
try {
return pref.getSync(key, defaultValue as preferences.ValueType) as T;
} catch (error) {
console.error(`get 失败: ${(error as BusinessError).message}`);
return defaultValue;
}
}
/**
* 删除一个键
*/
remove(context: Context, key: string): void {
const pref = this.getPreferences(context);
if (!pref) return;
try {
if (pref.hasSync(key)) {
pref.deleteSync(key);
pref.flush((err) => {
if (err) console.error(`flush 失败: ${err.message}`);
});
}
} catch (error) {
console.error(`remove 失败: ${(error as BusinessError).message}`);
}
}
/**
* 检查键是否存在
*/
has(context: Context, key: string): boolean {
const pref = this.getPreferences(context);
if (!pref) return false;
return pref.hasSync(key);
}
}
管理多张卡片的 formId
用户可以把同一张卡片添加多次,每次生成不同的 formId。你需要管理这些 formId,方便批量更新卡片。
typescript
// 存储多个 formId 的方案:JSON 序列化为字符串
const FORM_IDS_KEY = 'formIdList';
// 添加一个 formId
function addFormId(context: Context, formId: string): void {
const util = PreferencesUtil.getInstance();
const idsJson = util.get<string>(context, FORM_IDS_KEY, '[]');
const ids: string[] = JSON.parse(idsJson) as string[];
if (!ids.includes(formId)) {
ids.push(formId);
util.put(context, FORM_IDS_KEY, JSON.stringify(ids));
console.info(`formId ${formId} 已存储,当前共 ${ids.length} 张卡片`);
}
}
// 删除一个 formId
function removeFormId(context: Context, formId: string): void {
const util = PreferencesUtil.getInstance();
const idsJson = util.get<string>(context, FORM_IDS_KEY, '[]');
const ids: string[] = JSON.parse(idsJson) as string[];
const filtered = ids.filter(id => id !== formId);
util.put(context, FORM_IDS_KEY, JSON.stringify(filtered));
console.info(`formId ${formId} 已删除,剩余 ${filtered.length} 张卡片`);
}
// 获取所有 formId
function getAllFormIds(context: Context): string[] {
const util = PreferencesUtil.getInstance();
const idsJson = util.get<string>(context, FORM_IDS_KEY, '[]');
return JSON.parse(idsJson) as string[];
}
在生命周期里正确使用 Preferences
把上面的工具方法整合到 FormExtensionAbility 里:
typescript
import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
// 卡片数据模型
interface CardData {
formId: string;
displayContent: string; // 用户选择展示的内容
lastUpdateTime: string;
}
const CARD_DATA_PREFIX = 'card_'; // 每张卡片数据的 key 前缀
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want: Want) {
const formId = want.parameters?.[
'ohos.extra.param.key.form_identity'
] as string ?? '';
// 记录 formId
addFormId(this.context, formId);
// 尝试读取该 formId 之前的数据(用户重新添加已删除的卡片时恢复)
const util = PreferencesUtil.getInstance();
const savedDataStr = util.get<string>(this.context, CARD_DATA_PREFIX + formId, '');
let displayContent = '默认内容';
if (savedDataStr) {
const savedData = JSON.parse(savedDataStr) as CardData;
displayContent = savedData.displayContent;
}
return formBindingData.createFormBindingData({
'message': displayContent,
'formId': formId
});
}
onDestroyForm(formId: string) {
// 从 formId 列表中移除
removeFormId(this.context, formId);
// 清理该卡片的数据(可选,保留的话重新添加时可以恢复)
// PreferencesUtil.getInstance().remove(this.context, CARD_DATA_PREFIX + formId);
console.info(`卡片 ${formId} 已删除`);
}
onFormEvent(formId: string, message: string) {
const msg = JSON.parse(message) as Record<string, string>;
if (msg['msgType'] === 'updateContent') {
const newContent = msg['content'];
// 持久化用户的选择
const cardData: CardData = {
formId: formId,
displayContent: newContent,
lastUpdateTime: new Date().toISOString()
};
PreferencesUtil.getInstance().put(
this.context,
CARD_DATA_PREFIX + formId,
JSON.stringify(cardData)
);
// 更新卡片展示
formProvider.updateForm(
formId,
formBindingData.createFormBindingData({
'message': newContent,
'lastUpdate': new Date().toLocaleTimeString()
})
);
}
}
}
完整的数据流转图

Preferences 的几个注意事项
跨进程读写一定要清缓存
FormExtensionAbility 和 UIAbility 运行在不同进程,两边都可能读写同一个 Preferences 文件。一边写入后,另一边如果不清缓存(removePreferencesFromCacheSync),读到的可能还是旧值。这是 FormKit 开发里一个经典坑。
flush 是异步的,别忘了
pref.putSync() 只是写到内存,必须调用 pref.flush() 才会真正写入磁盘 。如果 App 突然崩溃而没来得及 flush,数据就丢了。重要数据可以用同步版本 pref.flushSync(),但要注意主线程阻塞问题。
不适合存大对象
Preferences 底层是 XML 文件,存大量数据或者嵌套很深的 JSON 会有性能问题。如果需要存复杂的结构化数据,考虑用 relationalStore(SQLite)。