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

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

前言(必读)

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

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

图:AppStorage 全局状态管理架构------7 个核心键的写入来源与读取页面
#mermaid-svg-zsL8mVH1NuvaXVw4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zsL8mVH1NuvaXVw4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zsL8mVH1NuvaXVw4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zsL8mVH1NuvaXVw4 .error-icon{fill:#552222;}#mermaid-svg-zsL8mVH1NuvaXVw4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zsL8mVH1NuvaXVw4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zsL8mVH1NuvaXVw4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zsL8mVH1NuvaXVw4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zsL8mVH1NuvaXVw4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zsL8mVH1NuvaXVw4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zsL8mVH1NuvaXVw4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zsL8mVH1NuvaXVw4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zsL8mVH1NuvaXVw4 .marker.cross{stroke:#333333;}#mermaid-svg-zsL8mVH1NuvaXVw4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zsL8mVH1NuvaXVw4 p{margin:0;}#mermaid-svg-zsL8mVH1NuvaXVw4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zsL8mVH1NuvaXVw4 .cluster-label text{fill:#333;}#mermaid-svg-zsL8mVH1NuvaXVw4 .cluster-label span{color:#333;}#mermaid-svg-zsL8mVH1NuvaXVw4 .cluster-label span p{background-color:transparent;}#mermaid-svg-zsL8mVH1NuvaXVw4 .label text,#mermaid-svg-zsL8mVH1NuvaXVw4 span{fill:#333;color:#333;}#mermaid-svg-zsL8mVH1NuvaXVw4 .node rect,#mermaid-svg-zsL8mVH1NuvaXVw4 .node circle,#mermaid-svg-zsL8mVH1NuvaXVw4 .node ellipse,#mermaid-svg-zsL8mVH1NuvaXVw4 .node polygon,#mermaid-svg-zsL8mVH1NuvaXVw4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zsL8mVH1NuvaXVw4 .rough-node .label text,#mermaid-svg-zsL8mVH1NuvaXVw4 .node .label text,#mermaid-svg-zsL8mVH1NuvaXVw4 .image-shape .label,#mermaid-svg-zsL8mVH1NuvaXVw4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-zsL8mVH1NuvaXVw4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zsL8mVH1NuvaXVw4 .rough-node .label,#mermaid-svg-zsL8mVH1NuvaXVw4 .node .label,#mermaid-svg-zsL8mVH1NuvaXVw4 .image-shape .label,#mermaid-svg-zsL8mVH1NuvaXVw4 .icon-shape .label{text-align:center;}#mermaid-svg-zsL8mVH1NuvaXVw4 .node.clickable{cursor:pointer;}#mermaid-svg-zsL8mVH1NuvaXVw4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zsL8mVH1NuvaXVw4 .arrowheadPath{fill:#333333;}#mermaid-svg-zsL8mVH1NuvaXVw4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zsL8mVH1NuvaXVw4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zsL8mVH1NuvaXVw4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zsL8mVH1NuvaXVw4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zsL8mVH1NuvaXVw4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zsL8mVH1NuvaXVw4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zsL8mVH1NuvaXVw4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zsL8mVH1NuvaXVw4 .cluster text{fill:#333;}#mermaid-svg-zsL8mVH1NuvaXVw4 .cluster span{color:#333;}#mermaid-svg-zsL8mVH1NuvaXVw4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zsL8mVH1NuvaXVw4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zsL8mVH1NuvaXVw4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-zsL8mVH1NuvaXVw4 .icon-shape,#mermaid-svg-zsL8mVH1NuvaXVw4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zsL8mVH1NuvaXVw4 .icon-shape p,#mermaid-svg-zsL8mVH1NuvaXVw4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zsL8mVH1NuvaXVw4 .icon-shape .label rect,#mermaid-svg-zsL8mVH1NuvaXVw4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zsL8mVH1NuvaXVw4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zsL8mVH1NuvaXVw4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zsL8mVH1NuvaXVw4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} LoginPage 写入
LoginPage 写入
EntryAbility 写入
AppStorage 内存键值存储
currentUser: UserInfo
isLoggedIn: boolean
themeMode: string
statusBarHeight: number
currentArchiveId: number
analysisResult: AnalysisResult
coupleCode: string
所有页面读取
路由守卫检查
标题栏高度适配


一、AppStorage 核心 API

typescript 复制代码
// 写数据
AppStorage.setOrCreate<number>('key', value)

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

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

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


二、项目中全部 7 个 AppStorage 键

2.1 数据全景

typescript 复制代码
// 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') │
  │                              │                              │
typescript 复制代码
// 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 档案列表 → 报告详情

typescript 复制代码
// 方案: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
}

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

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

五、最佳实践

5.1 什么时候用 AppStorage

应该用 AppStorage:

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

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

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

不应该用 AppStorage:

typescript 复制代码
// 1. 列表数据(应走数据库)
AppStorage.setOrCreate('archives', bigArray)   // ❌

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

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

5.2 类型安全

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

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

5.3 初始化时机

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

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

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

六、AppStorage vs PersistentStorage

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

typescript 复制代码
// 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 全应用 + 持久化 永久 用户设置/偏好
typescript 复制代码
// @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 键的命名规范

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

typescript 复制代码
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

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

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

7.6 登出时清理 AppStorage

typescript 复制代码
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 本地状态

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

相关推荐
爱吃大芒果2 小时前
鸿蒙 7 新特性科普:什么是系统 Agent 智能体?
华为·harmonyos·智能体
listening7772 小时前
HarmonyOS 6.1 元服务实战:把电商卡片装进系统“负一屏”
华为·harmonyos
蓝速科技2 小时前
蓝速科技鸿蒙信创终端全场景落地实效展示
人工智能·科技·华为·harmonyos
子非鱼94272 小时前
10-Flutter调用鸿蒙原生能力前置课:MethodChannel和PlatformView准备
学习·flutter·华为·harmonyos
xyccstudio2 小时前
用 AI 审核 Skill 消灭“编译器不报错“的隐藏 Bug
harmonyos
星释3 小时前
鸿蒙智能体开发实战:38.鸿蒙壁纸大师 - 根据设备型号优化壁纸尺寸
华为·harmonyos
xd1855785553 小时前
鸿蒙PC时代的AI快递查询应用:基于鸿蒙Flutter框架的跨端创新实践
人工智能·flutter·华为·harmonyos·鸿蒙
FrameNotWork3 小时前
HarmonyOS 6.1 富文本与图文混排 — Span/ContainerSpan/ImageSpan组合拳
华为·harmonyos
爱吃大芒果3 小时前
鸿蒙 7 Agent 框架实战:搭建离线本地文件检索智能体
华为·harmonyos