HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(番外篇):【打包上架】三模块一体化工程的 Release 包构建与元服务独立分发

HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(番外篇):【打包上架】三模块一体化工程的 Release 包构建与元服务独立分发

摘要 :经过几十篇的迭代,《灵犀厨房》从一行代码成长为包含主应用、元服务和 HSP 共享库的完整项目。但在通往 AppGallery 的最后一步------打包 Release 包时,我们遭遇了一场"配置风暴":同一个工程如何分别打出主应用和元服务的包?为什么 entryatomicservice 会冲突?元服务上架为什么要求 type 必须是 entry?本文将完整复盘从工程配置到构建产物的全流程,提炼出"一个工程、两个产品、两条命令"的打包法则。


一、引言:最后的关卡

经过功能开发、Bug 修复、HarmonyOS 6.1 新特性适配,《灵犀厨房》终于走到了上架前的最后一步------打包 Release 包。

你打开 DevEco Studio,配置好发布证书,执行构建命令,然后------

复制代码
> hvigor ERROR: The tablet,2in1,phone device type under product default 
  already has an entry module.

这不是 Bug,这是 HarmonyOS 多模块工程的设计约束 :一个产品(Product)下只能有一个 entry 类型的模块。而我们的工程里,主应用的 entry 和元服务的 atomicservice 都是 entry 类型,它们不能同时存在于同一个产品中。


二、核心原理:理解 Product、Module 和 applyToProducts

要解决这个冲突,必须先理解 HarmonyOS 构建系统的三个核心概念:
#mermaid-svg-xi6qztQX1oHD1Nqr{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-xi6qztQX1oHD1Nqr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xi6qztQX1oHD1Nqr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xi6qztQX1oHD1Nqr .error-icon{fill:#552222;}#mermaid-svg-xi6qztQX1oHD1Nqr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xi6qztQX1oHD1Nqr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xi6qztQX1oHD1Nqr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xi6qztQX1oHD1Nqr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xi6qztQX1oHD1Nqr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xi6qztQX1oHD1Nqr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xi6qztQX1oHD1Nqr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xi6qztQX1oHD1Nqr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xi6qztQX1oHD1Nqr .marker.cross{stroke:#333333;}#mermaid-svg-xi6qztQX1oHD1Nqr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xi6qztQX1oHD1Nqr p{margin:0;}#mermaid-svg-xi6qztQX1oHD1Nqr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xi6qztQX1oHD1Nqr .cluster-label text{fill:#333;}#mermaid-svg-xi6qztQX1oHD1Nqr .cluster-label span{color:#333;}#mermaid-svg-xi6qztQX1oHD1Nqr .cluster-label span p{background-color:transparent;}#mermaid-svg-xi6qztQX1oHD1Nqr .label text,#mermaid-svg-xi6qztQX1oHD1Nqr span{fill:#333;color:#333;}#mermaid-svg-xi6qztQX1oHD1Nqr .node rect,#mermaid-svg-xi6qztQX1oHD1Nqr .node circle,#mermaid-svg-xi6qztQX1oHD1Nqr .node ellipse,#mermaid-svg-xi6qztQX1oHD1Nqr .node polygon,#mermaid-svg-xi6qztQX1oHD1Nqr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xi6qztQX1oHD1Nqr .rough-node .label text,#mermaid-svg-xi6qztQX1oHD1Nqr .node .label text,#mermaid-svg-xi6qztQX1oHD1Nqr .image-shape .label,#mermaid-svg-xi6qztQX1oHD1Nqr .icon-shape .label{text-anchor:middle;}#mermaid-svg-xi6qztQX1oHD1Nqr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xi6qztQX1oHD1Nqr .rough-node .label,#mermaid-svg-xi6qztQX1oHD1Nqr .node .label,#mermaid-svg-xi6qztQX1oHD1Nqr .image-shape .label,#mermaid-svg-xi6qztQX1oHD1Nqr .icon-shape .label{text-align:center;}#mermaid-svg-xi6qztQX1oHD1Nqr .node.clickable{cursor:pointer;}#mermaid-svg-xi6qztQX1oHD1Nqr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xi6qztQX1oHD1Nqr .arrowheadPath{fill:#333333;}#mermaid-svg-xi6qztQX1oHD1Nqr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xi6qztQX1oHD1Nqr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xi6qztQX1oHD1Nqr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xi6qztQX1oHD1Nqr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xi6qztQX1oHD1Nqr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xi6qztQX1oHD1Nqr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xi6qztQX1oHD1Nqr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xi6qztQX1oHD1Nqr .cluster text{fill:#333;}#mermaid-svg-xi6qztQX1oHD1Nqr .cluster span{color:#333;}#mermaid-svg-xi6qztQX1oHD1Nqr 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-xi6qztQX1oHD1Nqr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xi6qztQX1oHD1Nqr rect.text{fill:none;stroke-width:0;}#mermaid-svg-xi6qztQX1oHD1Nqr .icon-shape,#mermaid-svg-xi6qztQX1oHD1Nqr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xi6qztQX1oHD1Nqr .icon-shape p,#mermaid-svg-xi6qztQX1oHD1Nqr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xi6qztQX1oHD1Nqr .icon-shape .label rect,#mermaid-svg-xi6qztQX1oHD1Nqr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xi6qztQX1oHD1Nqr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xi6qztQX1oHD1Nqr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xi6qztQX1oHD1Nqr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 产品 atomicserviceProduct(元服务)
atomicservice(元服务首包)
shared(HSP 共享库)
产品 default(主应用)
entry(主应用首包)
shared(HSP 共享库)
atomicservice(作为 feature 模块)
build-profile.json5
products 产品定义

