HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(三十二):【数据一致性】个人档案的“三重持久化”修复——让偏好、健康与头像真正同步 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_users表storeHelper.executeSqlAuthViewModelAuthViewModel内存Trace属性initLocalAuth()/updateProfile()ProfileEditPageProfileViewModel内存Trace属性healthProfile/preferenceProfileTabContentPreferences本地 KV 文件preferences.get/putProfileTabContent头像/昵称️ UI 层 内存层 持久化层initLocalAuth() 加载updateProfile() 写入直接调用写入读取读取修复前未加载间接依赖修复前从未同步RelationalStorelocal_users 表Preferencesprofile_storeAuthViewModelTrace 属性ProfileViewModelhealthProfile preferenceProfileEditPage头像/昵称编辑ProfileTabContent档案 偏好展示HealthTabContent营养数据展示图一解读问题就出在ProfileViewModel和AuthViewModel之间的那条虚线——它们各自维护了一套“用户数据”但从未同步。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读取✅ 正确ProfileTabContent从ProfileViewModel读取❌ 显示默认值HealthTabContent从HealthDashboardViewModel→authViewModel.toUserHealthProfile()读取✅ 正确修复后三个页面对同一份用户数据有三条不同的读取路径。其中两条路径经过AuthViewModel正确一条路径经过ProfileViewModel错误。这就是为什么编辑页和健康页正常但“我的”Tab 异常的原因。四、修复方案三重保障确保数据一致修复 1ProfileViewModel 新增loadFromAuthViewModel()方法让ProfileViewModel在初始化时从AuthViewModel加载数据// ProfileViewModel.ets新增方法loadFromAuthViewModel():void{// 1. 从 authViewModel 同步健康档案constgenderauthViewModel.genderfemale?Gender.FEMALE:Gender.MALE;constactivityLevelthis.parseActivityLevelFromString(authViewModel.activityLevel);this.healthProfilemakeHealthProfile(gender,authViewModel.age,authViewModel.height,authViewModel.weight,activityLevel);// 2. 从 authViewModel 同步偏好设置this.preferencemakeUserPreference([...authViewModel.favoriteTags],[...authViewModel.allergies],authViewModel.maxCalories);console.info([ProfileVM] 已从 AuthViewModel 同步数据);}关键设计使用makeHealthProfile和makeUserPreference工厂函数创建全新对象引用——这是ObservedV2检测变化并触发 UI 刷新的必要条件。修复 2save()中同步更新 AuthViewModel在ProfileViewModel.save()中确保数据库写入成功后立即同步更新AuthViewModel的内存状态// ProfileViewModel.ets —— save() 方法修正部分asyncsave():Promisevoid{// ... 原有云端同步逻辑 ...if(success){// ★ 关键修复同步更新 authViewModel 内存状态constgenderStrthis.healthProfile.genderGender.MALE?male:female;letactivityLevelStrlight;switch(this.healthProfile.activityLevel){caseActivityLevel.SEDENTARY:activityLevelStrsedentary;break;caseActivityLevel.LIGHT:activityLevelStrlight;break;caseActivityLevel.MODERATE:activityLevelStrmoderate;break;caseActivityLevel.ACTIVE:activityLevelStractive;break;caseActivityLevel.VERY_ACTIVE:activityLevelStrveryActive;break;}authViewModel.gendergenderStr;authViewModel.agethis.healthProfile.age;authViewModel.heightthis.healthProfile.height;authViewModel.weightthis.healthProfile.weight;authViewModel.activityLevelactivityLevelStr;authViewModel.favoriteTags[...this.preference.favoriteTags];authViewModel.allergies[...this.preference.allergies];authViewModel.maxCaloriesthis.preference.maxCalories;}}设计考量为什么需要在save()中同步两次数据库 内存因为数据库写入是异步的如果其他组件如HealthTabContent在数据库写入完成前读取AuthViewModel可能读到旧值。内存同步是即时生效的填补了数据库写入的延迟窗口。修复 3ProfileTabContent 初始化时加载数据在ProfileTabContent.aboutToAppear中调用loadFromAuthViewModel()// MainContainer.ets → ProfileTabContent.aboutToAppear 新增一行asyncaboutToAppear():Promisevoid{constctxthis.getUIContext().getHostContext()ascommon.UIAbilityContext;// ... 加载头像和昵称 ...// ★ 关键修复从 authViewModel 加载健康档案和偏好设置this.vm.loadFromAuthViewModel();// ... 监听事件 ...}五、修复后的完整数据流SQLiteAuthViewModelProfileViewModelProfileTabContent 用户SQLiteAuthViewModelProfileViewModelProfileTabContent 用户 冷启动 用户修改 Trace 触发 UI 刷新 再次冷启动 initLocalAuth() 加载最新数据 (height175)loadFromAuthViewModel()读取 Trace 属性height175 ✅UI 显示 175cm ✅修改身高为 180cmupdateHeight(180)autoSave()updateProfile({height:180})UPDATE local_users SET height180同步内存状态 (height180)UI 显示 180cm ✅initLocalAuth() 加载height180loadFromAuthViewModel()height180 ✅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()方法避免魔法字符串散落各处集中管理转换逻辑八、验证方法修复后按以下步骤验证在“我的”Tab 修改身高为 175cm年龄为 36 岁切换到健康 Tab确认营养数据按新值计算完全关掉 App从最近任务中划掉重新打开 App切换到“我的”Tab确认身高显示 175cm年龄显示 36 岁而非默认的 170/30九、本阶段总结这次修复的本质是统一数据源。系统中的用户数据原本存在“三份事实版本”——数据库、AuthViewModel、ProfileViewModel。修复前只有前两者在冷启动时同步ProfileViewModel 是数据孤岛。修复后ProfileViewModel 通过loadFromAuthViewModel()成为 AuthViewModel 的“镜像”不再独立维护数据。核心收获单一数据源原则同一份数据只在一个地方维护其他地方通过同步获取内存同步不可省略数据库持久化是异步的内存同步填补了延迟窗口冷启动恢复测试每次修改数据持久化逻辑后必须测试“杀进程→重启→读取”的完整链路 本系列持续更新中下一篇将进入更深入的系统能力探索。专栏入口[《HarmonyOS6.1全场景实战》合集] 获取基线版本源码包包括第1-15篇所有代码 架构文档 Flask 后端**如果你发现本文还有任何不严谨之处欢迎随时指出我们一起共建最优质的 HarmonyOS 6.1 学习内容如果觉得有帮助请不要吝啬你的点赞 、收藏 ⭐ 和评论 !纯血鸿蒙用心造厨。我们下一篇见