【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(排错指南):【服务卡片跳转】页面栈“迷航”——从“回不去的主页”到精准 Tab 唤醒的全链路修复

HarmonyOS 6.1 全场景实战 |《灵犀厨房》实战(排错指南):【服务卡片跳转】页面栈"迷航"------从"回不去的主页"到精准 Tab 唤醒的全链路修复

摘要 :上一篇我们为《灵犀厨房》装上了服务卡片------抬手就能看到烤箱倒计时。但卡片点击后,一个幽灵般的 Bug 悄然出现:点击服务卡片进入厨电页,再点桌面图标,App 直接打开了厨电页,返回键一按就退出,再也回不到首页。经过排查,这不是代码写错了,而是页面栈被"截断"了 。本文将复盘从现象定位、根因分析、方案设计到代码修复的全过程:服务卡片通过 postCardAction 跳转,在 EntryAbility 中原本被直接加载为独立的 KitchenDevicePage,导致 MainContainer 从未进入页面栈。修复后,卡片跳转统一指向 MainContainer,通过 startTab 参数指定厨电 Tab,配合 EntryBridge 静态变量和 emitter 事件,完美实现冷启动、热启动、后台唤起全场景覆盖。本文还记录了修复过程中踩过的 emitter.emit 参数类型陷阱。


一、引言:一个"回不去"的幽灵 Bug

第二十一篇中,我们为《灵犀厨房》实现了桌面烹饪进度卡片。点击卡片,跳转到厨电控制页------看起来一切正常。直到测试时发现一个诡异的现象:

操作步骤 预期行为 实际行为
点击服务卡片 进入厨电控制页 ✅ 正常
按返回键 回到首页 直接退出 App
再次点击桌面图标 回到首页或上次停留的页面 又打开了厨电页
在厨电页点击返回 回到首页 又退出 App

用户被困在了一个"厨电页孤岛"上------无论怎么操作,都无法回到首页、健康页或个人中心。这是一个典型的页面栈被截断问题。


二、核心原理:HarmonyOS 的页面栈机制

要理解这个 Bug,必须先理解 HarmonyOS 的页面导航模型。
#mermaid-svg-6MaTjfjNYobDp2Wb{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-6MaTjfjNYobDp2Wb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6MaTjfjNYobDp2Wb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6MaTjfjNYobDp2Wb .error-icon{fill:#552222;}#mermaid-svg-6MaTjfjNYobDp2Wb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6MaTjfjNYobDp2Wb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6MaTjfjNYobDp2Wb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6MaTjfjNYobDp2Wb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6MaTjfjNYobDp2Wb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6MaTjfjNYobDp2Wb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6MaTjfjNYobDp2Wb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6MaTjfjNYobDp2Wb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6MaTjfjNYobDp2Wb .marker.cross{stroke:#333333;}#mermaid-svg-6MaTjfjNYobDp2Wb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6MaTjfjNYobDp2Wb p{margin:0;}#mermaid-svg-6MaTjfjNYobDp2Wb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6MaTjfjNYobDp2Wb .cluster-label text{fill:#333;}#mermaid-svg-6MaTjfjNYobDp2Wb .cluster-label span{color:#333;}#mermaid-svg-6MaTjfjNYobDp2Wb .cluster-label span p{background-color:transparent;}#mermaid-svg-6MaTjfjNYobDp2Wb .label text,#mermaid-svg-6MaTjfjNYobDp2Wb span{fill:#333;color:#333;}#mermaid-svg-6MaTjfjNYobDp2Wb .node rect,#mermaid-svg-6MaTjfjNYobDp2Wb .node circle,#mermaid-svg-6MaTjfjNYobDp2Wb .node ellipse,#mermaid-svg-6MaTjfjNYobDp2Wb .node polygon,#mermaid-svg-6MaTjfjNYobDp2Wb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6MaTjfjNYobDp2Wb .rough-node .label text,#mermaid-svg-6MaTjfjNYobDp2Wb .node .label text,#mermaid-svg-6MaTjfjNYobDp2Wb .image-shape .label,#mermaid-svg-6MaTjfjNYobDp2Wb .icon-shape .label{text-anchor:middle;}#mermaid-svg-6MaTjfjNYobDp2Wb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6MaTjfjNYobDp2Wb .rough-node .label,#mermaid-svg-6MaTjfjNYobDp2Wb .node .label,#mermaid-svg-6MaTjfjNYobDp2Wb .image-shape .label,#mermaid-svg-6MaTjfjNYobDp2Wb .icon-shape .label{text-align:center;}#mermaid-svg-6MaTjfjNYobDp2Wb .node.clickable{cursor:pointer;}#mermaid-svg-6MaTjfjNYobDp2Wb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6MaTjfjNYobDp2Wb .arrowheadPath{fill:#333333;}#mermaid-svg-6MaTjfjNYobDp2Wb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6MaTjfjNYobDp2Wb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6MaTjfjNYobDp2Wb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6MaTjfjNYobDp2Wb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6MaTjfjNYobDp2Wb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6MaTjfjNYobDp2Wb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6MaTjfjNYobDp2Wb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6MaTjfjNYobDp2Wb .cluster text{fill:#333;}#mermaid-svg-6MaTjfjNYobDp2Wb .cluster span{color:#333;}#mermaid-svg-6MaTjfjNYobDp2Wb 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-6MaTjfjNYobDp2Wb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6MaTjfjNYobDp2Wb rect.text{fill:none;stroke-width:0;}#mermaid-svg-6MaTjfjNYobDp2Wb .icon-shape,#mermaid-svg-6MaTjfjNYobDp2Wb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6MaTjfjNYobDp2Wb .icon-shape p,#mermaid-svg-6MaTjfjNYobDp2Wb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6MaTjfjNYobDp2Wb .icon-shape .label rect,#mermaid-svg-6MaTjfjNYobDp2Wb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6MaTjfjNYobDp2Wb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6MaTjfjNYobDp2Wb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6MaTjfjNYobDp2Wb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ❌ 服务卡片截断的页面栈
不存在
MainContainer

