鸿蒙实战:AppStorage 全局状态管理

前言(必读)

在这里插入图片描述

图:鸿蒙实战:AppStorage 全局状态管理(第 72 篇) 运行效果截图(HarmonyOS NEXT)

在跨页面共享数据时,如果每个页面都从数据库重新加载一遍,不仅代码冗余,性能也会受影响。HarmonyOS 提供了 AppStorage——应用级别的全局键值存储引擎,数据在内存中常驻,所有页面都能直接读写。本文从项目中的 7 个 AppStorage 键入手,详解全局状态管理的设计。

AppStorage全局状态管理示意图

图:AppStorage 全局状态管理架构——7 个核心键的写入来源与读取页面

LoginPage 写入

LoginPage 写入

EntryAbility 写入

AppStorage 内存键值存储

currentUser: UserInfo

isLoggedIn: boolean

themeMode: string

statusBarHeight: number

currentArchiveId: number

analysisResult: AnalysisResult

coupleCode: string

所有页面读取

路由守卫检查

标题栏高度适配


一、AppStorage 核心 API

// 写数据
AppStorage.setOrCreate<number>('key', value)

// 读数据
const value = AppStorage.get<number>('key') ?? defaultValue

// 删除
AppStorage.delete('key')
API 说明 区别
setOrCreate(key, value) 设置或创建键值 取代旧的 set,自动判断是否存在
get<T>(key) 读取数据 返回 T | undefined
delete(key) 删除键值 清理不需要的数据
keys() 获取所有键 调试用

重要特点:AppStorage 存储在内存中,应用退出后数据自动清除。它适合全局状态共享,不适合持久化(持久化用 RDB 数据库)。


二、项目中全部 7 个 AppStorage 键

2.1 数据全景

// AppStorage 键值全景(项目中完整使用)

// ── 用户身份 ──
AppStorage.setOrCreate<number>('user_id', 1)           // 用户 ID
AppStorage.setOrCreate<string>('user_name', '鹿鹿')     // 用户昵称

// ── 导航与页面跳转 ──
AppStorage.setOrCreate<number>('current_report_id', 0)  // 当前查看的报告 ID
AppStorage.setOrCreate<number>('current_archive_id', 0) // 当前查看的档案 ID

// ── 拍照流程 ──
AppStorage.setOrCreate<string>('capture_image_path', '')// 拍照临时文件路径

// ── 分析结果 ──
AppStorage.setOrCreate<Record<string, Object>>('analysis_result', {})  // 分析结果对象

// ── 伴侣关系 ──
AppStorage.setOrCreate<string>('couple_partner_name', '') // 伴侣昵称
AppStorage.setOrCreate<string>('couple_bound', '1')       // 是否已绑定

2.2 各键用途速查

键名 类型 写入位置 读取位置 用途
user_id number LoginPage 所有需要 userId 的页面 数据库查询参数
user_name string LoginPage HomePage/MePage/TapSharePage/SharePage 用户显示名
current_report_id number HomePage/ArchivePage ReportDetailPage 跳转报告详情
current_archive_id number ArchivePage ReportDetailPage 跳转档案详情
capture_image_path string CapturePage AnalyzingPage 传递拍照路径
analysis_result object AnalyzingPage ReportDetailPage/SharePage/TapSharePage 分析结果共享
couple_partner_name string CoupleHomePage MePage/DuetReportPage 伴侣显示名
couple_bound string CoupleHomePage CoupleHomePage 是否绑定标记

三、典型数据流

3.1 登录 → 首页

LoginPage                     AppStorage                    HomePage
  │                              │                              │
  │  login()                      │                              │
  │  ├─ setOrCreate('user_id',…)  │                              │
  │  ├─ setOrCreate('user_name',…)│                              │
  │                              │                              │
  │    router.replaceUrl()       │        aboutToAppear()        │
  │  ─────────────────────────►  │  ◄────────────────────────┐   │
  │                              │     AppStorage.get('user_name') │
  │                              │                              │
// LoginPage.ets — 写入
AppStorage.setOrCreate<number>('user_id', user.id)
AppStorage.setOrCreate<string>('user_name', user.name)
this.getUIContext().getRouter().replaceUrl({ url: 'pages/HomePage' })

// HomePage.ets — 读取
aboutToAppear(): void {
  const name = AppStorage.get<string>('user_name')
  if (name) this.userName = name
}

3.2 拍照 → 分析 → 报告

CapturePage       →     AnalyzingPage       →     ReportDetailPage
    │                       │                        │
    │ save temp image       │                        │
    │ setOrCreate(          │                        │
    │  'capture_image_path')│                        │
    │                       │                        │
    │                       │ 分析完成后              │
    │                       │ setOrCreate(           │
    │                       │  'analysis_result', …) │
    │                       │                        │
    │                       │                        │ AppStorage.get(
    │                       │                        │  'analysis_result')
    │                       │                        │ AppStorage.get(
    │                       │                        │  'current_report_id')

