PersistentStorage是应用程序中的可选单例对象。此对象的作用是持久化存储选定的AppStorage属性,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。
PersistentStorage提供状态变量持久化的能力,但是需要注意,其持久化和读回UI的能力都需要依赖AppStorage。在阅读本文档前,建议提前阅读:AppStorage,PersistentStorage API文档。
PersistentStorage将选定的AppStorage属性保留在设备磁盘上。应用程序通过API,以决定哪些属性应借助PersistentStorage持久化。PersistentStorage和AppStorage中的属性建立了双向同步,UI和业务逻辑不直接访问PersistentStorage中的属性,所有属性访问都是对AppStorage的访问,AppStorage中的更改会自动同步到PersistentStorage。
PersistentStorage的存储路径为module级别,即哪个module调用了PersistentStorage,数据副本存入对应module的持久化文件中。如果多个module使用相同的key,则数据归属到最先使用PersistentStorage的module里。
PersistentStorage的存储路径在应用第一个ability启动时就已确定,为该ability所属的module。如果一个ability调用了PersistentStorage,并且该ability能被不同的module拉起,那么ability存在多少种启动方式,就会有多少份数据副本。
PersistentStorage功能上耦合了AppStorage,并且数据在不同module中使用也会有问题,因此推荐开发者使用PersistenceV2的globalConnect接口替换掉PersistentStorage的persistProp接口。PersistentStorage向PersistenceV2迁移的方案见PersistentStorage->PersistenceV2。
PersistentStorage允许的类型和值有:
PersistentStorage不允许的类型和值有:
持久化数据是一个相对缓慢的操作,应用程序应避免以下情况:
持久化大型数据集。
持久化经常变化的变量。
PersistentStorage的持久化变量最好是小于2kb的数据,不要大量的数据持久化,因为PersistentStorage写入磁盘是在UI线程同步执行的,大量数据本地读写会影响UI渲染性能。如果开发者需要存储大量的数据,建议使用@ohos.data.relationalStore (关系型数据库)相关接口。
PersistentStorage和UI实例相关联,持久化操作需要在UI实例初始化成功后(即loadContent传入的回调被调用时)才可以被调用,早于该时机调用会导致持久化失败。
- // EntryAbility.ets
- onWindowStageCreate(windowStage: window.WindowStage): void {
- windowStage.loadContent('pages/PageOneMessageStorage', (err) => {
- if (err.code) {
- return;
- }
- PersistentStorage.persistProp('aProp', 47);
- });
- }
初始化PersistentStorage:
- PersistentStorage.persistProp('aProp', 47);
在AppStorage获取对应属性:
- AppStorage.get<number>('aProp'); // returns 47
或在组件内部定义:
- @StorageLink('aProp') aProp: number = 48;
完整代码如下:
- PersistentStorage.persistProp('aProp', 47);
-
- @Entry
- @Component
- struct TestPageOne {
- @State message: string = 'Hello World';
- @StorageLink('aProp') aProp: number = 48;
-
- build() {
- Row() {
- Column() {
- Text(this.message)
- .fontSize(20)
- .margin(10)
- // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
- // 未修改时默认值为47
- Text(`${this.aProp}`)
- .fontSize(20)
- .margin(10)
- .onClick(() => {
- this.aProp += 1;
- })
- }
- .width('100%')
- }
- .height('100%')
- }
- }

新应用安装后首次启动运行:
图1 persistProp初始化流程

触发点击事件后:
后续启动应用:
该示例为反例。在调用PersistentStorage.persistProp或者persistProps之前使用接口访问AppStorage中的属性是错误的,因为这样的调用顺序会丢失上一次应用程序运行中的属性值:
- let aProp = AppStorage.setOrCreate('aProp', 47);
- PersistentStorage.persistProp('aProp', 48);
应用在非首次运行时,先执行AppStorage.setOrCreate('aProp', 47):属性“aProp”在AppStorage中创建,其类型为number,其值设置为指定的默认值47。“aProp”是持久化的属性,所以会被写回PersistentStorage磁盘中,PersistentStorage存储的上次退出应用的值被覆盖。
PersistentStorage.persistProp('aProp', 48):在PersistentStorage中查找到“aProp”,值为刚刚使用AppStorage接口写入的47。
开发者可以先判断是否需要覆盖上一次保存在PersistentStorage中的值,如果需要覆盖,再调用AppStorage的接口进行修改,如果不需要覆盖,则不调用AppStorage的接口。
- const MAX_NUM: number = 50;
- PersistentStorage.persistProp('aProp', 48);
- if ((AppStorage.get<number>('aProp') ?? 0) > MAX_NUM) {
- // 如果PersistentStorage存储的值超过50,设置为47
- AppStorage.setOrCreate('aProp', 47);
- }
示例代码在读取PersistentStorage存储的数据后,判断“aProp”的值是否大于50,如果大于50,则使用AppStorage的接口将其设置为47。
PersistentStorage支持联合类型和undefined和null,在下面的示例中,使用persistProp方法初始化“P”为undefined。通过@StorageLink('P')绑定变量p,类型为number | undefined | null,点击Button改变P的值,视图会随之刷新。且P的值被持久化存储。
- // 定义常量替代魔法值,明确数值含义
- const DEFAULT_NUMBER: number = 10; // 默认数字值
- const FONT_SIZE_LARGE: number = 50; // 大字体尺寸
-
- // 初始化持久化属性,键名使用常量定义(若有多处使用可提取)
- const STORAGE_KEY_P: string = 'P';
- PersistentStorage.persistProp(STORAGE_KEY_P, undefined);
-
- @Entry
- @Component
- struct TestCase6 {
- // 使用常量作为默认值,类型明确
- @StorageLink(STORAGE_KEY_P) p: number | undefined | null = DEFAULT_NUMBER;
-
- build() {
- Row() {
- Column() {
- Text(this.p + '')
- .fontSize(FONT_SIZE_LARGE)
- .fontWeight(FontWeight.Bold)
- .margin(10)
- Button('changeToNumber')
- .width(300)
- .margin(10)
- .onClick(() => {
- this.p = DEFAULT_NUMBER;
- })
- Button('changeTo undefined')
- .width(300)
- .margin(10)
- .onClick(() => {
- this.p = undefined;
- })
- Button('changeTo null')
- .width(300)
- .margin(10)
- .onClick(() => {
- this.p = null;
- })
- }
- .width('100%')
- }
- .height('100%')
- }
- }