(从未进入栈)
KitchenDevicePage

(loadContent 直接加载)
✅ 正常页面栈
MainContainer

(Tab 0: 首页)
RecipeDetailPage

(pushUrl)
KitchenDevicePage

(pushUrl)

图一解读 :正常流程下,用户从首页点击菜谱卡片 → router.pushUrl 打开详情页 → 再点"开始烹饪" → router.pushUrl 打开厨电页。页面栈中依次是 MainContainer → RecipeDetailPage → KitchenDevicePage,按返回键时,系统从栈顶逐个弹出,最终回到 MainContainer

但服务卡片跳转时,EntryAbility 通过 windowStage.loadContent 直接加载了 KitchenDevicePageMainContainer 从未进入页面栈。于是按返回键时,系统发现栈中只有一个页面,直接退出 App。而系统"记住"了这个状态,再次点图标时又恢复了这个孤零零的厨电页。


三、架构修正:统一跳转入口

修正的核心思想是:服务卡片不直接加载任何独立页面,而是跳转到 MainContainer,通过 startTab 参数指定要激活的 Tab
AppIcon MainContainer emitter EntryBridge EntryAbility 服务卡片 AppIcon MainContainer emitter EntryBridge EntryAbility 服务卡片 #mermaid-svg-Fr6Z5DjMTpyB2r1w{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-Fr6Z5DjMTpyB2r1w .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .error-icon{fill:#552222;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .marker.cross{stroke:#333333;}#mermaid-svg-Fr6Z5DjMTpyB2r1w svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Fr6Z5DjMTpyB2r1w p{margin:0;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Fr6Z5DjMTpyB2r1w text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Fr6Z5DjMTpyB2r1w .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Fr6Z5DjMTpyB2r1w #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .sequenceNumber{fill:white;}#mermaid-svg-Fr6Z5DjMTpyB2r1w #sequencenumber{fill:#333;}#mermaid-svg-Fr6Z5DjMTpyB2r1w #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .messageText{fill:#333;stroke:none;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .labelText,#mermaid-svg-Fr6Z5DjMTpyB2r1w .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .loopText,#mermaid-svg-Fr6Z5DjMTpyB2r1w .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .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-Fr6Z5DjMTpyB2r1w .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .noteText,#mermaid-svg-Fr6Z5DjMTpyB2r1w .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .actorPopupMenu{position:absolute;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .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-Fr6Z5DjMTpyB2r1w .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Fr6Z5DjMTpyB2r1w .actor-man circle,#mermaid-svg-Fr6Z5DjMTpyB2r1w line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Fr6Z5DjMTpyB2r1w :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 场景1:冷启动(App 未运行) 场景2:热启动(App 在后台) 场景3:从桌面图标点击(无 startTab) onCreate(want) extractParams: startTab=2 EntryBridge.initialTab = 2 loadContent('pages/MainContainer') aboutToAppear() 读取 EntryBridge.initialTab = 2 currentIndex = 2, changeIndex(2) 显示厨电 Tab onNewWant(want) extractParams: startTab=2 emit({ eventId: 10003 }, { data: { tabIndex: 2 } }) tabChangeCallback 触发 currentIndex = 2, changeIndex(2) 切换到厨电 Tab(无重复加载) onNewWant(无 startTab 参数) startTab 为 undefined,跳过 emitter 通知 保持当前 Tab,不做切换

