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.Set、collections.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函数,并将其放在合适位置迁移即可。