在这里插入图片描述

你是不是也在想——“鸿蒙这么火,我能不能学会?”
答案是:当然可以!
这个专栏专为零基础小白设计,不需要编程基础,也不需要懂原理、背术语。我们会用最通俗易懂的语言、最贴近生活的案例,手把手带你从安装开发工具开始,一步步学会开发自己的鸿蒙应用。
不管你是学生、上班族、打算转行,还是单纯对技术感兴趣,只要你愿意花一点时间,就能在这里搞懂鸿蒙开发,并做出属于自己的App!
📌 关注本专栏《零基础学鸿蒙开发》,一起变强!
每一节内容我都会持续更新,配图+代码+解释全都有,欢迎点个关注,不走丢,我是小白酷爱学习,我们一起上路 🚀

前言

刚创建一个 Stage 模型工程时,entry/src/main/module.json5 往往已经被 DevEco Studio 填好了。很多时候我们只是继续往里面加权限、Ability 配置,却没有认真区分:name 是谁的名字,mainElement 指向谁,pages 为什么又跳到一个 profile 文件,两个位置都出现的 srcEntry 到底是不是一回事。

这篇文章不扩散到 app.json5、build-profile.json5 或 oh-package.json5,只沿着一个最小 entry 模块,把 name、type、srcEntry、mainElement、deviceTypes、pages/profile、abilities 串起来看清楚。

本文以 Stage 模型为前提。华为官方 HarmonyOS 7.0 升级指导明确给出了 HarmonyOS 7.0 与 API 26.0.0 的对应关系,并建议面向该版本升级开发套件。需要特别说明的是,module.json5 中这些字段属于模块配置描述,并不是 ArkTS 可调用 API,因此不能简单给每个 JSON 字段套一个“API 首次支持版本”;涉及本文示例代码中的 Ability Kit 能力,则需要按 API 文档核对版本。

一、先别把 module.json5 当成“应用总配置”

一个典型的 entry 模块可以看到这样的结构:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "srcEntry": "./ets/myabilitystage/MyAbilityStage.ets",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone",
      "tablet"
    ],
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets"
      }
    ]
  }
}

先不管其他配置,只看这几个字段,可以把它理解成一条入口链:

entry 模块
   │
   ├── name:这个 Module 叫什么
   ├── type:这个 Module 是什么类型
   ├── srcEntry:Module 级代码入口(配置 AbilityStage 时)
   ├── mainElement:这个 Module 的入口组件是谁
   ├── deviceTypes:这个 Module 面向哪些设备类型
   ├── pages:页面清单资源在哪里
   └── abilities:这个 Module 注册了哪些 UIAbility
          │
          └── srcEntry:某个 UIAbility 的实现代码在哪里

这里最容易理解错的是三个“入口”:module.srcEntry、mainElement、abilities[].srcEntry。它们不是三个不同写法表达同一件事,而是处在不同层级。

华为官方当前示例中,entry 模块仍采用 name: "entry"、type: "entry"、mainElement: "EntryAbility"、pages: "$profile:main_pages" 和 abilities[].srcEntry 这样的组织方式。

二、name:给 Module 一个逻辑名字

"name": "entry"

这里描述的是当前 Module 的名称,不是应用的 Bundle 名,也不是页面名,更不是 UIAbility 名。

这个名称并不只存在于源码配置阶段。华为官方拆包工具对 HAP 信息的说明中,HapInfo.name 表示当前 module 的名字;官方 Native 跨模块调用 FAQ 也明确使用 module.json5 中 module.name 识别入口模块。

所以看到:

"module": {
  "name": "entry"
}

可以先建立一个最简单的认知:

name 回答的是“这个模块叫什么”。

不要把它和下面的:

"abilities": [
  {
    "name": "EntryAbility"
  }
]

混在一起。前者是 Module 名,后者是 Ability 名,层级完全不同。

三、type:决定它是不是入口模块

"type": "entry"

本文只讨论 entry,因此不展开其他模块类型。

type: "entry" 表示当前 Module 是应用的入口类型模块。华为官方工程管理文档明确通过修改 module.json5 的 type 和 deviceTypes 配置模块,并说明同一个设备类型在一个工程中只支持一个 Entry 类型模块。官方构建 FAQ 也会根据 module.json5 的 type 判断模块对应的构建任务。