图二解读 :修正后的架构覆盖了三种场景。冷启动时,通过静态变量 EntryBridge.initialTab 传递 Tab 索引;热启动时,通过 emitter 事件通知已存在的 MainContainer 切换 Tab;从桌面图标正常进入时,不携带 startTab 参数,保持用户上次离开时的页面状态。三条路径统一指向 MainContainer,页面栈完整,返回键行为恢复正常。


四、代码修正清单

4.1 WidgetCard.ets --- 修正跳转目标

typescript 复制代码
// 修正前:直接跳转到独立 KitchenDevicePage
.onClick(() => {
  postCardAction(this, {
    action: 'router',
    abilityName: 'EntryAbility',
    params: { targetPage: 'KitchenDevicePage' }
  });
})

// 修正后:跳转到 MainContainer,携带 startTab 参数
.onClick(() => {
  postCardAction(this, {
    action: 'router',
    abilityName: 'EntryAbility',
    params: {
      targetPage: 'MainContainer',
      startTab: 2  // 指定激活"厨电"Tab
    }
  });
})

4.2 EntryAbility.ets --- 核心路由改造

新增 EntryBridge 静态桥梁(用于冷启动):

typescript 复制代码
export class EntryBridge {
  static initialTab: number = -1;
}

extractParams 中提取 startTab

typescript 复制代码
private extractParams(want: Want): void {
  const targetPage = want?.parameters?.targetPage as string;
  if (targetPage && targetPage.length > 0) {
    this.pendingTargetPage = targetPage;
    // 提取 Tab 索引并写入静态桥梁
    const startTab = want?.parameters?.startTab as number;
    if (startTab !== undefined && startTab >= 0) {
      EntryBridge.initialTab = startTab;
    }
    return;
  }
  // ... 原有的 recipeId 提取逻辑
}

onNewWant 中无条件处理 startTab(用于热启动):

typescript 复制代码
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  this.extractParams(want);
  if (cookingProgressManager.isActive) return;

  // ★ 无条件处理 startTab 参数
  const startTab = want?.parameters?.startTab as number;
  if (startTab !== undefined && startTab >= 0) {
    EntryBridge.initialTab = startTab;
    emitter.emit(
      { eventId: TAB_CHANGE_EVENT_ID },
      { data: { tabIndex: startTab } }
    );
    return;
  }
  // 原有的菜谱详情页跳转逻辑
  if (this.pendingRecipeId.length > 0) {
    this.loadRecipeDetailPage();
  }
}

4.3 MainContainer.ets --- 响应初始 Tab 与动态切换

冷启动读取静态桥梁

typescript 复制代码
aboutToAppear(): void {
  if (EntryBridge.initialTab >= 0) {
    const tabIndex = EntryBridge.initialTab;
    EntryBridge.initialTab = -1;
    this.currentIndex = tabIndex;
    this.tabsController.changeIndex(tabIndex);
  }
  // 监听热启动 Tab 切换事件
  emitter.on({ eventId: TAB_CHANGE_EVENT_ID }, this.tabChangeCallback);
}

热启动监听回调

typescript 复制代码
private tabChangeCallback: Callback<emitter.EventData> = (eventData: emitter.EventData) => {
  const tabIndex = eventData?.data?.tabIndex as number;
  if (tabIndex !== undefined && tabIndex >= 0) {
    this.currentIndex = tabIndex;
    this.tabsController.changeIndex(tabIndex);
  }
};

五、踩坑实录:emitter.emit 参数类型陷阱

修复过程中,emitter.emit 的参数类型报错是一个典型的 API 23 陷阱:

错误写法

typescript 复制代码
emitter.emit({ eventId: TAB_CHANGE_EVENT_ID, data: { tabIndex: 2 } });
// 报错:'data' does not exist in type 'InnerEvent'

正确写法

typescript 复制代码
emitter.emit(
  { eventId: TAB_CHANGE_EVENT_ID },   // 第1参数:InnerEvent 对象
  { data: { tabIndex: 2 } }            // 第2参数:EventData 对象
);

emitter 的三个方法签名在 API 23 中极不一致:

方法 第1参数 第2参数
emitter.on { eventId: number } 对象 回调函数
emitter.emit { eventId: number } 对象 { data: { ... } } 对象
emitter.off number 数字 回调函数

核心记忆口诀on 传对象,emit 拆两个(对象 + data),off 传数字。如果照搬旧文档写成 emitter.emit({ eventId, data }),编译器会直接报错。


六、验证结果

修复后,完整的控制台日志展示了从卡片点击到 Tab 唤醒的全链路:

热启动场景

