HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(三十二):【数据一致性】个人档案的“三重持久化”修复——让偏好、健康与头像真正同步
2026/6/10 0:55:36 网站建设 项目流程

HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(三十二):【数据一致性】个人档案的“三重持久化”修复——让偏好、健康与头像真正同步

摘要:你的 App 在“我的”Tab 修改了身高体重,切换到健康 Tab 也确实刷新了营养数据。但当你完全关掉 App 再重启——所有档案恢复默认,头像回到灰色占位。你怀疑是 Preferences 没存上,或数据库没写进去。但真正的根源更隐蔽:数据不是没存,而是存错了地方——ProfileViewModel 和 AuthViewModel 各有一套独立的“事实版本”,ProfileViewModel 启动时从未从 AuthViewModel 加载数据,导致 UI 始终展示默认值。本文将复盘这场数据一致性追查的全过程:从数据源梳理到内存同步修复,再到冷启动恢复验证。最终,我们用“单一数据源 + 内存同步 + 持久化兜底”三重保障,彻底解决档案持久化问题。


一、引言:一个“幽灵”般的 Bug

在第 31 篇发布后,一个用户在测试群反馈了一个诡异的现象:

操作步骤预期实际
在“我的”Tab 修改身高为 175cm保存成功✅ 保存成功
切换到健康 Tab营养数据按身高 175cm 重新计算✅ 正确刷新
完全关掉 App 再重启身高仍然是 175cm身高恢复默认 170cm
进入编辑页查看头像头像显示正常✅ 正常
返回“我的”Tab头像显示正常头像变成灰色默认图标

两个表面症状指向同一个底层问题:数据持久化了,但恢复时没读对地方


二、数据源审计:同一个数据,三套“事实版本”

要理解这个 Bug,必须先理清系统中到底有多少套“用户数据”:

数据源存储位置读写接口使用者
RelationalStorelocal_usersstoreHelper.executeSqlAuthViewModel
AuthViewModel内存(@Trace属性)initLocalAuth()/updateProfile()ProfileEditPage
ProfileViewModel内存(@Trace属性)healthProfile/preferenceProfileTabContent
Preferences本地 KV 文件preferences.get/putProfileTabContent(头像/昵称)

🖥️ UI 层

🧠 内存层

💾 持久化层

initLocalAuth() 加载

updateProfile() 写入

直接调用

写入

读取

读取(修复前:未加载)

间接依赖

修复前:从未同步

RelationalStore
local_users 表

Preferences
profile_store

AuthViewModel
@Trace 属性

ProfileViewModel
healthProfile + preference

ProfileEditPage
头像/昵称编辑

ProfileTabContent
档案 + 偏好展示

HealthTabContent
营养数据展示

图一解读:问题就出在ProfileViewModelAuthViewModel之间的那条虚线——它们各自维护了一套“用户数据”,但从未同步。AuthViewModel在冷启动时从数据库加载了最新数据,但ProfileViewModel始终使用自己的默认值。UI 从ProfileViewModel读取数据展示,自然看不到最新值。


三、根本原因:ProfileViewModel 的“数据孤岛”

3.1 问题链路推演

冷启动流程: AuthViewModel.initLocalAuth() → 从 local_users 表加载最新数据 → this.height = 175 ✅ → this.age = 36 ✅ ProfileTabContent.aboutToAppear() → 创建 ProfileViewModel(默认值) → this.vm.healthProfile.height = 170 ❌ → this.vm.healthProfile.age = 30 ❌ → UI 展示 170cm / 30 岁 ❌

ProfileViewModel在初始化时使用了defaultHealthProfile,从未尝试从AuthViewModel或数据库中加载真实数据。它像一个“数据孤岛”——保存时能把数据写入数据库,但加载时完全忽略数据库中已有的数据。

3.2 为什么编辑页正常而展示页异常?

页面数据来源冷启动后
ProfileEditPage直接从Preferences读取✅ 正确
ProfileTabContentProfileViewModel读取❌ 显示默认值
HealthTabContentHealthDashboardViewModelauthViewModel.toUserHealthProfile()读取✅ 正确(修复后)

三个页面对同一份用户数据,有三条不同的读取路径。其中两条路径经过AuthViewModel(正确),一条路径经过ProfileViewModel(错误)。这就是为什么编辑页和健康页正常,但“我的”Tab 异常的原因。


四、修复方案:三重保障确保数据一致

修复 1:ProfileViewModel 新增loadFromAuthViewModel()方法

ProfileViewModel在初始化时从AuthViewModel加载数据:

// ProfileViewModel.ets(新增方法)loadFromAuthViewModel():void{// 1. 从 authViewModel 同步健康档案constgender=authViewModel.gender==='female'?Gender.FEMALE:Gender.MALE;constactivityLevel=this.parseActivityLevelFromString(authViewModel.activityLevel);this.healthProfile=makeHealthProfile(gender,authViewModel.age,authViewModel.height,authViewModel.weight,activityLevel);// 2. 从 authViewModel 同步偏好设置this.preference=makeUserPreference([...authViewModel.favoriteTags],[...authViewModel.allergies],authViewModel.maxCalories);console.info('[ProfileVM] 已从 AuthViewModel 同步数据');}

关键设计:使用makeHealthProfilemakeUserPreference工厂函数创建全新对象引用——这是@ObservedV2检测变化并触发 UI 刷新的必要条件。

修复 2:save()中同步更新 AuthViewModel

ProfileViewModel.save()中,确保数据库写入成功后立即同步更新AuthViewModel的内存状态:

