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

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 使用模式

typescript 复制代码
import { preferences } from '@kit.ArkData';

const PREF_NAME = 'LingxiKitchen_prefs';
const prefs = await preferences.getPreferences(context, PREF_NAME);

// 写入
await prefs.put('favoriteTags', JSON.stringify(['快手菜', '高蛋白']));
await prefs.put('allergies', JSON.stringify(['花生', '海鲜']));
await prefs.flush();  // 必须 flush 才会持久化到文件

// 读取
const tags = prefs.getSync('favoriteTags', '[]') as string;
const parsed = JSON.parse(tags);  // ['快手菜', '高蛋白']

关键三个操作:

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

三、数据流全景

#mermaid-svg-3wv5QQCBNZT2j0iT{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-3wv5QQCBNZT2j0iT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3wv5QQCBNZT2j0iT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3wv5QQCBNZT2j0iT .error-icon{fill:#552222;}#mermaid-svg-3wv5QQCBNZT2j0iT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3wv5QQCBNZT2j0iT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3wv5QQCBNZT2j0iT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3wv5QQCBNZT2j0iT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3wv5QQCBNZT2j0iT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3wv5QQCBNZT2j0iT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3wv5QQCBNZT2j0iT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3wv5QQCBNZT2j0iT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3wv5QQCBNZT2j0iT .marker.cross{stroke:#333333;}#mermaid-svg-3wv5QQCBNZT2j0iT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3wv5QQCBNZT2j0iT p{margin:0;}#mermaid-svg-3wv5QQCBNZT2j0iT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3wv5QQCBNZT2j0iT .cluster-label text{fill:#333;}#mermaid-svg-3wv5QQCBNZT2j0iT .cluster-label span{color:#333;}#mermaid-svg-3wv5QQCBNZT2j0iT .cluster-label span p{background-color:transparent;}#mermaid-svg-3wv5QQCBNZT2j0iT .label text,#mermaid-svg-3wv5QQCBNZT2j0iT span{fill:#333;color:#333;}#mermaid-svg-3wv5QQCBNZT2j0iT .node rect,#mermaid-svg-3wv5QQCBNZT2j0iT .node circle,#mermaid-svg-3wv5QQCBNZT2j0iT .node ellipse,#mermaid-svg-3wv5QQCBNZT2j0iT .node polygon,#mermaid-svg-3wv5QQCBNZT2j0iT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3wv5QQCBNZT2j0iT .rough-node .label text,#mermaid-svg-3wv5QQCBNZT2j0iT .node .label text,#mermaid-svg-3wv5QQCBNZT2j0iT .image-shape .label,#mermaid-svg-3wv5QQCBNZT2j0iT .icon-shape .label{text-anchor:middle;}#mermaid-svg-3wv5QQCBNZT2j0iT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3wv5QQCBNZT2j0iT .rough-node .label,#mermaid-svg-3wv5QQCBNZT2j0iT .node .label,#mermaid-svg-3wv5QQCBNZT2j0iT .image-shape .label,#mermaid-svg-3wv5QQCBNZT2j0iT .icon-shape .label{text-align:center;}#mermaid-svg-3wv5QQCBNZT2j0iT .node.clickable{cursor:pointer;}#mermaid-svg-3wv5QQCBNZT2j0iT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3wv5QQCBNZT2j0iT .arrowheadPath{fill:#333333;}#mermaid-svg-3wv5QQCBNZT2j0iT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3wv5QQCBNZT2j0iT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3wv5QQCBNZT2j0iT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3wv5QQCBNZT2j0iT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3wv5QQCBNZT2j0iT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3wv5QQCBNZT2j0iT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3wv5QQCBNZT2j0iT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3wv5QQCBNZT2j0iT .cluster text{fill:#333;}#mermaid-svg-3wv5QQCBNZT2j0iT .cluster span{color:#333;}#mermaid-svg-3wv5QQCBNZT2j0iT 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-3wv5QQCBNZT2j0iT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3wv5QQCBNZT2j0iT rect.text{fill:none;stroke-width:0;}#mermaid-svg-3wv5QQCBNZT2j0iT .icon-shape,#mermaid-svg-3wv5QQCBNZT2j0iT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3wv5QQCBNZT2j0iT .icon-shape p,#mermaid-svg-3wv5QQCBNZT2j0iT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3wv5QQCBNZT2j0iT .icon-shape .label rect,#mermaid-svg-3wv5QQCBNZT2j0iT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3wv5QQCBNZT2j0iT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3wv5QQCBNZT2j0iT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3wv5QQCBNZT2j0iT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 📤 推荐结果
🧠 推荐引擎
📖 偏好读取
📦 本地文件
💾 持久化写入
👤 用户操作
设置口味偏好
设置过敏源
设置卡路里上限
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 两个方法:

typescript 复制代码
/**
 * 启动时加载持久化偏好
 * 在 ProfileTabContent.aboutToAppear 中调用
 */