所以:

"name": "entry",
"type": "entry"

表达的是两件事:

name 是身份名称,type 是模块角色。

这里比较容易出现一个理解偏差:模块目录叫 entry,并不等于“因为目录名叫 entry,所以它天然就是入口模块”。从配置语义上真正声明模块类型的是 type。

四、srcEntry:先看清它出现在哪一层

srcEntry 是这几个字段里最容易看错的一个,因为它既可以出现在 module 层,也会出现在 abilities 的成员里。

1. module.srcEntry:指向 AbilityStage

例如:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "srcEntry": "./ets/myabilitystage/MyAbilityStage.ets"
  }
}

华为官方 AbilityStage 开发指导说明,AbilityStage 是 Module 级的组件容器,一个 AbilityStage 实例对应一个 Module。当应用的 HAP 首次加载时会创建 AbilityStage;如果需要使用自定义 AbilityStage,可以在 module.json5 的 Module 层通过 srcEntry 指定其代码路径。

对应的最小 ArkTS 可以写成:

import { AbilityStage } from '@kit.AbilityKit';

export default class MyAbilityStage extends AbilityStage {
  onCreate(): void {
    console.info('MyAbilityStage onCreate');
  }
}

这段代码解决的不是“打开首页”,而是给 Module 增加一个 AbilityStage 级入口。真正需要关注的是:这个 srcEntry 位于 module 对象直属层级。

如果项目根本没有自定义 AbilityStage,就不要为了“配置看起来完整”硬加一个。官方文档也明确指出,DevEco Studio 默认工程不会自动生成 AbilityStage,需要使用时再创建。

2. abilities[].srcEntry:指向具体 UIAbility

再看另一个:

"abilities": [
  {
    "name": "EntryAbility",
    "srcEntry": "./ets/entryability/EntryAbility.ets"
  }
]

这里的 srcEntry 属于某一个 Ability 配置,它指向这个 Ability 对应的代码实现。华为官方多个当前示例都使用这种方式,把 EntryAbility 与具体的 .ets 文件关联起来。

所以不要只记“srcEntry 是入口文件”。更准确的理解应该是:

先看 srcEntry 所在的配置层级,再判断它是谁的代码入口。

五、mainElement:它不是首页路径

"mainElement": "EntryAbility"

这个字段非常容易被理解成“首页”,但它并不直接指向 Index.ets。

华为官方拆包工具文档将 Stage 模型中的 mainElement 描述为 HAP 的入口 Ability 名称或者 Extension 名称。常见 entry 工程中,它配置为:

"mainElement": "EntryAbility"

而 abilities 中同时注册:

{
  "name": "EntryAbility",
  "srcEntry": "./ets/entryability/EntryAbility.ets"
}

也就是说,在这个最小场景里,mainElement 找的是入口组件 EntryAbility,而 EntryAbility 的实现文件再由该 Ability 自己的 srcEntry 找到。

这条关系可以写成:

mainElement
    ↓
"EntryAbility"
    ↓
abilities[].name
    ↓
abilities[].srcEntry
    ↓
EntryAbility.ets

真正的 ArkUI 页面还在下一层。

这也是为什么把:

"mainElement": "pages/Index"

理解成“设置首页”的思路是不对的。mainElement 与页面路径不是同一个概念。

六、deviceTypes:声明这个 Module 面向哪些设备类型

例如:

"deviceTypes": [
  "phone",
  "tablet"
]

华为官方工程管理文档直接通过修改 deviceTypes 来配置 Module 支持的设备类型,并给出了 entry 模块配置 phone、tablet 的示例;当前官方 FAQ 中也可以看到加入 "2in1" 的配置方式。

因此这里回答的问题是:

当前这个 Module 面向哪些设备类型进行安装和运行。

它不是 ArkUI 响应式布局代码,也不会替你完成不同屏幕尺寸下的界面适配。

例如:

"deviceTypes": [
  "phone",
  "tablet",
  "2in1"
]

表达的是模块支持范围发生了变化,不等于现有页面已经自动完成 Phone、Tablet、2in1 的布局适配。