// ProfileViewModel.ets —— save() 方法修正部分asyncsave():Promise<void>{// ... 原有云端同步逻辑 ...if(success){// ★ 关键修复:同步更新 authViewModel 内存状态constgenderStr=this.healthProfile.gender===Gender.MALE?'male':'female';letactivityLevelStr='light';switch(this.healthProfile.activityLevel){caseActivityLevel.SEDENTARY:activityLevelStr='sedentary';break;caseActivityLevel.LIGHT:activityLevelStr='light';break;caseActivityLevel.MODERATE:activityLevelStr='moderate';break;caseActivityLevel.ACTIVE:activityLevelStr='active';break;caseActivityLevel.VERY_ACTIVE:activityLevelStr='veryActive';break;}authViewModel.gender=genderStr;authViewModel.age=this.healthProfile.age;authViewModel.height=this.healthProfile.height;authViewModel.weight=this.healthProfile.weight;authViewModel.activityLevel=activityLevelStr;authViewModel.favoriteTags=[...this.preference.favoriteTags];authViewModel.allergies=[...this.preference.allergies];authViewModel.maxCalories=this.preference.maxCalories;}}

设计考量:为什么需要在save()中同步两次(数据库 + 内存)?因为数据库写入是异步的,如果其他组件(如HealthTabContent)在数据库写入完成前读取AuthViewModel,可能读到旧值。内存同步是即时生效的,填补了数据库写入的延迟窗口。

修复 3:ProfileTabContent 初始化时加载数据

ProfileTabContent.aboutToAppear中调用loadFromAuthViewModel()

// MainContainer.ets → ProfileTabContent.aboutToAppear 新增一行asyncaboutToAppear():Promise<void>{constctx=this.getUIContext().getHostContext()ascommon.UIAbilityContext;// ... 加载头像和昵称 ...// ★ 关键修复:从 authViewModel 加载健康档案和偏好设置this.vm.loadFromAuthViewModel();// ... 监听事件 ...}

五、修复后的完整数据流

SQLiteAuthViewModelProfileViewModelProfileTabContent👤 用户SQLiteAuthViewModelProfileViewModelProfileTabContent👤 用户=== 冷启动 ====== 用户修改 ===@Trace 触发 UI 刷新=== 再次冷启动 ===initLocalAuth() 加载最新数据 (height=175)loadFromAuthViewModel()读取 @Trace 属性height=175 ✅UI 显示 175cm ✅修改身高为 180cmupdateHeight(180)autoSave()updateProfile({height:180})UPDATE local_users SET height=180同步内存状态 (height=180)UI 显示 180cm ✅initLocalAuth() 加载height=180loadFromAuthViewModel()height=180 ✅UI 显示 180cm ✅

图二解读:修复后的数据流实现了三重保障——冷启动时从 AuthViewModel 加载(保障1)、保存时同步 AuthViewModel 内存(保障2)、数据库持久化(保障3)。无论 App 如何重启,UI 始终展示最新数据。


六、代码交付清单

文件新增/修改行数说明
ProfileViewModel.ets新增loadFromAuthViewModel()+40从 AuthViewModel 同步健康档案和偏好
ProfileViewModel.ets修改save()+15保存后同步 AuthViewModel 内存状态
ProfileViewModel.ets新增parseActivityLevelFromString()+10活动等级字符串→枚举转换
MainContainer.ets修改ProfileTabContent.aboutToAppear()+1初始化时加载数据

七、设计决策

决策选择理由
数据加载时机aboutToAppear中主动调用每次组件出现时都重新同步,确保任何场景下数据最新
同步方式对象引用替换(makeHealthProfile等工厂函数)@ObservedV2检测引用变化,而非属性变化
save()中同步 AuthViewModel数据库写入后立即同步填补异步写入的延迟窗口,其他组件读取 AuthViewModel 时数据已最新
活动等级转换独立的parseActivityLevelFromString()方法避免魔法字符串散落各处,集中管理转换逻辑

八、验证方法

修复后,按以下步骤验证:

  1. 在“我的”Tab 修改身高为 175cm,年龄为 36 岁
  2. 切换到健康 Tab,确认营养数据按新值计算
  3. 完全关掉 App(从最近任务中划掉)
  4. 重新打开 App,切换到“我的”Tab
  5. 确认身高显示 175cm,年龄显示 36 岁(而非默认的 170/30)

九、本阶段总结

这次修复的本质是统一数据源。系统中的用户数据原本存在“三份事实版本”——数据库、AuthViewModel、ProfileViewModel。修复前,只有前两者在冷启动时同步,ProfileViewModel 是数据孤岛。修复后,ProfileViewModel 通过loadFromAuthViewModel()成为 AuthViewModel 的“镜像”,不再独立维护数据。

核心收获:

  • 单一数据源原则:同一份数据只在一个地方维护,其他地方通过同步获取
  • 内存同步不可省略:数据库持久化是异步的,内存同步填补了延迟窗口
  • 冷启动恢复测试:每次修改数据持久化逻辑后,必须测试“杀进程→重启→读取”的完整链路

📚 本系列持续更新中:下一篇将进入更深入的系统能力探索。

🔗专栏入口:[《HarmonyOS6.1全场景实战》合集]
📦 获取基线版本源码包:包括第1-15篇所有代码 + 架构文档 + Flask 后端
**如果你发现本文还有任何不严谨之处,欢迎随时指出,我们一起共建最优质的 HarmonyOS 6.1 学习内容!如果觉得有帮助,请不要吝啬你的点赞 👍、收藏 ⭐ 和评论 💬!
纯血鸿蒙,用心造厨。我们下一篇见!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询