一、什么是持久化存储

在鸿蒙(HarmonyOS)应用开发中,持久化存储指的是将 UI 相关的状态数据保存到磁盘,使得应用重启后这些数据能够自动恢复。持久化存储与普通文件存储不同,它专为状态管理设计,与组件生命周期、装饰器深度集成,开发者无需手动读写文件即可实现状态保活。

持久化方案分为两代:V1 版本的 PersistentStorage(API 9 引入)和 V2 版本的 PersistenceV2(API 12 引入),后者在性能、容量和开发体验上全面升级。

二、V1 持久化方案:PersistentStorage

2.1 核心概念

PersistentStorage 是一个应用全局单例,负责持久化 UI 状态数据。它通过 persistProp 方法设置初始值并绑定键名,UI 组件通过 @StorageLink@StorageProp 装饰器与该键建立连接。

  • 数据容量:单 key 不超过 2KB
  • 操作方式:同步磁盘写入,可能在主线程产生卡顿。
  • 路径隔离:仅当前 module 可见。

2.2 完整使用示例

// 1. 在应用入口或必要处持久化初始值
PersistentStorage.persistProp('userScore', 0);

@Entry
@Component
struct ScoreBoard {
  // @StorageLink 双向绑定:UI 修改会自动写回磁盘
  @StorageLink('userScore') score: number = 0;

  build() {
    Column({ space: 16 }) {
      Text(`当前积分:${this.score}`)
        .fontSize(24)

      Button('增加积分')
        .onClick(() => {
          this.score += 10;
        })

      Button('重置积分')
        .onClick(() => {
          this.score = 0;
        })
    }
    .padding(20)
    .width('100%')
    .height('100%')
  }
}

运行效果:

在这里插入图片描述

运行说明:修改积分并重启应用后,userScore 的值为修改后的值,而不是初始值。

必须在 UI 实例初始化成功后(即 loadContent 回调中)调用 这一重要约束。根据官方文档,早于该时机调用会导致持久化失败。

2.3 PersistentStorage 的适用场景

  • 保存少量全局配置项(如用户偏好、主题选择)。
  • 简单游戏积分、上次浏览位置等轻量数据。
  • 兼容旧项目(API 9–12 范围内)。

三、V2 持久化方案:PersistenceV2

3.1 核心概念

PersistenceV2 是专为状态管理 V2 设计的新一代持久化方案,它支持:

  • 通过 PersistenceV2.connect(type: TypeConstructorWithArgs<T>, keyOrDefaultCreator?, defaultCreator?) 连接一个持久化键。
  • 通过 PersistenceV2.globalConnect(key, defaultValue) 跨 module 共享(应用级)。
  • PersistenceV2 在 API 12 ~ API 22 版本中存在明确限制:不支持直接持久化基本数据类型(如 string、number、boolean),也不支持容器类型(如 Array、Map、Set)以及内置构造对象(如 Date、Number)。从 API 23 开始,支持集合类型(ArrayMapSetDate)、@Sendable 对象、循环引用对象,以及单个 key 超过 8KB 的大数据量。

3.2 基本使用

import { PersistenceV2 } from '@kit.ArkUI';

@ObservedV2
class ThemeConfig {
  @Trace themeMode: string = 'light';
}

@Entry
@ComponentV2
struct SettingsPage {
  @Local themeConfig: ThemeConfig = PersistenceV2.connect(ThemeConfig, () => new ThemeConfig())!;

  build() {
    Column({ space: 20 }) {
      Text(`当前主题:${this.themeConfig.themeMode == 'light' ? '浅色模式' : '深色模式'}`)
        .fontSize(20)
        .fontColor(Color.Red)

      Button('切换为深色模式')
        .onClick(() => {
          this.themeConfig.themeMode = 'dark';
        })

      Button('切换为浅色模式')
        .onClick(() => {
          this.themeConfig.themeMode = 'light';
        })
    }
    .padding(20)
    .width('100%')
    .height('100%')
    .backgroundColor(this.themeConfig.themeMode == 'light' ? Color.White : Color.Black)
  }
}

运行效果:

在这里插入图片描述

3.3 高级用法:集合类型持久化

import { PersistenceV2 } from '@kit.ArkUI';

@Entry
@ComponentV2
struct FavoritesPage {
  @Local
  favoriteIds: number[] = (PersistenceV2.globalConnect({
    type: Array<number>,
    defaultCreator: () => []
  }) ?? []);

  build() {
    Column({ space: 12 }) {
      Text(`已收藏的文章数:${this.favoriteIds.length}`)
        .fontSize(20)

      Button('添加收藏 (ID=1001)')
        .width("80%")
        .onClick(() => {
          this.favoriteIds = [...this.favoriteIds, 1001];
        })

      Button('清空收藏')
        .width("80%")
        .onClick(() => {
          this.favoriteIds = [];
        })
    }
    .padding(20)
    .width("100%")
  }
}