复制代码
★ onNewWant 收到 Want 参数: {"targetPage":"MainContainer","startTab":2}
★ EntryBridge.initialTab 已写入: 2
★ 热启动: 通知 MainContainer 切换 Tab → 2
[MainContainer] ★ 热启动: 收到 Tab 切换事件, tabIndex = 2
[MainContainer] ★ Tab 切换前: currentIndex = 0
[MainContainer] ★ Tab 切换完成: currentIndex = 2

路由覆盖矩阵

主应用状态 触发方法 页面 Tab 来源
未启动(冷启动) onCreateonWindowStageCreate MainContainer EntryBridge.initialTab
后台运行 onNewWant MainContainer emitter 事件
桌面图标正常启动 onWindowStageCreate MainContainer 无(保持上次 Tab)

七、问题与解决办法汇总

问题 现象 根因 解决办法
服务卡片点击后无法返回首页 返回键直接退出 App 卡片直接加载独立 KitchenDevicePage,MainContainer 未入栈 卡片跳转目标改为 MainContainer + startTab 参数
再次点击桌面图标仍打开厨电页 App 从后台恢复时停留在厨电页 系统记住了被截断的页面栈 统一入口后,页面栈完整,系统记忆正常
热启动时 Tab 未切换 点击卡片但停留在之前 Tab onNewWant 中 pendingTargetPage 判断条件过严 无条件提取 startTab 并发送 emitter 通知
emitter.emit 报类型错误 data does not exist in type InnerEvent API 23 中 emit 的 data 需放在第2参数 改为 emit({ eventId }, { data: {...} })
从其他 Tab 退后台再点卡片不切换 卡片点击无反应 onNewWant 中 pendingTargetPage 为空,跳过了 emitter 通知 独立判断 startTab,不依赖 pendingTargetPage

八、本阶段总结

这次修复从发现"回不去"的幽灵 Bug 开始,深入到 HarmonyOS 页面栈机制,最终通过统一跳转入口、静态变量桥梁和 emitter 事件通知,实现了服务卡片跳转的冷启动、热启动、后台唤起全场景覆盖。

核心收获:

  • 不要用 loadContent 直接加载独立页面------这会截断页面栈
  • 服务卡片统一跳转到主容器,通过参数指定 Tab
  • 冷启动用静态变量传参,热启动用 emitter 通知
  • emitter.emit 的 data 必须放在第2参数 ,不能与 eventId 混在同一个对象里

📚 本系列持续更新中:下一篇我们将深入更复杂的业务场景,探索鸿蒙底层的多线程并发与性能调优。
🔗 专栏入口《HarmonyOS6.1全场景实战》合集
📦 获取基线版本源码包包括第1-15篇所有代码 + 架构文档 + Flask 后端
如果你觉得这篇"排雷指南"对您有所帮助,麻烦您动动发财之手点赞 👍、收藏 ⭐ 和评论 💬。谢谢大家支持!!

纯血鸿蒙,踩坑填坑。我们下一篇见!

相关推荐
若兰幽竹2 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(番外篇):【深度排查】24小时死磕服务卡片不刷新,我踩平了 API 23 的所有底坑
华为鸿蒙系统·harmonyos6.1.0·灵犀厨房
若兰幽竹3 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二十)扩展:【工程集成】主应用 + 元服务 + HSP 共享库——三模块一体化架构
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹3 天前
HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(二十点五):【排错指南】元服务跳转主应用——Want 参数传递的五个陷阱与架构修复
元服务·华为鸿蒙系统·harmonyos6.1·排除指南
若兰幽竹4 天前
HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(二十):【元服务】一键烹饪推荐原子化服务——免安装直达美味
华为鸿蒙系统·harmonyos6.1.0·灵犀厨房
若兰幽竹4 天前
HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(十九):【通知系统】延时烹饪提醒——让通知不再错过关键步骤
华为鸿蒙系统·harmonyos6.1.0·灵犀厨房
若兰幽竹9 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(十八):【手表协同】烹饪计时器流转至智能手表——手腕掌控烹饪节奏
智能手表·华为鸿蒙系统·harmonyos6.1.0·灵犀厨房
若兰幽竹12 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(十七):【语音识别】免提声控启动播报——动口不动手
语音识别·华为鸿蒙系统·harmonyos6.1.0·灵犀厨房
若兰幽竹14 天前
【HarmonyOS6.1全场景实战】基线版本:我用了15篇文章,造出了一个能登录、能推荐、带后台的鸿蒙全栈App
华为鸿蒙系统·harmonyos6.1.0·灵犀厨房
若兰幽竹14 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(十五)之【超级设备模拟器实战】多设备交互调试:像上帝一样俯瞰整个智能厨房
华为鸿蒙系统·harmonyos6.1.0·灵犀厨房