Harmony os 技术实战|拼豆制图12:用 module.json5 锁死手机、平板与 2in1 的唯一入口
标签Harmony os、ArkTS、module.json5、Stage 模型、多设备适配页面在预览器里能运行不代表应用入口配置就是完整的。实际工程中经常出现这样的错位mainElement写的是一个名字abilities数组登记的是另一个名字main_pages.json已经改成新路径loadContent仍然加载旧页面配置声称支持平板和 2in1页面却把内容宽度固定在 360vp。这些问题不会集中报在同一处。它们可能表现为安装失败、点击图标无反应、启动白屏、图标资源缺失或者大屏上只有一条被过度拉伸的手机页面。根因却是同一个构建配置、模块声明、系统入口、页面清单和响应式实现没有形成闭环。拼豆制图只有一个entry模块、一个桌面主入口和一个 ArkUI 首页但同时声明支持 phone、tablet、2in1。本文把这组配置整理成一份可以逐项核对的“入口契约”。本文重点解决如何从build-profile.json5确认 Stage 模型与构建目标。module.type、mainElement、abilities.name、srcEntry如何互相约束。deviceTypes为什么是产品与安装承诺而不只是一个字符串数组。启动图标、背景色、页面清单如何通过资源引用连接起来。如何把备份扩展能力留在独立边界避免它变成第二个主入口。如何用静态检查和三种设备形态验证整条入口链。配置文件不是说明文档而是可执行契约入口链涉及的文件不多但每个文件回答的问题不同文件决定什么与谁必须一致根build-profile.json5产品、签名、构建模式模块构建配置entry/build-profile.json5Stage 模型、targets、构建选项模块类型与源码组织entry/src/main/module.json5模块、设备、Ability、扩展能力Ability 源码与资源main_pages.json可装载的 ArkUI 页面路径页面文件与loadContentEntryAbility.ets运行时加载哪个首页面页面清单其中任何一条连接断开都不应靠页面代码兜底。例如页面清单漏掉pages/Index时Index.ets写得再正确也无法被装载srcEntry指向不存在的文件时也不是 Builder 能解决的问题。工程维护时应把这些字段当作引用关系而不是孤立配置项。先确认应用模型避免混入另一套入口语义entry/build-profile.json5明确声明当前模块使用 Stage 模型{ apiType: stageMode, targets: [ { name: default }, { name: ohosTest } ] }这项确认决定了后续使用UIAbility、WindowStage和 Stage 模型的模块结构。不要在同一篇配置里把另一种应用模型的入口概念混进来也不要因为旧项目中出现相似字段就直接复制。两个 target 的职责也要分开default是正常构建目标ohosTest用于设备测试模块。测试目标存在不会自动成为安装入口更不会替代module.json5中的mainElement。若入口行为异常第一步先确认正在构建哪个 target、模块是否仍为stageMode再检查 Ability 代码。entry 类型负责给应用提供安装入口当前模块的核心声明如下{ module: { name: entry, type: entry, mainElement: EntryAbility, deliveryWithInstall: true, installationFree: false } }五个字段表达了不同层次name是模块身份构建脚本和依赖关系会使用它。type: entry表示这是应用的入口模块。mainElement指向系统启动时要找到的主元素名称。deliveryWithInstall表示该模块随应用安装交付。installationFree描述当前模块不走免安装形态。不要把mainElement写成pages/Index。系统首先启动的是 AbilityAbility 获得窗口后才装载 ArkUI 页面。把页面路径和 Ability 名称混用会把系统入口层与页面层搅在一起。mainElement、name 与 srcEntry 必须三点闭合mainElement的值必须在abilities数组找到同名 AbilitymainElement: EntryAbility, abilities: [ { name: EntryAbility, srcEntry: ./ets/entryability/EntryAbility.ets, description: $string:EntryAbility_desc, icon: $media:layered_image, label: $string:EntryAbility_label } ]这组关系可以写成一个简单等式module.mainElement abilities[i].name - abilities[i].srcEntry 对应的真实文件改名时最容易只改一半。例如把类文件移动到abilities/MainAbility.ets却保留原srcEntry或者把name改成MainAbility但mainElement仍是EntryAbility。稳妥做法是把改名作为一次原子操作同时修改主元素名、Ability 名、源码路径和相关测试再立即构建不要把半完成状态跨提交保留。桌面入口 skills 决定系统如何发现 Ability拼豆制图的主 Ability 配置了桌面入口能力exported: true, skills: [ { entities: [ entity.system.home ], actions: [ ohos.want.action.home ] } ]skills描述系统可以用什么实体和动作匹配到该 Ability。这里的目标是让应用成为桌面可启动入口而不是开放任意业务调用。exported需要结合入口用途审视。主桌面入口与内部备份扩展能力的暴露策略不同前者承担系统入口后者当前明确为false。不要为了“让调用成功”把所有 Ability 和 ExtensionAbility 都改成可导出那会扩大不必要的外部边界。如果未来增加分享、文件打开或快捷入口应为新的 Want 规则单独定义匹配条件并在 Ability 内校验参数而不是把桌面入口的skills无限扩张。deviceTypes 是可以安装和运行的产品承诺当前模块声明三种设备类型deviceTypes: [ phone, tablet, 2in1 ]这不是“希望有一天适配”的备忘录。声明某种设备后至少应保证入口资源、首帧、核心交互和主要布局在该形态可用。拼豆制图把声明与页面响应式规则对应起来设备形态入口配置页面最小验证phonephone360vp 左右双列图库、底部导航不遮挡tablettablet600vp 以上三列、内容居中且不贴边2in12in1840vp 以上四列、窗口缩放时断点可重算配置只决定系统是否把应用视为支持该设备不能替页面完成自适应。若大屏只是把手机布局横向放大应先收紧deviceTypes或补齐响应式实现再对外承诺支持。页面清单与 loadContent 要共享同一条路径模块通过 profile 资源引用页面清单pages: $profile:main_pages对应main_pages.json{src:[pages/Index]}Ability 仍使用同一个页面标识windowStage.loadContent(pages/Index,(err){if(err.code){hilog.error(DOMAIN,PindouStartup,loadContent failed: %{public}s,JSON.stringify(err));return;}hilog.info(DOMAIN,PindouStartup,%{public}s,Index loaded);});页面新增后可以进入src数组但“列进清单”不等于“自动成为首页”。首帧仍由loadContent的路径决定。反过来Ability 写了一个清单中不存在的页面也不会因为文件恰好存在就可靠装载。排查入口白屏时按真实文件 → 页面清单 →loadContent的顺序核对通常比改 UI 更快。启动窗口资源也属于入口契约主 Ability 同时声明启动阶段资源{ icon: $media:layered_image, label: $string:EntryAbility_label, startWindowIcon: $media:startIcon, startWindowBackground: $color:start_window_background }这些引用必须在对应资源目录存在。尤其是start_window_background它发生在 ArkUI 首页完全建立之前若启动背景与首页page_bg差异过大用户会看到明显闪变。当前浅色资源把两者都设为接近白色的背景{name:start_window_background,value:#FFFAFC},{name:page_bg,value:#FFFAFC}深色目录也应提供同名资源。这样系统切换颜色模式时启动窗口与首页使用同一语义层次而不是只把页面改深、启动阶段仍然闪白。字符串和媒体资源不要直接写死在模块配置里模块描述、入口描述和标签当前都通过资源引用description: $string:module_descbase/element/string.json维护真实文本{string:[{name:module_desc,value:拼豆制图},{name:EntryAbility_desc,value:拼豆图纸制作工具},{name:EntryAbility_label,value:拼豆制图}]}资源引用的好处不只是复用。它让不同限定目录可以提供相同名字的变体也让配置结构保持稳定。修改入口名称时只改资源值不必触碰模块层的引用关系。需要注意的是资源“能找到”与资源“适合该场景”是两件事。启动图标要验证裁切安全区深色背景要检查对比度2in1 桌面环境还要确认小尺寸图标仍然清楚。备份 ExtensionAbility 必须与主入口隔离项目还声明了一个备份扩展能力extensionAbilities: [ { name: EntryBackupAbility, srcEntry: ./ets/entrybackupability/EntryBackupAbility.ets, type: backup, exported: false, metadata: [ { name: ohos.extension.backup, resource: $profile:backup_config } ] } ]它与主入口有三个明确区别类型是backup由备份 metadata 描述能力且不对外导出。它不应出现在mainElement也不负责加载pages/Index。扩展能力的配置错误可能在普通启动时不暴露因此入口验收和备份验收要分开。删除备份能力时也要连同srcEntry、metadata 资源和相关 profile 一起审计避免留下悬空引用。构建变体不能悄悄改变入口含义当前模块定义了默认与测试 target并在 release 选项中维护混淆配置。无论选择哪个构建变体主入口的名称、页面路径和设备声明都不应该被意外改写。可以建立一份变体检查表检查项defaultreleaseohosTestEntryAbility.ets可解析必须必须被测主模块必须可用pages/Index可装载必须必须测试可启动主页面入口资源完整必须必须按测试目的确认deviceTypes未漂移必须必须不由测试 target 扩大如果 release 才出现启动失败要优先比较构建选项、资源裁剪和混淆影响而不是假设页面逻辑在 release 中自动不同。用静态脚本提前发现悬空引用配置问题适合在运行前检查。下面的 Python 脚本不尝试完整解析 JSON5而是针对当前工程的关键关系做窄检查frompathlibimportPathimportre ROOTPath(entry/src/main)module_text(ROOT/module.json5).read_text(encodingutf-8)pages_text(ROOT/resources/base/profile/main_pages.json)\.read_text(encodingutf-8)main_elementre.search(rmainElement\s*:\s*([^]),module_text).group(1)ability_namesre.findall(rname\s*:\s*([A-Za-z0-9_]Ability),module_text)assertmain_elementinability_namesassertpages/Indexinpages_textassert(ROOT/ets/pages/Index.ets).exists()assert(ROOT/ets/entryability/EntryAbility.ets).exists()print(entry contract ok)这段脚本只承担“发现明显悬空关系”的职责不能替代系统对完整配置格式、资源引用和设备能力的构建检查。它适合放在批量改名或目录迁移之后快速运行。进一步还可以读取 base 与 dark 的资源名集合确认start_window_background在两边都存在。三种设备要验证同一条主路径入口验证分为构建、启动和布局三层hvigor assembleHap--no-daemon构建通过后按设备形态执行相同动作冷启动 → 进入图库 → 打开编号图 → 返回首页 → 切后台再回来。形态入口验证布局验证交互验证手机图标、启动背景、首页正常双列卡片、底栏完整选图与导出入口可触达平板同一 Ability 启动三列卡片、内容有最大宽度横纵方向都能操作2in1桌面入口可发现四列卡片、窗口缩放重排鼠标点击与滚动区域清楚不要用手机构建成功替代平板和 2in1 的实际布局验证。deviceTypes解决的是系统侧支持声明页面断点解决的是运行时布局两层证据缺一不可。常见配置故障按引用关系排查现象优先检查常见根因修复方式点击图标无反应mainElement与abilities.name主元素名改了一半同步改名并构建WindowStage 创建后白屏页面清单与loadContent路径大小写或目录不一致三处统一为真实路径编译报告找不到 AbilitysrcEntry文件移动后引用未更新修正相对路径启动阶段闪白启动背景与页面背景dark 缺少同名资源补齐深色资源并验证平板无法安装或入口缺失deviceTypes与产物未声明或构建了错误 target核对模块声明与目标平板页面仍像手机onAreaChange与断点函数只有设备声明没有布局适配增加运行时重排备份能力意外暴露Extension 的exported复制主入口配置按扩展能力用途收紧release 才启动失败release 构建选项资源或混淆配置差异对比变体产物和日志配置排障的核心是沿引用前进产品选择模块模块选择 AbilityAbility 选择页面页面消费资源。不要从最终白屏反向同时修改所有文件。小结多设备入口稳定的关键不是把更多设备名写进deviceTypes而是让配置链每一环都能被验证Stage 模型明确entry 模块唯一mainElement能找到 AbilitysrcEntry指向真实文件页面清单与loadContent同名启动资源在深浅模式下都完整。在这条链路之上phone、tablet、2in1 的声明才有实际意义。入口配置负责“系统能找到并启动应用”响应式页面负责“不同窗口里仍然可用”两者共同构成真正的多设备支持。