运行效果:

在这里插入图片描述

上面的代码存在问题,虽然点击添加收藏按钮,收藏数量会实时变化,但是重新启动应用后,已收藏的文章数量清零了。这是因为 this.favoriteIds = [...this.favoriteIds, 1001] 会创建一个全新的数组对象,切断了与 PersistenceV2 的连接。

PersistenceV2.globalConnect() 返回的对象是框架代理后的可观察对象。当咱们用 = 整体赋值一个新数组时:

✅ @Local 检测到引用变化 → UI 刷新
❌ 新数组与 PersistenceV2 失去连接 → 不会触发自动持久化
❌ 应用重启后,PersistenceV2 读到的仍是旧数据

怎么解决这个问题呢?咱们可以利用 @ObservedV2 + @Trace 装饰器,让数组作为 class 的 属性被观测,而不是直接作为顶层类型持久化。修改后的代码如下:

import { PersistenceV2 } from '@kit.ArkUI';

// 1. 用 class 包装数组,@Trace 追踪变化
@ObservedV2
class FavoriteIds {
  @Trace
  ids: number[] = [];
}

@Entry
@ComponentV2
struct FavoritesPage {
  // 2. globalConnect 连接 class 实例
  @Local
  favoriteData: FavoriteIds = (PersistenceV2.globalConnect({
    type: FavoriteIds,
    defaultCreator: () => new FavoriteIds()
  }) ?? new FavoriteIds());

  build() {
    Column({ space: 12 }) {
      Text(`已收藏的文章数:${this.favoriteData.ids.length}`)
        .fontSize(20)

      Button('添加收藏 (ID=1001)')
        .width('80%')
        .onClick(() => {
          // 3. 修改 @Trace 属性的值 → 触发自动持久化
          this.favoriteData.ids = [...this.favoriteData.ids, 1001];
        })

      Button('清空收藏')
        .width('80%')
        .onClick(() => {
          this.favoriteData.ids = [];
        })
    }
    .padding(20)
    .width("100%")
  }
}

修改后的数据流向图:

应用重启启动
    ↓
PersistenceV2.globalConnect({ type: FavoriteIds })
    ↓ 从磁盘读取持久化数据
favoriteData = 框架代理后的 FavoriteIds 实例
    ↓
用户点击按钮 → this.favoriteData.ids = [...newArray]
    ↓
@Trace 检测到 ids 属性变化
    ↓
┌─────────────────────┐
│ ① → UI 自动刷新 ✅  │
│ ② → 自动写入磁盘 ✅ │
└─────────────────────┘

3.4 PersistenceV2 的优势

  • 丰富的数据类型:数组、Map、Set、Date 原生支持,无需手动序列化。
  • 大数据支持:单 key 可存储超过 8KB(API 23+)。
  • 全局共享:通过 globalConnect 可在不同 HAP/Module 间共享同一持久化数据。
  • 自管理序列化:状态管理引擎自动完成序列化与反序列化。

四、preferences

除了 PersistentStoragePersistenceV2,鸿蒙还提供了 preferences(首选项)模块,用于轻量级键值对数据的持久化存储。它属于 @kit.ArkData 套件,适合存储用户配置、设置项等不要求与 UI 状态管理强绑定的数据。

4.1 preferences 核心特点

  • 轻量级:以键值对形式存储,适合存储简单的用户配置数据。
  • 异步接口:所有操作(读取、写入、刷新)均返回 Promise,不会阻塞 UI 线程。
  • 手动序列化:与状态管理装饰器解耦,开发者需自行读取并赋值给状态变量。
  • 文件级隔离:不同的 preferences 文件(通过名称区分)可以管理不同类别的配置。

4.2 使用示例

以下示例演示如何使用 preferences 实现主题切换功能,将用户选择的主题模式持久化到本地,并在应用启动时恢复:

// 导入 preferences 模块,用于轻量级键值对数据持久化存储(如用户配置)
import { preferences } from '@kit.ArkData';
// 导入 AbilityKit 中的 common 模块,用于获取 UIAbilityContext,以便访问应用上下文
import { common } from '@kit.AbilityKit';

@Entry
@ComponentV2
struct Index {
  @Local themeMode: string = 'light';

  // 获取当前组件的 UIAbility 上下文对象
  // 通过 getUIContext().getHostContext() 获取宿主 Ability 的上下文
  // as common.UIAbilityContext 进行类型断言,使其具备操作应用数据的能力
  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;

  async aboutToAppear() {
    // 获取(或创建)名为 'app_settings' 的首选项存储实例
    const store = await preferences.getPreferences(this.context, 'app_settings');
    // 从存储中读取 'themeMode' 键对应的值,若不存在则使用默认值 'light'
    // get() 返回 Promise<ValueType>,必须用 await 获取实际值
    this.themeMode = await store.get('themeMode', 'light') as string;
  }