3.3 档案列表 → 报告详情

// 方案:AppStorage + router.params 双重传递
// ArchivePage.ets
.onClick(() => {
  AppStorage.setOrCreate<number>('current_archive_id', item.id)
  this.getUIContext().getRouter().pushUrl({
    url: 'pages/ReportDetailPage',
    params: { archiveId: item.id }
  })
})

// ReportDetailPage.ets — 接收端兜底逻辑
aboutToAppear(): void {
  // 优先从 router.params 读取
  const params = this.getUIContext().getRouter().getParams()
  // 再从 AppStorage 兜底
  const archivedId = AppStorage.get<number>('current_archive_id') ?? 0
}

四、与其他存储方案的对比

// 本项目三层存储体系
┌─────────────────────────────────────────────────┐
│  第一层:AppStorage(内存)                       │
│  特点:极快、非持久化、跨页面                      │
│  用途:userId / 当前跳转 ID / 临时数据            │
├─────────────────────────────────────────────────┤
│  第二层:@State + @Prop(组件内)                 │
│  特点:自动触发 UI 重渲染                         │
│  用途:页面局部状态                              │
├─────────────────────────────────────────────────┤
│  第三层:RDB 数据库(SQLite)                     │
│  特点:持久化、复杂查询、CRUD                      │
│  用途:用户数据 / 笔迹记录 / 报告                  │
└─────────────────────────────────────────────────┘
维度 AppStorage 数据库 (RDB) @State
存储位置 内存 磁盘文件 组件实例
生命周期 应用运行期间 永久 组件生命周期
读写性能 ns 级 ms 级 自动刷新
跨页面访问 ✅ 所有页面 ✅ 所有页面 ❌ 仅当前组件
容量限制 无(受内存限制) 数百 MB 小(UI 状态)
典型场景 跨页面传 ID 持久化数据 按钮动画状态

五、最佳实践

5.1 什么时候用 AppStorage

应该用 AppStorage:

// 1. 跨页面传递 ID(最佳场景)
AppStorage.setOrCreate('current_report_id', reportId)

// 2. 全局用户身份
AppStorage.get<number>('user_id')

// 3. 临时流程数据(拍照→分析→报告)
AppStorage.setOrCreate('analyse_result', result)

不应该用 AppStorage:

// 1. 列表数据(应走数据库)
AppStorage.setOrCreate('archives', bigArray)   // ❌

// 2. 敏感凭证(AppStorage 在内存未加密)
AppStorage.setOrCreate('token', jwtString)      // ❌

// 3. UI 局部状态(应用 @State)
AppStorage.setOrCreate('btnScale', 0.95)        // ❌

5.2 类型安全

// 读取时使用泛型确保类型安全
const userId = AppStorage.get<number>('user_id') ?? 0

// 写入时使用同名泛型
AppStorage.setOrCreate<number>('user_id', userId)

5.3 初始化时机

AppStorage 数据的写入时机和读取时机需确保时序正确:

// ❌ 可能出错:在 AppStorage 写入前读取
aboutToAppear() {
  const fromApp = AppStorage.get<string>('user_name')
  // 如果用户还没有登录过,这里返回 undefined
}

// ✅ 正确:提供默认值兜底
aboutToAppear() {
  const fromApp = AppStorage.get<string>('user_name') ?? '默认名称'
}

六、AppStorage vs PersistentStorage

HarmonyOS 还提供了 PersistentStorage,它与 AppStorage 类似但支持持久化到文件:

// PersistentStorage 用法
PersistentStorage.persistProp('user_name', '')

// 之后可以用 AppStorage 访问
AppStorage.get<string>('user_name')
对比 AppStorage PersistentStorage
持久化 ❌ 内存级 ✅ 文件级
跨应用重启 ❌ 清空 ✅ 保留
初始化 即时 需要在 UI 线程前初始化
本项目使用 ✅ 主力 ❌ 未使用

本项目控制在 AppStorage + RDB 两层,没有使用 PersistentStorage,因为需要持久化的数据(用户、档案、报告)已由 RDB 管理,AppStorage 只用作内存缓存层。


七、与 @Provide / @Consume 的对比

特性 AppStorage @Provide / @Consume
作用域 全局(任意页面) 组件树(父→子)
类型安全 需手动泛型 自动推导
写入触发刷新 不自动触发 自动触发
跨页面 ❌ 仅组件树
适合 页面间导航 ID 父子组件通信

八、注意事项与常见问题

8.1 开发注意事项