官方拆包工具最终解析出的 PackInfo.deviceType 同样表示当前 HAP 包所支持的设备类型,这说明 deviceTypes 最终还会进入构建产物的模块描述信息,而不只是 IDE 中的一个提示字段。

七、pages 和 profile:它保存的是页面清单

常见配置是:

"pages": "$profile:main_pages"

这里并没有直接写:

pages/Index

而是引用一个 profile 资源。

对应文件通常位于:

entry
└── src
    └── main
        └── resources
            └── base
                └── profile
                    └── main_pages.json

最小内容可以是:

{
  "src": [
    "pages/Index"
  ]
}

华为官方 HAR 转 HSP 指导给出了同样的 pages: "$profile:main_pages" 与 resources/base/profile/main_pages.json 配置,并在 src 数组中登记 pages/PageIndex。官方 ArkUI 文档也明确要求目标页面加入 main_pages.json 后才能按相应页面路径使用。

这里要分清两个动作:

module.json5
pages: "$profile:main_pages"
        │
        ↓
resources/base/profile/main_pages.json
        │
        ↓
登记 pages/Index

它解决的是“这个 Module 有哪些已登记页面”的问题。

但应用启动后到底加载哪个页面,还要看 UIAbility 的窗口加载逻辑。

例如官方当前代码形式:

import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        console.error(`Failed to load page, code: ${err.code}`);
        return;
      }
      console.info('Page loaded.');
    });
  }
}

华为当前文档仍使用 UIAbility 配合 WindowStage.loadContent('pages/Index', ...) 加载页面。Ability Kit 的相关 Stage 模型基础接口从 API version 9 开始提供;HarmonyOS 7/API 26.0.0 环境覆盖这一版本要求。

所以,pages 和 loadContent() 也不要混为一谈:

pages/profile:页面登记
loadContent():某个 UIAbility 创建窗口后加载具体页面

八、abilities:真正注册 UIAbility 的地方

最后看:

"abilities": [
  {
    "name": "EntryAbility",
    "srcEntry": "./ets/entryability/EntryAbility.ets"
  }
]

abilities 是数组,因为一个 Module 可以配置多个 UIAbility。华为官方当前 FAQ 中就给出了在同一 abilities 数组中同时注册 EntryAbility 与 KeyboardAbility 的示例,每个 Ability 都有自己的 name 和 srcEntry。

对于本文的最小 entry 场景,我们只保留一个:

"abilities": [
  {
    "name": "EntryAbility",
    "srcEntry": "./ets/entryability/EntryAbility.ets"
  }
]

此时可以把整条关系完整连起来:

module.name = entry
        │
module.type = entry
        │
        ├── module.srcEntry
        │      └── MyAbilityStage.ets(如果配置自定义 AbilityStage)
        │
        ├── mainElement = EntryAbility
        │                  │
        │                  ↓
        └── abilities[].name = EntryAbility
                           │
                           ↓
                 abilities[].srcEntry
                           │
                           ↓
                    EntryAbility.ets
                           │
                           ↓
             windowStage.loadContent()
                           │
                           ↓
                     pages/Index
                           ↑
                           │
               main_pages.json 登记

如果能把这条链路建立起来,module.json5 就不再是一堆需要背诵的 JSON 字段。

九、搭一个最小可核对的 entry 配置

把前面的内容收回来,一个专门用于理解入口关系的最小示例可以组织为:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "srcEntry": "./ets/myabilitystage/MyAbilityStage.ets",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone",
      "tablet"
    ],
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets"
      }
    ]
  }
}

main_pages.json:

{
  "src": [
    "pages/Index"
  ]
}

MyAbilityStage.ets:

import { AbilityStage } from '@kit.AbilityKit';

export default class MyAbilityStage extends AbilityStage {
  onCreate(): void {
    console.info('MyAbilityStage onCreate');
  }
}

EntryAbility.ets:

import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        console.error(`Failed to load page, code: ${err.code}`);
        return;
      }
      console.info('Page loaded.');
    });
  }
}

Index.ets:

@Entry
@Component
struct Index {
  build() {
    Column() {
      Text('module.json5 entry demo')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

这组代码按照华为官方当前的 Stage 模型配置、AbilityStage、UIAbility 与页面加载方式组织,用途是观察各层配置关系,不包含网络、文件、相机、位置等受保护能力,因此这个最小场景本身不需要额外增加 requestPermissions。实际工程仍应按照自身使用的系统能力单独核对权限。

需要说明的是,这里没有声称上述代码已经在本地 HarmonyOS 7 工程编译运行;正式使用时仍应以目标 SDK 和 DevEco Studio 的配置 Schema 检查结果为准。

十、几个特别容易理解错的地方

1. mainElement 不是 Index.ets

mainElement 找的是入口 Ability;Index.ets 是页面。入口 Ability 可以在 onWindowStageCreate() 中通过 loadContent() 加载页面。

2. 两个 srcEntry 不能只看字段名

module.srcEntry

对应 Module 级 AbilityStage。

abilities[n].srcEntry

对应具体 Ability 的实现文件。

只搜索字段名而不看 JSON 层级,很容易把两者解释成同一件事。AbilityStage 官方开发指导也明确把 Module 层 srcEntry 用作 AbilityStage 源文件路径。

3. pages 不是“首页设置”

pages 指向页面清单 profile;真正由 UIAbility 创建窗口后加载哪个页面,可以在 loadContent() 中看到。

4. deviceTypes 不是响应式布局开关

增加 "tablet" 或 "2in1" 描述的是模块支持范围。页面是否适合这些设备,仍然是另一层的 UI 适配问题。官方工程管理文档也是把 deviceTypes 定义在 Module 配置层处理。

5. 不要给 JSON 配置字段硬套 API Level

HarmonyOS 7.0 对应 API 26.0.0,这是系统/API 版本关系;UIAbility、AbilityStage 等代码能力需要检查 API 文档,而 module.json5 字段还受到当前 SDK 配置 Schema 和构建工具校验。DevEco Studio 官方 FAQ 明确展示了字段缺失、字段拼写错误时出现 Schema validate failed,并可通过 instancePath、required、propertyNames 等信息定位到具体配置。

十一、实际项目中怎么排查这条入口链

遇到“应用入口不对”“Ability 找不到”“页面没有按预期加载”一类问题时,可以沿着配置关系检查,而不是一上来修改 ArkUI 页面。

建议顺序是:先确认当前检查的是 entry/src/main/module.json5;再确认 type 是否确实为 entry;然后看 mainElement 指向的名字能否在 abilities[].name 中找到对应组件;接着检查这个 Ability 的 srcEntry 是否对应实际 ArkTS 文件;如果使用了 Module 级 srcEntry,再确认它指向的是预期 AbilityStage;最后检查 pages 引用的 profile 与 main_pages.json 页面登记,以及 loadContent() 使用的页面路径是否对应。

如果编译阶段直接出现 Schema validate failed,官方建议优先看错误中的 instancePath 和 missingProperty 等信息,而不是靠猜字段名称修改。

开发经验总结

module.json5 最适合按“Module → Ability → Page”三层关系理解,而不是逐字段死记。

name 和 type 先确定“这是哪个模块、它承担什么角色”;Module 层 srcEntry 在需要时接到 AbilityStage;mainElement 确定入口组件;abilities 注册 UIAbility,并通过各自的 srcEntry 接到具体实现;pages 再通过 $profile:main_pages 接到页面清单;最终由 UIAbility 的窗口加载逻辑决定具体加载哪个页面。

真正需要记住的,其实只有一句:

mainElement 找组件,abilities[].srcEntry 找组件代码,pages/profile 登记页面,loadContent() 才把具体页面装进 UIAbility 的窗口;而 Module 层的 srcEntry 是另一层——它用于指定 AbilityStage。

如果以后再看到一份复杂的 module.json5,可以先把其他字段暂时折叠,只留下本文这七组配置。入口链路看清以后,再继续理解权限、ExtensionAbility、skills 等配置,会容易很多。

❤️ 如果本文帮到了你…

  • 请点个赞,让我知道你还在坚持阅读技术长文!
  • 请收藏本文,因为你以后一定还会用上!
  • 如果你在学习过程中遇到bug,请留言,我帮你踩坑!
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