  // switchTheme 方法:切换主题模式,并将新值持久化到本地存储
  // @param newMode - 新的主题模式字符串('light' 或 'dark')
  async switchTheme(newMode: string) {
    // 获取首选项存储实例
    const store = await preferences.getPreferences(this.context, 'app_settings');
    // 将新的主题模式写入存储(键为 'themeMode',值为 newMode)
    await store.put('themeMode', newMode);
    // 手动刷新(flush)数据到磁盘,确保数据真正持久化保存
    await store.flush();
    // 更新组件内部的 themeMode 状态变量,触发 UI 刷新
    this.themeMode = newMode;
  }

  build() {
    Column({ space: 20 }) {
      Text(`当前主题:${this.themeMode == 'light' ? '浅色模式' : '深色模式'}`)
        .fontSize(20)
        .fontColor(Color.Red)

      Button('切换为深色模式')
        .onClick(() => {
          this.switchTheme('dark')
        })

      Button('切换为浅色模式')
        .onClick(() => {
          this.switchTheme('light')
        })
    }
    .padding(20)
    .width('100%')
    .height('100%')
    .backgroundColor(this.themeMode == 'light' ? Color.White : Color.Black)
  }
}

运行效果:

在这里插入图片描述

4.3 preferences 与 PersistenceV2 的对比

维度 preferences PersistenceV2
设计定位 轻量级键值对存储 状态管理 V2 的内置持久化引擎
与状态管理集成 手动读写 自动序列化/反序列化,与 @StorageProp 集成
数据类型 基本类型(string、number、boolean) 对象、集合、Sendable、循环引用(API 23+)
容量 适合少量配置项 支持大数据量(API 23+ 单 key >8KB)
跨 Module 需自行管理文件路径 内置 module 级 / 应用级 connect

4.4 使用建议

  • 偏好设置/用户配置:优先使用 preferences,它更轻量,不需要引入状态管理 V2 的特性。
  • UI 状态持久化:当需要持久化与 UI 绑定的数据(如列表项选中状态、表单输入)时,推荐使用 PersistenceV2 以简化代码。
  • 混合使用:两个方案不冲突,可以在同一应用中共存——用 preferences 存配置,用 PersistenceV2 存 UI 状态。

五、PersistenceV2 VS PersistentStorage 选型建议与迁移指南

5.1 如何选择

场景 推荐方案 理由
新项目(API 12+) PersistenceV2 性能好、功能全、开发体验佳
旧项目(API 9–11) PersistentStorage 兼容已有代码,零迁移成本
需要跨 Module 共享 PersistenceV2 globalConnect 路径隔离更灵活
存储大数据/集合类型 PersistenceV2(API 23+) 超 2KB 或数组数据必须用 V2

5.2 V1→V2 迁移步骤

  1. 将组件装饰器从 @Component 替换为 @ComponentV2
  2. @StorageLink / @StorageProp 替换为 V2 版本(注意 API 签名差异)。
  3. 替换 PersistentStorage.persistProp()PersistenceV2.connect()
  4. 注意:V1 和 V2 的状态管理装饰器不能混用在同一个组件中,建议逐个组件迁移。

5.3 完整迁移示例

// 改造前(V1)
PersistentStorage.persistProp('userName', 'Guest');

@Entry
@Component
struct Profile {
  @StorageLink('userName') name: string = 'Guest';
  // ...
}

// 改造后(V2)
@Entry
@ComponentV2
struct Profile {
  @Local name: string = PersistenceV2.connect('userName', 'Guest');
  // ...
}

六、最佳实践要点

  • 不要持久化过多数据:持久化存储适合 UI 状态,不适合业务大文件;大文件应使用文件管理 API。
  • 避免频繁写入:虽然 V2 是异步写入,但高频写操作会触发多次磁盘 I/O,建议合并写操作。
  • 默认值处理connect 的第二个参数是默认值,仅在磁盘无该 key 时生效;如需重置,需主动调用 PersistenceV2.remove(key)
  • 测试冷启动恢复:务必在真机或 DevEco Studio 模拟器上做冷启动测试,确认数据恢复逻辑正确。

七、总结

鸿蒙应用开发中的持久化方案从 V1 的 PersistentStorage 演进到 V2 的 PersistenceV2,核心变化在于:性能更优、数据类型更丰富、与状态管理引擎集成更紧密。对于新开发的项目,应优先选择 V2 方案;对于存量 V1 项目,可按组件粒度逐步迁移。

持久化存储只是鸿蒙状态管理的一部分,建议结合 @State@Prop@Provide/@Consume 等装饰器综合使用,打造流畅且数据不丢失的用户体验。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