在正式开发前,建议按以下步骤完成环境准备与前置检查:

  1. 版本确认:检查 DevEco Studio 与 SDK 版本,确保满足目标 API Level 要求
  2. 权限声明:在 module.json5requestPermissions 字段中提前声明所有需要的系统权限
  3. 设备能力检查:调用前验证设备是否支持目标能力(相机、NFC、传感器等)
  4. 异步封装:所有耗时操作(数据库、文件 I/O、网络请求)统一使用 async/await 处理
  5. 资源释放:在组件 aboutToDisappear() 生命周期钩子中及时释放系统资源,防止内存泄漏

8.2 常见错误与解决方案

常见问题快速排查表:

问题类型 排查方向 参考方法
应用崩溃 查看 hilog 错误日志 hilog.error(TAG, "...", e.message)
状态丢失 检查 AppStorage 键名拼写 统一使用常量管理键名
动画不流畅 避免在 animateTo 回调中执行 I/O 动画与数据操作分离

总结

  • AppStorage 核心 APIsetOrCreate / get / delete 三步操作
  • 项目 7 个 AppStorage 键:user_id / user_name / report_id / archive_id / image_path / analysis_result / couple
  • 三层存储体系:AppStorage(内存)→ @State(组件)→ RDB(持久化)
  • 典型数据流:登录→首页、拍照→分析→报告、档案→报告详情
  • 与 @State / RDB / PersistentStorage 对比:各司其职
  • 最佳实践:传 ID 而非传对象、类型安全、默认值兜底

下一篇将讲解 @State 本地状态与 UI 刷新


📌 收藏提示:AppStorage 传 ID 而非传对象——这是全局状态管理的核心原则。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:


七、AppStorage 深度实践

7.1 三种状态存储的对比选型

存储方式 作用域 生命周期 适合场景
@State 组件内部 组件生命周期 临时 UI 状态
LocalStorage 页面内/组件树 页面生命周期 页面级共享数据
AppStorage 全应用 应用运行期间 跨页面全局数据
Preferences 全应用 + 持久化 永久 用户设置/偏好

7.2 @StorageLink vs @StorageProp

// @StorageProp: 单向同步(AppStorage → 组件)
@StorageProp('themeMode') themeMode: string = 'light';
// 修改 this.themeMode 只影响本组件,不写回 AppStorage

// @StorageLink: 双向同步(AppStorage ↔ 组件)
@StorageLink('themeMode') themeMode: string = 'light';
// 修改 this.themeMode 会自动同步到 AppStorage,所有订阅者刷新

选择原则:只读取全局状态用 @StorageProp,需要写回用 @StorageLink

7.3 AppStorage 键的命名规范

项目统一用常量类管理所有键名,避免字符串拼写错误:

class StorageKeys {
  static readonly CURRENT_USER = 'currentUser';
  static readonly IS_LOGGED_IN = 'isLoggedIn';
  static readonly STATUS_BAR_HEIGHT = 'statusBarHeight';
  static readonly ANALYSIS_RESULT = 'analysisResult';
  static readonly COUPLE_CODE = 'coupleCode';
}

// 使用常量而非字面字符串
AppStorage.set(StorageKeys.CURRENT_USER, user);

7.4 性能注意事项

  • 避免存储大型对象:AppStorage 适合存 ID、标志位等轻量数据,图片 PixelMap 等大对象应通过数据库或文件传递
  • 避免频繁 set:高频更新(如滑动进度)不应写 AppStorage,会触发全局刷新
  • 及时清理:登出时应清除用户相关的 key,避免内存泄漏

7.5 AppStorage 与 PersistentStorage 的配合

AppStorage 是内存存储,应用退出后数据清除。如需持久化,配合 PersistentStorage

// 在 EntryAbility.onCreate 中初始化持久化
PersistentStorage.persistProp('themeMode', 'light');
PersistentStorage.persistProp('language', 'zh');

// 此后 AppStorage 的同名 key 会自动同步到磁盘
// 应用重启后仍能读取之前的设置

7.6 登出时清理 AppStorage

function logout() {
  // 清除用户相关状态
  AppStorage.delete(StorageKeys.CURRENT_USER);
  AppStorage.set(StorageKeys.IS_LOGGED_IN, false);
  AppStorage.delete(StorageKeys.ANALYSIS_RESULT);
  AppStorage.delete(StorageKeys.COUPLE_CODE);
  // 跳转登录页
  router.pushUrl({ url: 'pages/LoginPage' });
}

小结:AppStorage 适合「应用级别的轻量共享状态」,PersistentStorage 负责持久化,@State 管理组件内部临时状态,三者分层配合是 HarmonyOS 状态管理的标准模式。

更多关于状态管理的详细内容,参见第73篇 @State 本地状态

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!

Logo

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

更多推荐