【HarmonyOS 6.1 全场景实战】《灵犀厨房》实战(二):Stage模型与“一次开发,多端部署”的骨架
本文介绍了HarmonyOS 6.1.0中Stage模型的核心概念及其在《灵犀厨房》项目中的应用。Stage模型通过解耦应用组件与窗口,实现原子化能力集合,支持多设备适配。文章详细解析了app.json5和module.json5配置文件的关键属性,包括bundleName、deviceTypes等,并强调API 23新增标签的注意事项。开发者需重点关注多端适配的声明配置,同时避免使用废弃属性。
HarmonyOS 6.1 开发者盛宴|《灵犀厨房》实战(二):Stage模型与“一次开发,多端部署”的骨架
摘要:在上一篇中,我们跑通了 API 23 的《灵犀厨房》首个页面。但“全场景”的秘密武器到底藏在哪里?答案就是 Stage 模型。今天,我们就来彻底拆解 Stage 模型的核心概念、项目结构,并依据官方最新文档,为《灵犀厨房》搭建一套能自适应手机、平板和智慧屏的“多端部署”骨架。
一、为什么 Stage 模型是“全场景”的基石?
很多从 Android 或 iOS 转过来的开发者,看到 EntryAbility 可能会本能地把它当成 Activity。这是一个巨大的误解。
HarmonyOS 6.1.0 主推的 Stage 模型,核心设计思想是:将应用组件(Ability)从窗口(Window)中彻底解耦。
- FA 模型(历史):高度绑定 UI 界面,组件间跳转规则复杂,不同设备适配极其痛苦。
- Stage 模型(现在):
- 应用:由 1 个或多个 Module 组成,实现功能解耦。
- Module:独立的编译、运行单元。比如把“菜谱浏览”做成 Feature Module。
- UIAbility:负责创建窗口并与用户交互,但它不直接等于一个页面。一个 UIAbility 可在不同设备上,通过窗口管理,以完全不同的形态呈现。
一句话总结:我们写的《灵犀厨房》不再是“手机 App”,而是一个可以按需解构、组合、流转的原子化能力集合。
二、解剖《灵犀厨房》Stage 模型项目骨架
让我们重新审视 LingxiKitchen 项目,你会发现很多为全场景而生的“暗门”。
2.1 核心配置:app.json5 与 module.json5
-
AppScope/app.json5(应用级配置)
这是整个应用的“身份证”。在 HarmonyOS 6.1.0 中,我们按官方属性进行配置:{ "app": { "bundleName": "com.annan.lingxikitchen", "vendor": "annan", "versionCode": 1000000, "versionName": "1.0.0", "buildVersion": "1", "icon": "$media:layered_image", "label": "$string:app_name", "debug": true, "bundleType": "app", "description": "$string:app_desc", "multiAppMode": { "multiAppModeType": "appClone", "maxCount": 2 } } }核心属性解读:
bundleName:应用唯一标识,必须三段以上,采用反域名格式,这是应用的“户口名”。bundleType:我们设为app,代表这是一个常规应用;未来做元服务时,会改成atomicService。debug:开发期设为true,方便调错;正式发布前一定要改回false。versionCode&versionName:前者为正整数,数值越大版本越新,用于系统判断;后者是给用户看的版本字符串(如1.0.0)。buildVersion:API 23 新增标签,建议采用A.B.C三段式,让我们能更精细地追踪构建。multiAppMode:配置应用分身,让《灵犀厨房》理论上支持双开,一个用作家庭版,一个用作个人版。- 特别注意:
minAPIVersion和targetAPIVersion由 IDE 编译时自动生成,此处无需也没必要手动配置。distributedNotificationEnabled已从 API 9 废弃,不再生效,移除它。 - 详情请查看官网文档:app.json5配置文件
注意:description的值需要在DevEco中配置一个,如下所示:
-
entry/src/main/module.json5(模块级配置)
这是《灵犀厨房》当前唯一主模块的“户口本”,我们的多端适配就从这里开始:{ "module": { "name": "entry", "type": "entry", "description": "$string:module_desc", "mainElement": "EntryAbility", "deviceTypes": [ "phone", "tablet", "tv" ], "deliveryWithInstall": true, "installationFree": false, "pages": "$profile:main_pages", "abilities": [ { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", "description": "$string:EntryAbility_desc", "icon": "$media:layered_image", "label": "$string:EntryAbility_label", "startWindowIcon": "$media:startIcon", "startWindowBackground": "$color:start_window_background", "exported": true, "skills": [ { "entities": [ "entity.system.home" ], "actions": [ "ohos.want.action.home" ] } ] } ], "extensionAbilities": [ { "name": "EntryBackupAbility", "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", "type": "backup", "exported": false, "metadata": [ { "name": "ohos.extension.backup", "resource": "$profile:backup_config" } ] } ] } }核心属性解读(基于官方文档支持的属性):
deviceTypes:这是多端适配的第一步。加入"tablet"和"tv",意味着《灵犀厨房》声明支持在这些设备上运行。仅是系统允许还不够,后面必须辅以布局适配。startWindowIcon:冷启动时,鸿蒙会立刻显示这张图,产生“秒开”的错觉,是官方推荐的重要优化项。installationFree:用于元服务免安装,常规应用我们保持false。- 详情请查看官网文档:module.json5配置文件
需要你特别留意的四个关键点:
installationFree:开发者只读,编译时自动决定
根据官方说明,这个标签手动配置不生效。当应用为元服务(bundleType: "atomicService")时,它会被强制设为true;常规应用则强制为false。我们当前只需保持false,无需干预。
2.pages:并非“页面文件夹”,它是路由清单pages对应的$profile:main_pages,实际指向的是resources/base/profile/main_pages.json文件。这个文件里才会一一列出pages/Index、pages/RecipeDetail等具体页面路径。若你新建了页面但没在此注册,路由将无法跳转。
3.deviceTypes:不仅是声明,更是系统分发的依据
我们配了"phone"、"tablet"、"tv",意味着《灵犀厨房》会在这些设备上被允许运行。但系统允许 ≠ 体验好,后续我们必须为它们分别做布局适配。
4. 废弃标签勿碰:uiSyntax、srcEntrance
它们从 API 9 起就被废弃了。本篇没有使用这些过时属性,你后续在其他项目或旧教程中看到时,可以直接忽略。
那 API 23 下新增了哪些可用的
module.json5标签?
结合你的官方说明,虽然我们现阶段暂未启用,但你得知道它们的存在和用途,方便后续扩展:shareFiles(API 23 新增):配置应用文件的安全分享范围,是鸿蒙对用户隐私保护的进一步强化。easyGo(API 23 新增):用于实现平行视界分栏等产品定制能力,平板适配时会用到。executableBinaryPaths(API 24 新增):我们目前基于 API 23,暂不涉及,先行了解即可。
有了对
app.json5和module.json5的深度理解,我们的《灵犀厨房》骨架才算真正搭建得坚实。下面,就进入“多设备适配”的实战环节。
2.2 生命周期洞察:EntryAbility.ets
打开 entryability/EntryAbility.ets,这就是《灵犀厨房》在移动设备上的“主窗口经理”:
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
try {
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
} catch (err) {
hilog.error(DOMAIN, 'LingxiKitchen', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
}
hilog.info(DOMAIN, 'LingxiKitchen', '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(DOMAIN, 'LingxiKitchen', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(DOMAIN, 'LingxiKitchen', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'LingxiKitchen', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'LingxiKitchen', 'Succeeded in loading the content.');
});
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(DOMAIN, 'LingxiKitchen', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(DOMAIN, 'LingxiKitchen', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(DOMAIN, 'LingxiKitchen', '%{public}s', 'Ability onBackground');
}
}
核心要点:
UIAbility不等于 UI 界面。它本身没视图,管理器WindowStage才负责调度。- 前后台监听:
onForeground和onBackground是实现“流转恢复”逻辑的关键。比如用户做饭时,手机切后台,手表切前台自动继续计时。
三、实战:搭建多设备适配骨架
清晰的 Stage 模型和配置之后,要让《灵犀厨房》能在手机、平板、智慧屏上运行得游刃有余,我们需掌握资源限定词和窗口适配。
3.1 资源限定词:一套代码,多套皮肤
HarmonyOS 的资源匹配机制,能让应用根据设备类型、语言、屏幕密度等自动选用最合适的资源。base 目录是默认存在的,里面存放通用资源;当我们需要针对特定设备做差异化时,就在 resources 下创建限定词目录。
限定词目录的命名必须遵循官方规则:语言_文字_国家或地区-横竖屏-设备类型-颜色模式-屏幕密度。我们这里只为平板做适配,因此只需创建一个设备类型限定词目录 tablet。
操作步骤:
-
手机默认资源(
base目录)
打开entry/src/main/resources/base/element/string.json,修改如下:{ "string": [ { "name": "module_desc", "value": "灵犀厨房手机版" }, { "name": "EntryAbility_desc", "value": "灵犀厨房主入口" }, { "name": "EntryAbility_label", "value": "灵犀厨房" } ] } -
平板专用资源(
tablet限定词目录)
在resources目录上右键,选择 New > Resource Directory,选择限定词 Device type 为tablet,资源组类型选择Element,IDE 会自动生成tablet/element目录。



然后在该目录下新建
string.json(完整文件路径entry/src/main/resources/tablet/element/string.json):{ "string": [ { "name": "module_desc", "value": "灵犀厨房平板大屏版" }, { "name": "EntryAbility_label", "value": "灵犀厨房 HD" } ] }验证:在 Phone 模拟器上桌面图标显示“灵犀厨房”,应用内描述为“手机版”;在 Tablet 模拟器上桌面图标显示“灵犀厨房 HD”,描述为“平板大屏版”。系统根据设备类型自动匹配 tablet 限定词目录,找不到的资源则回退到 base。
资源匹配规则补充:
当应用请求某个资源时,系统会按照 MCC/MNC > 区域(语言、文字、国家/地区) > 横竖屏 > 设备类型 > 颜色模式 > 屏幕密度 的优先级,寻找最匹配的限定词目录。只有找不到时,才会使用base中的资源。rawfile和resfile目录不参与匹配,里面的文件会原样打包。
平板模拟器正常运行:
3.2 窗口尺寸自适应(ArkTS 响应式布局)
不同设备屏幕形态天差地别,纯靠限定词文件夹不够灵活。ArkTS 提供强大的断点系统来实时感知窗口变化。
在 Index.ets 中,我们改写代码,让《灵犀厨房》首屏能智能识别横竖屏并改变布局(注意:在模拟器中没有横竖屏按钮,需要使用多屏功能来验证):
import { window } from '@kit.ArkUI';
@Entry
@Component
struct Index {
@State appName: string = '灵犀厨房';
@State slogan: string = '你的AI私人厨艺助手';
@State currentBreakpoint: string = 'sm';
private windowClass: window.Window | null = null;
private sizeChangeCallback: ((size: window.Size) => void) | null = null;
// 根据窗口宽度手动计算断点(参照官方推荐阈值)
private getBreakpoint(width: number): string {
if (width >= 840) return 'lg';
if (width >= 600) return 'md';
return 'sm';
}
async aboutToAppear(): Promise<void> {
try {
this.windowClass = await window.getLastWindow(getContext(this));
if (this.windowClass) {
const rect = this.windowClass.getWindowProperties().windowRect;
console.info('初始窗口宽度: ' + rect.width + ', 高度: ' + rect.height);
this.currentBreakpoint = this.getBreakpoint(rect.width);
console.info('初始断点: ' + this.currentBreakpoint);
this.sizeChangeCallback = (size: window.Size): void => {
console.info('【回调触发】新窗口宽度: ' + size.width);
const bp = this.getBreakpoint(size.width);
this.currentBreakpoint = bp;
console.info('【回调触发】当前断点切换为: ' + bp);
};
this.windowClass.on('windowSizeChange', this.sizeChangeCallback);
console.info('已注册 windowSizeChange 监听');
}
} catch (err) {
console.error('获取窗口实例失败', JSON.stringify(err));
}
}
aboutToDisappear(): void {
if (this.windowClass && this.sizeChangeCallback) {
this.windowClass.off('windowSizeChange', this.sizeChangeCallback);
}
}
build() {
Flex({
direction: this.currentBreakpoint === 'sm' ? FlexDirection.Column : FlexDirection.Row,
wrap: FlexWrap.NoWrap,
justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Center
}) {
Column() {
Text('🍳')
.fontSize(this.currentBreakpoint === 'sm' ? 80 : 120)
Text(this.appName)
.fontSize(this.currentBreakpoint === 'sm' ? 36 : 48)
.fontWeight(FontWeight.Bold)
.fontColor('#FF6B35')
}
.margin({ bottom: this.currentBreakpoint === 'sm' ? 30 : 0 })
Column({ space: 15 }) {
Text(this.slogan)
.fontSize(18)
.fontColor('#999999')
Button('开始点菜')
.fontSize(18)
.backgroundColor('#FF6B35')
.borderRadius(24)
.padding({ left: 40, right: 40 })
}
.margin({ left: this.currentBreakpoint === 'sm' ? 0 : 40 })
}
.height('100%')
.width('100%')
.backgroundColor('#FFF8F0')
}
}
验证实录:我们在 Phone 模拟器的多屏模式下,将《灵犀厨房》拖拽到不同屏幕,Log 输出如下:
初始窗口宽度: 1280, 高度: 2832
初始断点: lg
已注册 windowSizeChange 监听
【回调触发】新窗口宽度: 1320
【回调触发】当前断点切换为: lg
注意:单 Phone 模拟器旋转屏幕时,窗口像素宽高不变,因此该事件不会触发(不是代码问题)。要体验完整响应式效果,你可以:
- 分屏/多窗模式:手动缩小窗口宽度,断点将实时切换(md / sm)。
- 平板模拟器:横竖屏切换通常伴随窗口尺寸变化。
- 真机:旋转屏幕时绝大多数机型会触发 windowSizeChange。
- 预览器:直接点击横竖屏切换按钮,预览器会模拟断点变化。
四、本阶段总结与下篇预告
今天,我们将《灵犀厨房》的“骨架”——Stage 模型彻底梳理了一遍。你学到了:
- Stage 模型的核心理念:应用、Module、UIAbility、Window 的分层与职责。
- 关键配置文件的正确写法:严格依据 HarmonyOS 6.1.0 官方文档,逐项解析
app.json5和module.json5。 - 多设备适配实战:
deviceTypes声明、资源限定词、ArkTS 断点响应式布局。
下篇预告:基础打牢了,是时候真正写代码了!下一篇《ArkTS 高效开发:TypeScript 核心与 API 23 新规》,我将带你掌握 ArkTS 语言精髓及官方推荐的最新语法规范。
粉丝专属福利:为感谢大家支持,点赞 + 收藏 本专栏任意文章,并在评论区留言“纯血鸿蒙,我准备好了”,私信我即可领取《HarmonyOS 6.0 安全技术白皮书》电子版!
本文源码地址:
https://gitee.com/sulongannababy/lingxi-kitchen(第二章代码已同步更新)
专栏目录:点击查看全部40篇文章预告
如果你发现本文还有任何不严谨之处,欢迎随时指出,我们一起共建最优质的 HarmonyOS 6.1 学习内容!如果觉得有帮助,请不要吝啬你的点赞 👍、收藏 ⭐ 和评论 💬,我们下一篇见~
更多推荐




所有评论(0)