async loadPreferences(context: common.UIAbilityContext): Promise<void> {
  try {
    const prefs = await preferences.getPreferences(context, 'LingxiKitchen_prefs');
    const favTags = prefs.getSync('favoriteTags', '[]') as string;
    const allergies = prefs.getSync('allergies', '[]') as string;
    const maxCal = prefs.getSync('maxCalories', 800) as number;
    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() 方法中调用
 */
async persistPreferences(context: common.UIAbilityContext): Promise<void> {
  try {
    const prefs = await preferences.getPreferences(context, 'LingxiKitchen_prefs');
    await prefs.put('favoriteTags', JSON.stringify(this.preference.favoriteTags));
    await prefs.put('allergies', JSON.stringify(this.preference.allergies));
    await prefs.put('maxCalories', this.preference.maxCalories);
    await prefs.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() 方法中,云端同步之后追加本地持久化:

typescript 复制代码
async save(): Promise<void> {
  this.isSaving = true;
  // ... 原有云端同步逻辑(authViewModel.updateProfile 等)...
  
  // ★ 新增:本地持久化(独立于云端同步,离线模式依然有效)
  const ctx = getContext(this) as common.UIAbilityContext;
  await this.persistPreferences(ctx);
  
  this.isSaving = false;
}

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

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

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

typescript 复制代码
const scored = 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:完整交互时序

首页瀑布流 HomeViewModel RecommendEngine Preferences ProfileViewModel 👤 用户 首页瀑布流 HomeViewModel RecommendEngine Preferences ProfileViewModel 👤 用户 #mermaid-svg-Qi08DyHLePtHpZJv{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-Qi08DyHLePtHpZJv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Qi08DyHLePtHpZJv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Qi08DyHLePtHpZJv .error-icon{fill:#552222;}#mermaid-svg-Qi08DyHLePtHpZJv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Qi08DyHLePtHpZJv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Qi08DyHLePtHpZJv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Qi08DyHLePtHpZJv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Qi08DyHLePtHpZJv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Qi08DyHLePtHpZJv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Qi08DyHLePtHpZJv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Qi08DyHLePtHpZJv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Qi08DyHLePtHpZJv .marker.cross{stroke:#333333;}#mermaid-svg-Qi08DyHLePtHpZJv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Qi08DyHLePtHpZJv p{margin:0;}#mermaid-svg-Qi08DyHLePtHpZJv .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Qi08DyHLePtHpZJv text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Qi08DyHLePtHpZJv .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Qi08DyHLePtHpZJv .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Qi08DyHLePtHpZJv .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Qi08DyHLePtHpZJv .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Qi08DyHLePtHpZJv #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Qi08DyHLePtHpZJv .sequenceNumber{fill:white;}#mermaid-svg-Qi08DyHLePtHpZJv #sequencenumber{fill:#333;}#mermaid-svg-Qi08DyHLePtHpZJv #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Qi08DyHLePtHpZJv .messageText{fill:#333;stroke:none;}#mermaid-svg-Qi08DyHLePtHpZJv .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Qi08DyHLePtHpZJv .labelText,#mermaid-svg-Qi08DyHLePtHpZJv .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Qi08DyHLePtHpZJv .loopText,#mermaid-svg-Qi08DyHLePtHpZJv .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Qi08DyHLePtHpZJv .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Qi08DyHLePtHpZJv .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Qi08DyHLePtHpZJv .noteText,#mermaid-svg-Qi08DyHLePtHpZJv .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Qi08DyHLePtHpZJv .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Qi08DyHLePtHpZJv .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Qi08DyHLePtHpZJv .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Qi08DyHLePtHpZJv .actorPopupMenu{position:absolute;}#mermaid-svg-Qi08DyHLePtHpZJv .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Qi08DyHLePtHpZJv .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Qi08DyHLePtHpZJv .actor-man circle,#mermaid-svg-Qi08DyHLePtHpZJv line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Qi08DyHLePtHpZJv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 宫保鸡丁→含花生→淘汰 番茄炒蛋→安全→保留 设置过敏源=花生,保存 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 无需修改 0 isSafe() 和偏好加分已就绪

六、设计决策

决策 选择 理由
存储方案 preferences(KV)而非 RelationalStore(SQLite) 偏好是少量 KV 数据,不需要复杂查询
数组序列化 JSON.stringify + JSON.parse preferences 只支持 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 后端
如果你觉得这篇文章对您有所帮助,麻烦您动动发财之手点赞 👍、收藏 ⭐ 和评论 💬。谢谢大家!!

纯血鸿蒙,用心造厨。我们下一篇见!

相关推荐
若兰幽竹3 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十七):告别 UI 冻结——使用 TaskPool 实现高性能并发图像分析
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹3 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十八):【数据持久化】收藏与浏览历史——让数据在 App 重启后依然“活着”
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹4 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十六):【响应式布局】折叠屏与平板完美适配——一套代码,多端呈现
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹5 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二十五):【深色模式】一键切换暗色主题——让 App 在深夜也温柔
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹6 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二十二) | 多媒体 | AVPlayer嵌入教学视频——让智慧屏真正“活”起来
音视频·华为鸿蒙系统·harmonyos6.1.0·灵犀厨房·harmonyos6.1
若兰幽竹6 天前
HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(二十三):【交互动效】转场、列表动画与趣味反馈——让每一次点击都有温度
交互·华为鸿蒙系统·harmonyos6.1
若兰幽竹7 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二十一):【服务卡片】在桌面查看烹饪进度——主进程强推与跨进程桥接
服务卡片·华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹7 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(排错指南):【服务卡片跳转】页面栈“迷航”——从“回不去的主页”到精准 Tab 唤醒的全链路修复
华为鸿蒙系统·灵犀厨房·harmonyos6.1·排错指南
若兰幽竹9 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(番外篇):【深度排查】24小时死磕服务卡片不刷新,我踩平了 API 23 的所有底坑
华为鸿蒙系统·harmonyos6.1.0·灵犀厨房