default / atomicserviceProduct
modules 模块定义

entry / shared / atomicservice

图一解读 :一个工程可以定义多个产品,每个产品通过 applyToProducts 字段选择包含哪些模块。同一个模块可以被多个产品共享(如 shared),但 entry 类型的模块在同一个产品中只能出现一次

概念 作用 类比
Product(产品) 定义一次构建的产物组合 一道菜的"套餐"
Module(模块) 代码和资源的组织单元 套餐中的"食材"
applyToProducts 决定某个模块属于哪些产品 食材被哪些套餐使用

主应用和元服务共享同一个 bundleName 和同一套发布证书,但它们需要生成两个独立的 .app 文件,分别上传到 AppGallery Connect 的"应用"和"元服务"入口。因此,必须定义两个产品。


三、完整配置

3.1 根目录 build-profile.json5

json5 复制代码
{
  "app": {
    "signingConfigs": [
      {
        "name": "release",
        "type": "HarmonyOS",
        "material": {
          "storeFile": "D:/BaiduNetdiskDownload/HarmonyOS/certificates/release/lingxikitchenrelease.p12",
          "profile": "D:/BaiduNetdiskDownload/HarmonyOS/certificates/release/lingxikitchenreleaseRelease.p7b",
          "certpath": "D:/BaiduNetdiskDownload/HarmonyOS/certificates/release/lingxikitchenrelease.cer"
        }
      }
    ],
    "products": [
      {
        "name": "default",
        "signingConfig": "release",
        "targetSdkVersion": "6.1.1(24)",
        "compatibleSdkVersion": "6.1.0(23)",
        "runtimeOS": "HarmonyOS",
        "buildOption": {
          "strictMode": { "caseSensitiveCheck": true, "useNormalizedOHMUrl": true }
        }
      },
      {
        "name": "atomicserviceProduct",
        "signingConfig": "release",
        "targetSdkVersion": "6.1.1(24)",
        "compatibleSdkVersion": "6.1.0(23)",
        "runtimeOS": "HarmonyOS",
        "buildOption": {
          "strictMode": { "caseSensitiveCheck": true, "useNormalizedOHMUrl": true }
        }
      }
    ],
    "buildModeSet": [
      { "name": "debug" },
      { "name": "release" }
    ]
  },
  "modules": [
    {
      "name": "entry",
      "srcPath": "./entry",
      "targets": [{ "name": "default", "applyToProducts": ["default"] }]
    },
    {
      "name": "shared",
      "srcPath": "./shared",
      "targets": [{ "name": "default", "applyToProducts": ["default", "atomicserviceProduct"] }]
    },
    {
      "name": "atomicservice",
      "srcPath": "./atomicservice",
      "targets": [{ "name": "default", "applyToProducts": ["atomicserviceProduct"] }]
    }
  ]
}

