HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十九):【偏好持久化】偏好设置与推荐引擎联动——让 App 越用越“懂你”
2026/6/8 10:41:19 网站建设 项目流程

HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十九):【偏好持久化】偏好设置与推荐引擎联动——让 App 越用越“懂你”

摘要:前面 28 篇中,用户在个人中心设置了口味偏好和过敏源,但 App 重启后全部丢失——因为这些数据只存在内存里。推荐引擎虽然已经写好了过敏源过滤逻辑(isSafe()),但从未与偏好设置真正联动。本篇利用 HarmonyOS 的preferences API实现偏好持久化,并打通偏好→推荐的数据链路——用户设置一次偏好,App 永久记住,推荐结果自动规避过敏源、响应口味变化。全程不改推荐引擎核心代码,仅新增约 30 行持久化逻辑。


一、引言:被遗忘的“口味档案”

一个实验:在第 28 篇的基础上,打开个人中心,设置口味偏好为“快手菜、高蛋白”,忌口为“花生、海鲜”。然后关掉 App,重新打开。

偏好设置全部恢复默认——App 完全忘记了你的口味和忌口。

更糟糕的是:推荐引擎虽然已经写好了isSafe()方法(过滤过敏源)和calcScore()方法(偏好标签加分),但从未被调用过。用户设置过敏源后,推荐结果依然包含“宫保鸡丁”(含花生);用户偏好高蛋白,推荐引擎却一无所知。

问题根因影响
偏好设置重启丢失数据只在内存中每次启动都要重新设置
过敏源过滤未生效isSafe()写了但没人调用花生过敏用户仍被推荐含花生的菜
偏好加分未生效calcScore()的偏好逻辑闲置推荐引擎是“无差别推荐”

🎯本篇目标:用 preferences API 将偏好持久化到本地文件,App 重启后自动恢复;打通偏好→推荐的完整数据链路,让isSafe()和偏好加分真正生效。推荐引擎核心代码零改动,持久化代码仅约 30 行。


二、核心原理:preferences API 的“便签本”模型

2.1 为什么是 preferences 而非 RelationalStore?

HarmonyOS 提供了两种本地持久化方案:

方案数据结构适用场景本篇选型
preferences键值对(KV)少量结构化数据(设置、偏好、Token)
RelationalStore(SQLite)表 + SQL结构化数据(收藏、历史、订单)❌(第 28 篇已使用)

口味偏好是典型的 KV 场景——一个用户只有一组偏好,不需要复杂查询,不需要多表关联。用 SQLite 就像用杀牛刀杀鸡:能做,但太重。

你可以把 preferences 想象成一个便签本:你写下一行“喜欢的口味:快手菜、高蛋白”,合上本子(flush),下次打开还在。不需要建表、不需要写 SQL、不需要管理连接——三行代码搞定写入,一行代码搞定读取。

2.2 使用模式

import{preferences}from'@kit.ArkData';constPREF_NAME='LingxiKitchen_prefs';constprefs=awaitpreferences.getPreferences(context,PREF_NAME);// 写入awaitprefs.put('favoriteTags',JSON.stringify(['快手菜','高蛋白']));awaitprefs.put('allergies',JSON.stringify(['花生','海鲜']));awaitprefs.flush();// 必须 flush 才会持久化到文件// 读取consttags=prefs.getSync('favoriteTags','[]')asstring;constparsed=JSON.parse(tags);// ['快手菜', '高蛋白']

关键三个操作:

  1. put(key, value):写入键值对,value 只能是 string/number/boolean
  2. flush():将内存中的修改刷入磁盘。没有这一步,App 崩溃或被杀后数据丢失
  3. getSync(key, default):同步读取。default在首次启动(Preferences 为空)时作为兜底值

三、数据流全景

📤 推荐结果

🧠 推荐引擎

📖 偏好读取

📦 本地文件

💾 持久化写入

👤 用户操作

设置口味偏好