在下面的示例中,@StorageLink装饰的persistedDate类型为Date,点击Button改变persistedDate的值,视图会随之刷新。且persistedDate的值被持久化存储。
- PersistentStorage.persistProp('persistedDate', new Date());
-
- @Entry
- @Component
- struct PersistedDate {
- @StorageLink('persistedDate') persistedDate: Date = new Date();
-
- updateDate() {
- this.persistedDate = new Date();
- }
-
- build() {
- List() {
- ListItem() {
- Column() {
- Text(`Persisted Date is ${this.persistedDate.toString()}`)
- .fontSize(20)
- .margin(20)
-
- Text(`Persisted Date year is ${this.persistedDate.getFullYear()}`)
- .fontSize(20)
- .margin(20)
-
- Text(`Persisted Date hours is ${this.persistedDate.getHours()}`)
- .fontSize(20)
- .margin(20)
-
- Text(`Persisted Date minutes is ${this.persistedDate.getMinutes()}`)
- .fontSize(20)
- .margin(20)
-
- Text(`Persisted Date time is ${this.persistedDate.toLocaleTimeString()}`)
- .fontSize(20)
- .margin(20)
-
- Button() {
- Text('Update Date')
- .fontSize(25)
- .fontWeight(FontWeight.Bold)
- .fontColor(Color.White)
- }
- .type(ButtonType.Capsule)
- .margin({
- top: 20
- })
- .backgroundColor('#0D9FFB')
- .width('60%')
- .height('5%')
- .onClick(() => {
- // 改变persistedDate的值,视图会随之刷新
- this.updateDate();
- })
- }
- .width('100%')
- }
- }
- }
- }

在下面的示例中,@StorageLink装饰的persistedMapString类型为Map<number, string>,点击Button改变persistedMapString的值,视图会随之刷新。且persistedMapString的值被持久化存储。
- PersistentStorage.persistProp('persistedMapString', new Map<number, string>([]));
-
- @Entry
- @Component
- struct PersistedMap {
- @StorageLink('persistedMapString') persistedMapString: Map<number, string> = new Map<number, string>([]);
-
- persistMapString() {
- this.persistedMapString = new Map<number, string>([[3, 'one'], [6, 'two'], [9, 'three']]);
- }
-
- build() {
- List() {
- ListItem() {
- Column() {
- Text(`Persisted Map String is `)
- .fontSize(20)
- .margin(20)
- ForEach(Array.from(this.persistedMapString.entries()), (item: [number, string]) => {
- Text(`${item[0]} ${item[1]}`)
- .fontSize(20)
- .margin(10)
- })
-
- Button() {
- Text('Persist Map String')
- .fontSize(20)
- .fontWeight(FontWeight.Bold)
- .fontColor(Color.White)
- }
- .type(ButtonType.Capsule)
- .margin({
- top: 20
- })
- .backgroundColor('#0D9FFB')
- .width('60%')
- .height('5%')
- .onClick(() => {
- // 点击Button改变persistedMapString的值,视图会随之刷新
- this.persistMapString();
- })
- }
- .width('100%')
- }
- }
- }
- }

在下面的示例中,@StorageLink装饰的persistedSet类型为Set<number>,点击Button改变persistedSet的值,视图会随之刷新。且persistedSet的值被持久化存储。
- PersistentStorage.persistProp('persistedSet', new Set<number>([]));
-
- @Entry
- @Component
- struct PersistedSet {
- @StorageLink('persistedSet') persistedSet: Set<number> = new Set<number>([]);
-
- persistSet() {
- this.persistedSet = new Set<number>([33, 1, 3]);
- }
-
- clearSet() {
- this.persistedSet.clear();
- }
-
- build() {
- List() {
- ListItem() {
- Column() {
- Text(`Persisted Set is `)
- .fontSize(20)
- .margin(20)
- ForEach(Array.from(this.persistedSet.entries()), (item: [number, number]) => {
- Text(`${item[1]}`)
- .fontSize(20)
- .margin(10)
- })
-
- Button() {
- Text('Persist Set')
- .fontSize(25)
- .fontWeight(FontWeight.Bold)
- .fontColor(Color.White)
- }
- .type(ButtonType.Capsule)
- .margin({
- top: 20
- })
- .backgroundColor('#0D9FFB')
- .width('60%')
- .height('5%')
- .onClick(() => {
- this.persistSet();
- })
-
- Button() {
- Text('Persist Clear')
- .fontSize(25)
- .fontWeight(FontWeight.Bold)
- .fontColor(Color.White)
- }
- .type(ButtonType.Capsule)
- .margin({
- top: 20
- })
- .backgroundColor('#0D9FFB')
- .width('60%')
- .height('5%')
- .onClick(() => {
- // 点击Button改变persistedSet的值,视图会随之刷新
- this.clearSet();
- })
- }
- .width('100%')
- }
- }
- }
- }
