• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# PersistenceV2: 持久化存储UI状态
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @zzq212050299-->
5<!--Designer: @s10021109-->
6<!--Tester: @TerryTsao-->
7<!--Adviser: @zhang_yixin13-->
8
9为了增强状态管理框架对持久化存储UI的能力,开发者可以使用PersistenceV2存储持久化的数据。
10
11PersistenceV2是应用程序中的可选单例对象。此对象的作用是持久化存储UI相关的数据,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。
12
13PersistenceV2提供状态变量持久化能力,开发者可以通过connect或者globalConnect绑定同一个key,在状态变量变化和应用冷启动时,实现持久化能力。
14
15在阅读本文档前,建议提前阅读:[\@ComponentV2](./arkts-new-componentV2.md),[\@ObservedV2和\@Trace](./arkts-new-observedV2-and-trace.md),配合阅读:[PersistentV2-API文档](../../reference/apis-arkui/js-apis-StateManagement.md#persistencev2)。
16
17>**说明:**
18>
19>PersistenceV2从API version 12开始支持。
20>
21>globalConnect从API version 18开始支持,行为和connect保持一致,唯一的区别为connect的底层存储路径为module级别的路径,而globalConnect的底层存储路径为应用级别,详细区别见使用场景[在不同的module中使用connect和globalConnect](#在不同的module中使用connect和globalconnect)。
22
23
24## 概述
25
26PersistenceV2是在应用UI启动时会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。数据通过唯一的键字符串值访问。不同于AppStorageV2,PersistenceV2还将最新数据存储在设备磁盘上(持久化)。这意味着,应用退出再次启动后,依然能保存选定的结果。
27
28对于与PersistenceV2关联的[\@ObservedV2](./arkts-new-observedV2-and-trace.md)对象,该对象的[\@Trace](./arkts-new-observedV2-and-trace.md)属性的变化,会触发**整个关联对象的自动持久化**;非[\@Trace](./arkts-new-observedV2-and-trace.md)属性的变化则不会,如有必要,可调用PersistenceV2 API手动持久化。请注意:被PersistenceV2持久化的类属性必须要有初值,否则不支持持久化。
29
30PersistenceV2可以和UI组件同步,且可以在应用业务逻辑中被访问。
31
32PersistenceV2支持应用的[主线程](../../application-models/thread-model-stage.md)内多个UIAbility实例间的状态共享。
33
34## 使用说明
35
36- connect:创建或获取存储的数据。
37
38>**说明:**
39>
40>1、关联[\@Observed](./arkts-observed-and-objectlink.md)对象时,由于该类型的name属性未定义,需要指定key或者自定义name属性。
41>
42>2、数据存储路径为module级别,即哪个module调用了connect,数据副本存入对应module的持久化文件中。如果多个module使用相同的key,则数据为最先使用connect的module,并且PersistenceV2中的数据也会存入最先使用connect的module里。
43>
44>3、因为存储路径在应用第一个ability启动时就已确定,为该ability所属的module。如果一个ability调用了connect,并且该ability能被不同module的拉起, 那么ability存在多少种启动方式,就会有多少份数据副本。
45
46- globalConnect:创建或获取存储的数据。
47- remove:删除指定key的存储数据。删除PersistenceV2中不存在的key会报警告。
48- keys:返回所有PersistenceV2中的key。包括module级别存储路径和应用级别存储路径中的所有key。
49- save:手动持久化数据。
50- notifyOnError:响应序列化或反序列化失败的回调。将数据存入磁盘时,需要对数据进行序列化;当某个key序列化失败时,错误是不可预知的;可调用该接口捕获异常。
51
52以上接口详细描述请参考[状态管理API指南](../../reference/apis-arkui/js-apis-StateManagement.md)。
53
54## 使用限制
55
561、需要配合UI使用(UI线程),不能在其他线程使用,如不支持@Sendable。
57
582、不支持collections.Setcollections.Map等类型。
59
603、不支持非built-in类型,如PixelMap、NativePointer、ArrayList等Native类型。
61
624、单个key支持数据大小约8k,过大会导致持久化失败。
63
645、持久化的数据必须是class对象,不支持容器类型(如Array、Set、Map),不支持built-in的构造对象(如Date、Number),不支持持久化基本类型(如string、number、boolean)。如果需要持久化非class对象,建议使用[prefrence](../../database/preferences-guidelines.md)进行数据持久化。
65
666、不支持循环引用的对象。
67
687、只有[\@Trace](./arkts-new-observedV2-and-trace.md)的数据改变会触发自动持久化,如V1状态变量、[\@Observed](./arkts-observed-and-objectlink.md)对象、普通数据的改变不会触发持久化。
69
708、不宜大量持久化数据,可能会导致页面卡顿。
71
729、connect和globalConnect不建议混用,如果混用,key不能一样,否则应用crash。
73
7410、PersistenceV2必须与UI实例关联,持久化操作需在UI实例初始化完成后调用(即[loadContent](../../reference/apis-arkui/arkts-apis-window-WindowStage.md#loadcontent9)回调触发后)。
75```ts
76// EntryAbility.ets
77// 以下为代码片段,需要开发者自己在EntryAbility.ets中补全
78import { PersistenceV2 } from '@kit.ArkUI';
79
80// 在EntryAbility外部定义class
81@ObservedV2
82class Storage {
83  @Trace isPersist: boolean = false;
84}
85
86// 在onWindowStageCreate的loadContent回调中调用PersistenceV2
87onWindowStageCreate(windowStage: window.WindowStage): void {
88  windowStage.loadContent('pages/Index', (err) => {
89    if (err.code) {
90      return;
91    }
92    PersistenceV2.connect(Storage, () => new Storage());
93  });
94}
95```
96
9711、如果开发者对数据持久化能力有较强的诉求,例如持久化时机,建议使用[Preferences](../../database/preferences-guidelines.md)进行数据持久化。注意:不允许混用PersistenceV2和prefrence,因为Preferences存储的数据不会有状态变量信息,反序列化的数据不能触发PersistenceV2的自动化存储。
98
99## 使用场景
100
101### 在两个页面之间存储数据
102
103数据页面
104```ts
105// Sample.ets
106import { Type } from '@kit.ArkUI';
107
108// 数据中心
109@ObservedV2
110class SampleChild {
111  @Trace p1: number = 0;
112  p2: number = 10;
113}
114
115@ObservedV2
116export class Sample {
117  // 对于复杂对象需要@Type修饰,确保序列化成功
118  @Type(SampleChild)
119  @Trace f: SampleChild = new SampleChild();
120}
121```
122
123页面1
124```ts
125// Page1.ets
126import { PersistenceV2 } from '@kit.ArkUI';
127import { Sample } from '../Sample';
128
129// 接受序列化失败的回调
130PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
131  console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`);
132});
133
134@Entry
135@ComponentV2
136struct Page1 {
137  // 在PersistenceV2中创建一个key为Sample的键值对(如果存在,则返回PersistenceV2中的数据),并且和prop关联
138  // 对于需要换connect对象的prop属性,需要加@Local修饰(不建议对属性换connect的对象)
139  @Local prop: Sample = PersistenceV2.connect(Sample, () => new Sample())!;
140  pageStack: NavPathStack = new NavPathStack();
141
142  build() {
143    Navigation(this.pageStack) {
144      Column() {
145        Button('Go to page2')
146          .onClick(() => {
147            this.pageStack.pushPathByName('Page2', null);
148          })
149
150        Button('Page1 connect the key Sample')
151          .onClick(() => {
152            // 在PersistenceV2中创建一个key为Sample的键值对(如果存在,则返回PersistenceV2中的数据),并且和prop关联
153            // 不建议对prop属性换connect的对象
154            this.prop = PersistenceV2.connect(Sample, 'Sample', () => new Sample())!;
155          })
156
157        Button('Page1 remove the key Sample')
158          .onClick(() => {
159            // 从PersistenceV2中删除后,prop将不会再与key为Sample的值关联
160            PersistenceV2.remove(Sample);
161          })
162
163        Button('Page1 save the key Sample')
164          .onClick(() => {
165            // 如果处于connect状态,持久化key为Sample的键值对
166            PersistenceV2.save(Sample);
167          })
168
169        Text(`Page1 add 1 to prop.p1: ${this.prop.f.p1}`)
170          .fontSize(30)
171          .onClick(() => {
172            this.prop.f.p1++;
173          })
174
175        Text(`Page1 add 1 to prop.p2: ${this.prop.f.p2}`)
176          .fontSize(30)
177          .onClick(() => {
178            // 页面不刷新,但是p2的值改变了
179            this.prop.f.p2++;
180          })
181
182        // 获取当前PersistenceV2里面的所有key
183        Text(`all keys in PersistenceV2: ${PersistenceV2.keys()}`)
184          .fontSize(30)
185      }
186      }
187  }
188}
189```
190
191页面2
192```ts
193// Page2.ets
194import { PersistenceV2 } from '@kit.ArkUI';
195import { Sample } from '../Sample';
196
197@Builder
198export function Page2Builder() {
199  Page2()
200}
201
202@ComponentV2
203struct Page2 {
204  // 在PersistenceV2中创建一个key为Sample的键值对(如果存在,则返回PersistenceV2中的数据),并且和prop关联
205  // 对于需要换connect对象的prop属性,需要加@Local修饰(不建议对属性换connect的对象)
206  @Local prop: Sample = PersistenceV2.connect(Sample, () => new Sample())!;
207  pathStack: NavPathStack = new NavPathStack();
208
209  build() {
210    NavDestination() {
211      Column() {
212        Button('Page2 connect the key Sample1')
213          .onClick(() => {
214            // 在PersistenceV2中创建一个key为Sample1的键值对(如果存在,则返回PersistenceV2中的数据),并且和prop关联
215            // 不建议对prop属性换connect的对象
216            this.prop = PersistenceV2.connect(Sample, 'Sample1', () => new Sample())!;
217          })
218
219        Text(`Page2 add 1 to prop.p1: ${this.prop.f.p1}`)
220          .fontSize(30)
221          .onClick(() => {
222            this.prop.f.p1++;
223          })
224
225        Text(`Page2 add 1 to prop.p2: ${this.prop.f.p2}`)
226          .fontSize(30)
227          .onClick(() => {
228            // 页面不刷新,但是p2的值改变了;只有重新初始化才会改变
229            this.prop.f.p2++;
230          })
231
232        // 获取当前PersistenceV2里面的所有key
233        Text(`all keys in PersistenceV2: ${PersistenceV2.keys()}`)
234          .fontSize(30)
235      }
236    }
237    .onReady((context: NavDestinationContext) => {
238      this.pathStack = context.pathStack;
239    })
240  }
241}
242```
243使用Navigation时,需要添加配置系统路由表文件src/main/resources/base/profile/route_map.json,并替换pageSourceFile为Page2页面的路径,并且在module.json5中添加:"routerMap": "$profile:route_map"。
244```json
245{
246  "routerMap": [
247    {
248      "name": "Page2",
249      "pageSourceFile": "src/main/ets/pages/Page2.ets",
250      "buildFunction": "Page2Builder",
251      "data": {
252        "description" : "PersistenceV2 example"
253      }
254    }
255  ]
256}
257```
258
259### 使用globalConnect存储数据
260
261```ts
262import { PersistenceV2, Type, ConnectOptions } from '@kit.ArkUI';
263import { contextConstant } from '@kit.AbilityKit';
264
265// 接受序列化失败的回调
266PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
267  console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`);
268});
269
270@ObservedV2
271class SampleChild {
272  @Trace childId: number = 0;
273  groupId: number = 1;
274}
275
276@ObservedV2
277export class Sample {
278  // 对于复杂对象需要@Type修饰,确保序列化成功
279  @Type(SampleChild)
280  @Trace father: SampleChild = new SampleChild();
281}
282
283@Entry
284@ComponentV2
285struct Page1 {
286  @Local refresh: number = 0;
287  // key不传入尝试用为type的name作为key,加密参数不传入默认加密等级为EL2
288  @Local p: Sample = PersistenceV2.globalConnect({type: Sample, defaultCreator:() => new Sample()})!;
289
290  // 使用key:global1连接,传入加密等级为EL1
291  @Local p1: Sample = PersistenceV2.globalConnect({type: Sample, key:'global1', defaultCreator:() => new Sample(), areaMode: contextConstant.AreaMode.EL1})!;
292
293  // 使用key:global2连接,使用构造函数形式,加密参数不传入默认加密等级为EL2
294  options: ConnectOptions<Sample> = {type: Sample, key: 'global2', defaultCreator:() => new Sample()};
295  @Local p2: Sample = PersistenceV2.globalConnect(this.options)!;
296
297  // 使用key:global3连接,直接写加密数值,范围只能在0-4,否则运行会crash,例如加密设置为EL3
298  @Local p3: Sample = PersistenceV2.globalConnect({type: Sample, key:'global3', defaultCreator:() => new Sample(), areaMode: 3})!;
299
300  build() {
301    Column() {
302      /**************************** 显示数据 **************************/
303      // 被@Trace修饰的数据可以自动持久化进磁盘
304      Text('Key Sample: ' + this.p.father.childId.toString())
305        .onClick(()=> {
306          this.p.father.childId += 1;
307        })
308        .fontSize(25)
309        .fontColor(Color.Red)
310      Text('Key global1: ' + this.p1.father.childId.toString())
311        .onClick(()=> {
312          this.p1.father.childId += 1;
313        })
314        .fontSize(25)
315        .fontColor(Color.Red)
316      Text('Key global2: ' + this.p2.father.childId.toString())
317        .onClick(()=> {
318          this.p2.father.childId += 1;
319        })
320        .fontSize(25)
321        .fontColor(Color.Red)
322      Text('Key global3: ' + this.p3.father.childId.toString())
323        .onClick(()=> {
324          this.p3.father.childId += 1;
325        })
326        .fontSize(25)
327        .fontColor(Color.Red)
328      /**************************** keys接口 **************************/
329      // keys 本身不会刷新,需要借助状态变量刷新
330      Text('Persist keys: ' + PersistenceV2.keys().toString() + ' refresh: ' + this.refresh)
331        .onClick(() => {
332          this.refresh += 1;
333        })
334        .fontSize(25)
335
336      /**************************** remove接口 **************************/
337      Text('Remove key Sample: ' + 'refresh: ' + this.refresh)
338        .onClick(() => {
339          // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect
340          PersistenceV2.remove(Sample);
341          this.refresh += 1;
342        })
343        .fontSize(25)
344      Text('Remove key global1: ' + 'refresh: ' + this.refresh)
345        .onClick(() => {
346          // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect
347          PersistenceV2.remove('global1');
348          this.refresh += 1;
349        })
350        .fontSize(25)
351      Text('Remove key global2: ' + 'refresh: ' + this.refresh)
352        .onClick(() => {
353          // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect
354          PersistenceV2.remove('global2');
355          this.refresh += 1;
356        })
357        .fontSize(25)
358      Text('Remove key global3: ' + 'refresh: ' + this.refresh)
359        .onClick(() => {
360          // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect
361          PersistenceV2.remove('global3');
362          this.refresh += 1;
363        })
364        .fontSize(25)
365      /**************************** reConnect **************************/
366      // 重新连接也无法和之前的状态变量建立联系,因此无法保存数据
367      Text('ReConnect key global2: ' + 'refresh: ' + this.refresh)
368        .onClick(() => {
369          // 删除这个key,会导致和p失去联系,之后p无法存储,即使reconnect
370          PersistenceV2.globalConnect(this.options);
371          this.refresh += 1;
372        })
373        .fontSize(25)
374
375      /**************************** save接口 **************************/
376      Text('not save key Sample: ' + this.p.father.groupId.toString() + ' refresh: ' + this.refresh)
377        .onClick(() => {
378          // 未被@Trace保存的对象无法自动存储
379          this.p.father.groupId += 1;
380          this.refresh += 1;
381        })
382        .fontSize(25)
383      Text('save key Sample: ' + this.p.father.groupId.toString() + ' refresh: ' + this.refresh)
384        .onClick(() => {
385          // 未被@Trace保存的对象无法自动存储,需要调用key存储
386          this.p.father.groupId += 1;
387          PersistenceV2.save(Sample);
388          this.refresh += 1;
389        })
390        .fontSize(25)
391    }
392    .width('100%')
393  }
394}
395```
396
397### 在不同的module中使用connect和globalConnect
398
399**connect的存储路径需要注意以下两点:**
400
4011、connect使用module级别的存储路径,以最先启动的module的路径作为存储路径,从内存回写磁盘时会回写到第一个连接该module的路径。应用如果之后先从另一个module启动,则会以新module的路径作为存储路径。
402
4032、当不同module使用相同的key时,哪个module先启动,数据就为哪个module中保存的键值对,回写到对应的module中。
404
405**globalConnect的存储路径需要注意:**
406
407globalConnect虽然是应用级别的路径,但是可以设置不同的加密分区,不同加密分区即代表不同的存储路径。connect不支持设置加密分区,但是module自身切换加密级别时,module存储路径也会切换成对应加密分区路径。
408
409示例代码如下:开发者需要在项目基础上,新建一个module,并按照示例代码跳转到新module中。
410
411```ts
412// 模块1
413import { PersistenceV2, Type } from '@kit.ArkUI';
414import { contextConstant, common, Want } from '@kit.AbilityKit';
415
416// 接受序列化失败的回调
417PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
418  console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`);
419});
420
421@ObservedV2
422class SampleChild {
423  @Trace childId: number = 0;
424  groupId: number = 1;
425}
426
427@ObservedV2
428export class Sample {
429  // 对于复杂对象需要@Type修饰,确保序列化成功
430  @Type(SampleChild)
431  @Trace father: SampleChild = new SampleChild();
432}
433
434@Entry
435@ComponentV2
436struct Page1 {
437  @Local refresh: number = 0;
438  // 使用key:global1连接,传入加密等级为EL1
439  @Local p1: Sample = PersistenceV2.globalConnect({type: Sample, key:'globalConnect1', defaultCreator:() => new Sample()})!;
440
441  // 使用key:global2连接,使用构造函数形式,加密参数不传入默认加密等级为EL2
442  @Local p2: Sample = PersistenceV2.connect(Sample, 'connect2', () => new Sample())!;
443  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
444
445  build() {
446    Column() {
447      /**************************** 显示数据 **************************/
448      Text('Key globalConnect1: ' + this.p1.father.childId.toString())
449        .onClick(()=> {
450          this.p1.father.childId += 1;
451        })
452        .fontSize(25)
453        .fontColor(Color.Red)
454      Text('Key connect2: ' + this.p2.father.childId.toString())
455        .onClick(()=> {
456          this.p2.father.childId += 1;
457        })
458        .fontSize(25)
459        .fontColor(Color.Red)
460
461      /**************************** 跳转 **************************/
462      Button('跳转newModule').onClick(() => { // 不同module之间使用,建议使用globalConnect
463        let want: Want = {
464          deviceId: '', // deviceId为空代表本设备
465          bundleName: 'com.example.myPersistenceV2', // 在app.json5中查看
466          moduleName: 'newModule', // 在需要跳转的module的module.json5中查看,非必选参数
467          abilityName: 'NewModuleAbility',  // 跳转启动的ability,在跳转模块对应的ability.ets文件中查看
468          uri:'src/main/ets/pages/Index'
469        }
470        // context为调用方UIAbility的UIAbilityContext
471        this.context.startAbility(want).then(() => {
472          console.info('start ability success');
473        }).catch((err: Error) => {
474          console.error(`start ability failed. code is ${err.name}, message is ${err.message}`);
475        })
476      })
477    }
478    .width('100%')
479    .borderWidth(3)
480    .borderColor(Color.Blue)
481    .margin({top: 5, bottom: 5})
482  }
483}
484```
485
486```ts
487// 模块2
488import { PersistenceV2, Type } from '@kit.ArkUI';
489import { contextConstant } from '@kit.AbilityKit';
490
491// 接受序列化失败的回调
492PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
493  console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`);
494});
495
496@ObservedV2
497class SampleChild {
498  @Trace childId: number = 0;
499  groupId: number = 1;
500}
501
502@ObservedV2
503export class Sample {
504  // 对于复杂对象需要@Type修饰,确保序列化成功
505  @Type(SampleChild)
506  @Trace father: SampleChild = new SampleChild();
507}
508
509@Entry
510@ComponentV2
511struct Page1 {
512  @Local a: number = 0;
513  // 使用key:global1连接,传入加密等级为EL1
514  @Local p1: Sample = PersistenceV2.globalConnect({type: Sample, key:'globalConnect1', defaultCreator:() => new Sample()})!;
515
516  // 使用key:global2连接,使用构造函数形式,加密参数不传入默认加密等级为EL2
517  @Local p2: Sample = PersistenceV2.connect(Sample, 'connect2', () => new Sample())!;
518
519  build() {
520    Column() {
521      /**************************** 显示数据 **************************/
522      Text('Key globalConnect1: ' + this.p1.father.childId.toString())
523        .onClick(()=> {
524          this.p1.father.childId += 1;
525        })
526        .fontSize(25)
527        .fontColor(Color.Red)
528      Text('Key connect2: ' + this.p2.father.childId.toString())
529        .onClick(()=> {
530          this.p2.father.childId += 1;
531        })
532        .fontSize(25)
533        .fontColor(Color.Red)
534    }
535    .width('100%')
536  }
537}
538```
539
540当开发者对newModule使用不同启动方式会有以下现象:
541
542*   开发者直接启动newModule,分别修改globalConnect1和connect2绑定的变量,例如将childId都改成5。
543* 应用退出并清空后台,启动模块entry,通过跳转按键启动newModule,会发现globalConnect1值为5,而connect2值为1未修改。
544* globalConnect为应用级别存储,对于一个key,整个应用在对应加密分区只有一份存储路径;connect为module级别的存储路径,会因为module的启动方式不同而在各自的加密分区对应不同的存储路径。
545
546## 使用建议
547
548建议开发者使用新接口globalConnect创建和获取数据。globalConnect的存储规格和内存规格一致,对于应用只有一份,并且支持设置加密级别,不需要去切换ability的加密才能设置数据的加密级别。当然如果开发者应用不涉及多模块,保持使用connect也不会有影响。
549
550### connect向globalConnect迁移实现
551
552```ts
553// 使用connect存储数据
554import { PersistenceV2, Type } from '@kit.ArkUI';
555
556// 接受序列化失败的回调
557PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
558  console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`);
559});
560
561@ObservedV2
562class SampleChild {
563  @Trace childId: number = 0;
564  groupId: number = 1;
565}
566
567@ObservedV2
568export class Sample {
569  // 对于复杂对象需要@Type修饰,确保序列化成功
570  @Type(SampleChild)
571  @Trace father: SampleChild = new SampleChild();
572}
573
574@Entry
575@ComponentV2
576struct Page1 {
577  @Local refresh: number = 0;
578  // 使用key:connect2存储
579  @Local p: Sample = PersistenceV2.connect(Sample, 'connect2', () => new Sample())!;
580
581  build() {
582    Column({space: 5}) {
583      /**************************** 显示数据 **************************/
584      Text('Key connect2: ' + this.p.father.childId.toString())
585        .onClick(() => {
586          this.p.father.childId += 1;
587        })
588        .fontSize(25)
589        .fontColor(Color.Red)
590
591      /**************************** save接口 **************************/
592      // 非状态变量需要借助状态变量refresh才能刷新
593      Text('save key Sample: ' + this.p.father.groupId.toString() + ' refresh:' + this.refresh)
594        .onClick(() => {
595          // 未被@Trace保存的对象无法自动存储,需要调用key存储
596          this.p.father.groupId += 1;
597          PersistenceV2.save('connect2');
598          this.refresh += 1
599        })
600        .fontSize(25)
601    }
602    .width('100%')
603  }
604}
605```
606
607```ts
608// 迁移到globalConnect
609import { PersistenceV2, Type } from '@kit.ArkUI';
610
611// 接受序列化失败的回调
612PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
613  console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`);
614});
615
616@ObservedV2
617class SampleChild {
618  @Trace childId: number = 0;
619  groupId: number = 1;
620}
621
622@ObservedV2
623export class Sample {
624  // 对于复杂对象需要@Type修饰,确保序列化成功
625  @Type(SampleChild)
626  @Trace father: SampleChild = new SampleChild();
627}
628
629// 用于判断是否完成数据迁移的辅助数据
630@ObservedV2
631class StorageState {
632  @Trace isCompleteMoving: boolean = false;
633}
634
635function move() {
636  let movingState = PersistenceV2.globalConnect({type: StorageState, defaultCreator: () => new StorageState()})!;
637  if (!movingState.isCompleteMoving) {
638    let p: Sample = PersistenceV2.connect(Sample, 'connect2', () => new Sample())!;
639    PersistenceV2.remove('connect2');
640    let p1 = PersistenceV2.globalConnect({type: Sample, key: 'connect2', defaultCreator: () => p})!;  // 使用默认构造函数也可以
641    // 赋值数据,@Trace修饰的会自动保存
642    p1.father = p.father;
643    // 将迁移标志设置为true
644    movingState.isCompleteMoving = true;
645  }
646}
647
648move();
649
650@Entry
651@ComponentV2
652struct Page1 {
653  @Local refresh: number = 0;
654  // 使用key:connect2存入数据
655  @Local p: Sample = PersistenceV2.globalConnect({type: Sample, key:'connect2', defaultCreator:() => new Sample()})!;
656
657  build() {
658    Column({space: 5}) {
659      /**************************** 显示数据 **************************/
660      Text('Key connect2: ' + this.p.father.childId.toString())
661        .onClick(() => {
662          this.p.father.childId += 1;
663        })
664        .fontSize(25)
665        .fontColor(Color.Red)
666
667      /**************************** save接口 **************************/
668      // 非状态变量需要借助状态变量refresh才能刷新
669      Text('save key connect2: ' + this.p.father.groupId.toString() + ' refresh:' + this.refresh)
670        .onClick(() => {
671          // 未被@Trace保存的对象无法自动存储,需要调用key存储
672          this.p.father.groupId += 1;
673          PersistenceV2.save('connect2');
674          this.refresh += 1
675        })
676        .fontSize(25)
677    }
678    .width('100%')
679  }
680}
681```
682
683connect向globalConnect迁移,需要将key绑定的value赋值给globalConnect进行存储,之后当自定义组件使用globalConnect连接时,globalConnect绑定的数据即为之前使用connect保存的数据,开发者可以自定义move函数,并将其放在合适位置迁移即可。