设置过敏源

设置卡路里上限

ProfileViewModel.save()

persistPreferences()

prefs.put + flush()

LingxiKitchen_prefs
/favoriteTags
/allergies
/maxCalories

HomeViewModel.refreshByPreference()

prefs.getSync()

构造 UserPreference

isSafe() 过滤过敏源

calcScore() 偏好标签加分

卡路里上限过滤

不含过敏源
偏好菜谱加分
不超卡路里上限

图一解读:这张全景图展示了偏好数据从用户设置到推荐结果的完整链路。上半部分是写入路径——用户设置偏好 →save()persistPreferences()prefs.put + flush()→ 持久化到本地文件。下半部分是读取路径——首页加载 →refreshByPreference()getSync()读取 Preferences → 构造UserPreference→ 传入推荐引擎 →isSafe()过滤过敏源 +calcScore()偏好加分。一套数据,两条路径,推荐引擎零改动。


四、实战步骤

Step 1:ProfileViewModel 新增持久化方法

ProfileViewModel中新增loadPreferencespersistPreferences两个方法:

/** * 启动时加载持久化偏好 * 在 ProfileTabContent.aboutToAppear 中调用 */asyncloadPreferences(context:common.UIAbilityContext):Promise<void>{try{constprefs=awaitpreferences.getPreferences(context,'LingxiKitchen_prefs');constfavTags=prefs.getSync('favoriteTags','[]')asstring;constallergies=prefs.getSync('allergies','[]')asstring;constmaxCal=prefs.getSync('maxCalories',800)asnumber;if(favTags!=='[]'){this.preference={favoriteTags:JSON.parse(favTags),allergies:JSON.parse(allergies),maxCalories:maxCal};console.info('[ProfileVM] 偏好已从本地加载');}}catch(err){console.warn('[ProfileVM] 加载偏好失败:',JSON.stringify(err));}}/** * 保存时持久化偏好 * 在 save() 方法中调用 */asyncpersistPreferences(context:common.UIAbilityContext):Promise<void>{try{constprefs=awaitpreferences.getPreferences(context,'LingxiKitchen_prefs');awaitprefs.put('favoriteTags',JSON.stringify(this.preference.favoriteTags));awaitprefs.put('allergies',JSON.stringify(this.preference.allergies));awaitprefs.put('maxCalories',this.preference.maxCalories);awaitprefs.flush();console.info('[ProfileVM] 偏好已持久化');}catch(err){console.error('[ProfileVM] 持久化失败:',JSON.stringify(err));}}

关键设计点

  • getSync(key, default)的第二个参数'[]'是兜底值——首次启动时 Preferences 为空,返回空数组 JSON 字符串,JSON.parse('[]')返回空数组,偏好保持初始状态
  • 数组类型需要JSON.stringify序列化——preferences 只支持 string/number/boolean 三种原生类型
  • flush()put之后必须调用——它是持久化的“确认键”,没有它,数据只在内存中

Step 2:save() 中接入持久化

在现有的save()方法中,云端同步之后追加本地持久化:

asyncsave():Promise<void>{this.isSaving=true;// ... 原有云端同步逻辑(authViewModel.updateProfile 等)...// ★ 新增:本地持久化(独立于云端同步,离线模式依然有效)constctx=getContext(this)ascommon.UIAbilityContext;awaitthis.persistPreferences(ctx);this.isSaving=false;}

设计考量:本地持久化在云端同步之后执行——不阻塞云端请求,也不依赖云端结果。即使用户离线,偏好依然能保存到本地。

Step 3:推荐联动——isSafe() 和偏好加分已就绪

RecommendEngine中的过敏源过滤逻辑从最早就已写好,在推荐方法的 filter 链中已调用:

constscored=allRecipes.filter(r=>this.isSafe(r,preference.allergies))// ← 过敏源过滤.filter(r=>preference.maxCalories<=0||r.calories<=preference.maxCalories).map(r=>({recipe:r,score:this.calcScore(r,preference,ingredients)}));

本篇无需修改 RecommendEngine 的任何代码——只需要确保传入的preference对象携带从 Preferences 加载的真实数据,而非默认空偏好。数据流打通后,推荐引擎自动生效。

Step 4:完整交互时序

首页瀑布流HomeViewModelRecommendEnginePreferencesProfileViewModel👤 用户首页瀑布流HomeViewModelRecommendEnginePreferencesProfileViewModel👤 用户宫保鸡丁→含花生→淘汰番茄炒蛋→安全→保留设置过敏源=花生,保存put('allergies', '["花生"]')flush() 持久化进入首页refreshByPreference()getSync('allergies')'["花生"]'构造 UserPreference{ allergies: ['花生'] }getRecommendations(preference)isSafe(recipe, ['花生'])推荐结果(不含花生)瀑布流刷新

图二解读:这条时序图展示了偏好持久化与推荐联动的完整闭环。用户设置过敏源后,persistPreferences写入本地文件。下次进入首页时,refreshByPreference从 Preferences 读取过敏源,构造UserPreference对象,传入推荐引擎。引擎的isSafe()自动过滤含过敏源的菜谱——calcScore()也同步生效,为偏好标签匹配的菜谱加分。整套链路中,用户只做了一次设置,App 永久记住,推荐结果自动响应。


五、代码交付清单

文件新增/修改行数说明
ProfileViewModel.ets新增loadPreferences()+15启动时从 Preferences 恢复偏好
ProfileViewModel.ets新增persistPreferences()+12保存时持久化偏好到文件
ProfileViewModel.ets修改save()+3调用persistPreferences
RecommendEngine.ets无需修改0isSafe()和偏好加分已就绪

六、设计决策

决策选择理由
存储方案preferences(KV)而非 RelationalStore(SQLite)偏好是少量 KV 数据,不需要复杂查询
数组序列化JSON.stringify+JSON.parsepreferences 只支持 string/number/boolean,数组必须转字符串
读取方式getSync(key, default)同步读取避免异步回调嵌套,默认值保证首次启动不报错
过敏源匹配ing.toLowerCase().includes(a.toLowerCase())模糊匹配“花生油"匹配"花生”,避免精确匹配遗漏
持久化时机save()中云端同步之后不阻塞云端请求,离线模式依然有效
推荐引擎零改动isSafe()和偏好加分从第 1 篇就已写好,本篇只打通数据链路

七、本阶段总结与下篇预告

本篇用约 30 行持久化代码,打通了偏好设置与推荐引擎之间的数据链路:

  • preferences API:以 KV 方式持久化偏好,App 重启后自动恢复
  • JSON 序列化数组:解决 preferences 不支持数组类型的限制
  • 推荐引擎零改动isSafe()calcScore()从最初就已就绪,本篇只让数据流入
  • 完整闭环:设置→持久化→读取→传入推荐引擎→过滤过敏源→偏好加分→推荐结果刷新

现在的用户体验

设置过敏源“花生”→ 保存 → 关闭 App → 重新打开 → 推荐结果不再出现宫保鸡丁

下篇预告:第 30 篇《社区分享——本地社区功能》。我们将利用已有的shared_recipes表,让用户分享自己的菜谱创意到本地社区,浏览其他用户的分享。这是《灵犀厨房》社交维度的第一步。


📚 本系列持续更新中:下一篇将为 App 注入社交基因,让烹饪从“独享”变为“共享”。

🔗专栏入口:[《HarmonyOS6.1全场景实战》合集]

📦 获取基线版本源码包:包括第1-15篇所有代码 + 架构文档 + Flask 后端

如果你觉得这篇文章对您有所帮助,麻烦您动动发财之手点赞 👍、收藏 ⭐ 和评论 💬。谢谢大家!!
纯血鸿蒙,用心造厨。我们下一篇见!

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

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

立即咨询