3.2 关键配置解读

配置项 作用
products[0].name default 主应用产品
products[1].name atomicserviceProduct 元服务专用产品
entry.applyToProducts ["default"] 仅主应用包含
shared.applyToProducts ["default", "atomicserviceProduct"] 两个产品共享
atomicservice.applyToProducts ["atomicserviceProduct"] 仅元服务包含

3.3 atomicservice 的 module.json5

元服务上架要求模块类型为 entry

json5 复制代码
{
  "module": {
    "name": "atomicservice",
    "type": "entry",  // ★ 必须为 entry
    "installationFree": true,
    ...
  }
}

四、打包命令

清理旧产物

bash 复制代码
hvigorw clean

构建主应用 Release 包

bash 复制代码
hvigorw assembleApp -p product=default -p buildMode=release

产物路径:build/outputs/default/lingxi-kitchen-v2-default-signed.app

构建元服务 Release 包

bash 复制代码
hvigorw assembleApp -p product=atomicserviceProduct -p buildMode=release

产物路径:build/outputs/atomicserviceProduct/atomicservice-default-signed.app


五、上传 AppGallery Connect

产物 上传入口 说明
lingxi-kitchen-v2-default-signed.app AGC 应用 → 版本管理 主应用完整包
atomicservice-default-signed.app AGC 元服务 → 版本管理 元服务独立包

注意事项

  1. 两个包使用同一套发布证书和 bundleName,元服务可正常唤醒主应用
  2. 元服务包的大小限制为 10MB(可申请放宽至 20MB)
  3. 主应用的应用名称和元服务的 label 必须与 AGC 填写的一致

六、设计决策

决策 选择 理由
主应用和元服务是否拆分工程 ------同一工程,不同产品 共享 HSP 代码,避免维护两套工程
元服务模块类型 type: "entry" AGC 元服务上架的硬性要求
atomicservice 是否被主应用包含 ------仅属于元服务产品 避免同一产品出现两个 entry 模块
shared 是否被两个产品共享 推荐引擎、数据模型等核心代码复用
构建模式指定方式 命令行 -p buildMode=release 较新版本已废弃 products 内的 buildMode 字段

七、本阶段总结

这次打包过程的核心教训是:HarmonyOS 的多模块工程通过"产品(Product)"来区分不同的构建产物,而不是通过拆分工程

  • 一个工程:维护成本最低,代码复用最方便
  • 两个产品default(主应用)和 atomicserviceProduct(元服务)
  • 两条命令:分别构建,分别上传

从开发到上架的最后一公里,配置的每一个字段都有其存在的原因。理解"Product"和"applyToProducts"的关系,是驾驭多模块工程的关键。


📚 本系列持续更新中:下一篇将介绍应用发布的完整流程------从签名校验到审核上架的全链路。

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

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

**如果你发现本文还有任何不严谨之处,欢迎随时指出,我们一起共建最优质的 HarmonyOS 6.1 学习内容!如果觉得有帮助,请不要吝啬你的点赞 👍、收藏 ⭐ 和评论 💬!

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

相关推荐
若兰幽竹2 小时前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(三十三):权限管理——用一套“安检系统”告别散装代码
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹1 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(三十二):【数据一致性】个人档案的“三重持久化”修复——让偏好、健康与头像真正同步
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹2 天前
HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(三十):【社区分享】本地社区功能——让菜谱从“独享”走向“共享”
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹2 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十九):【偏好持久化】偏好设置与推荐引擎联动——让 App 越用越“懂你”
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹5 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十七):告别 UI 冻结——使用 TaskPool 实现高性能并发图像分析
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹5 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十八):【数据持久化】收藏与浏览历史——让数据在 App 重启后依然“活着”
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹6 天前
HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(二十六):【响应式布局】折叠屏与平板完美适配——一套代码,多端呈现
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹7 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二十五):【深色模式】一键切换暗色主题——让 App 在深夜也温柔
华为鸿蒙系统·灵犀厨房·harmonyos6.1
若兰幽竹8 天前
【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二十二) | 多媒体 | AVPlayer嵌入教学视频——让智慧屏真正“活”起来
音视频·华为鸿蒙系统·harmonyos6.1.0·灵犀厨房·harmonyos6.1