HarmonyOS 工程目录、配置文件与 Stage 模型核心

本文献给:

已掌握 UI 构建、状态管理和生命周期的鸿蒙开发者。本文将深入讲解 HarmonyOS 工程的目录结构、核心配置文件(app.json5 和 module.json5)的详细含义与用法,以及 Stage 模型下 UIAbility 的生命周期、三种启动模式和 Want 跨 Ability 跳转机制。掌握这些知识后,你将具备从项目全局视角理解和配置应用的能力。

你将学到:

  1. 鸿蒙工程级目录结构中各文件夹的职责
  2. app.json5 应用全局配置详解
  3. module.json5 模块配置详解(Ability 声明、权限、窗口模式)
  4. Ability 的概念与 UIAbility 生命周期
  5. singleton、multiton、specified 三种启动模式的区别与配置
  6. Want 的结构与显式/隐式跳转及参数传递

目录

  • 一、工程级目录结构详解
    • [1.1 各目录职责速览](#1.1 各目录职责速览)
    • [1.2 资源目录区分:base、rawfile 与 element](#1.2 资源目录区分:base、rawfile 与 element)
  • [二、核心配置文件详解 ------ app.json5](#二、核心配置文件详解 —— app.json5)
    • [2.1 完整示例](#2.1 完整示例)
    • [2.2 关键字段说明](#2.2 关键字段说明)
  • [三、核心配置文件详解 ------ module.json5](#三、核心配置文件详解 —— module.json5)
    • [3.1 完整示例](#3.1 完整示例)
    • [3.2 关键字段说明](#3.2 关键字段说明)
    • [3.3 main_pages.json 路由文件](#3.3 main_pages.json 路由文件)
  • [四、Ability 概念与 UIAbility 生命周期](#四、Ability 概念与 UIAbility 生命周期)
    • [4.1 什么是 Ability](#4.1 什么是 Ability)
    • [4.2 UIAbility 生命周期](#4.2 UIAbility 生命周期)
    • [4.3 生命周期回调触发时机](#4.3 生命周期回调触发时机)
    • [4.4 典型生命周期流程](#4.4 典型生命周期流程)
  • [五、启动模式 ------ singleton、multiton、specified](#五、启动模式 —— singleton、multiton、specified)
    • [5.1 singleton ------ 单实例模式(默认)](#5.1 singleton —— 单实例模式(默认))
    • [5.2 multiton ------ 多实例模式](#5.2 multiton —— 多实例模式)
    • [5.3 specified ------ 指定实例模式](#5.3 specified —— 指定实例模式)
    • [5.4 三种模式对比](#5.4 三种模式对比)
  • [六、Want 详解与跨 Ability 跳转](#六、Want 详解与跨 Ability 跳转)
    • [6.1 Want 的结构](#6.1 Want 的结构)
    • [6.2 显式 Want 跳转](#6.2 显式 Want 跳转)
    • [6.3 隐式 Want 跳转](#6.3 隐式 Want 跳转)
    • [6.4 startAbilityForResult 获取返回结果](#6.4 startAbilityForResult 获取返回结果)
  • [七、综合示例 ------ 多 Ability 跳转与模式测试](#七、综合示例 —— 多 Ability 跳转与模式测试)
  • 八、常见错误与注意事项
    • [8.1 页面未在 main_pages.json 中注册](#8.1 页面未在 main_pages.json 中注册)
    • [8.2 Ability 未在 module.json5 中声明](#8.2 Ability 未在 module.json5 中声明)
    • [8.3 启动模式配置不匹配预期行为](#8.3 启动模式配置不匹配预期行为)
    • [8.4 隐式 Want 匹配失败](#8.4 隐式 Want 匹配失败)
    • [8.5 rawfile 路径错误](#8.5 rawfile 路径错误)
  • 九、小结

一、工程级目录结构详解

使用 DevEco Studio 创建一个 Stage 模型的 HarmonyOS 项目后,目录结构如下:

复制代码
MyApplication
├── AppScope
│   ├── resources
│   │   └── base
│   │       ├── element
│   │       └── media
│   └── app.json5
├── entry
│   ├── src
│   │   ├── main
│   │   │   ├── ets
│   │   │   │   ├── entryability
│   │   │   │   │   └── EntryAbility.ts
│   │   │   │   └── pages
│   │   │   │       └── Index.ets
│   │   │   ├── resources
│   │   │   │   ├── base
│   │   │   │   │   ├── element
│   │   │   │   │   ├── media
│   │   │   │   │   └── profile
│   │   │   │   │       └── main_pages.json
│   │   │   │   └── rawfile
│   │   │   └── module.json5
│   │   ├── ohosTest
│   │   │   └── ...
│   │   └── test
│   │       └── ...
│   ├── build-profile.json5
│   └── hvigorfile.ts
├── hvigor
│   └── hvigor-config.json5
├── oh_modules
├── build-profile.json5
└── hvigorfile.ts

1.1 各目录职责速览

目录 / 文件 职责
AppScope/ 应用全局配置与资源,独立于模块,包含 app.json5 和全局图标等
AppScope/app.json5 应用级别配置:包名、版本、图标、名称等
entry/ 主模块(HAP),应用的功能代码和资源主要放在此
entry/src/main/ets/ ArkTS 源码目录,包含 Ability 和 pages 等
entry/src/main/ets/entryability/ UIAbility 代码,应用的入口 Ability
entry/src/main/ets/pages/ 页面组件目录
entry/src/main/resources/ 模块级资源:字符串、图片、布局文件等
entry/src/main/resources/base/profile/ 配置文件,如路由页面列表 main_pages.json
entry/src/main/resources/rawfile/ 原始文件(任意格式,不会被编译),运行时按原样读取
entry/src/main/module.json5 模块配置文件:声明 Ability、权限、窗口模式等
hvigor/ 构建工具 hvigor 的配置文件
oh_modules/ 依赖包存储目录,类比 node_modules
build-profile.json5(根目录) 应用级构建配置(编译 SDK 版本、签名等)
build-profile.json5(模块内) 模块级构建配置

1.2 资源目录区分:base、rawfile 与 element

  • resources/base/ :存放需要适配的资源,可扩展多语言/多分辨率目录(如 zh_CNen_US),系统根据设备配置自动匹配。
  • resources/base/media/ :图片、音频等媒体文件,通过 $r('app.media.filename') 引用。
  • resources/base/element/ :字符串、颜色、尺寸等,通过 $r('app.string.name') 引用,支持国际化。
  • resources/base/profile/ :JSON 配置文件(如 main_pages.json),通过 $profile:main_pages 引用。
  • resources/rawfile/ :原始文件目录,文件会原样打包到应用中,运行时通过 resourceManager.getRawFileContent() 读取,适用于大文本、配置文件等无需编译的资源。

二、核心配置文件详解 ------ app.json5

app.json5 位于 AppScope/app.json5,定义了应用级别的全局信息。

2.1 完整示例

json5 复制代码
{
  "app": {
    "bundleName": "com.example.myapp",       // 应用包名(全局唯一标识)
    "vendor": "example",                     // 开发者/供应商名称
    "versionCode": 1,                        // 版本号(整数,用于版本对比)
    "versionName": "1.0.0",                  // 版本名(面向用户)
    "icon": "$media:app_icon",               // 应用图标(引用 resources 下的图片)
    "label": "$string:app_name",             // 应用名称(引用字符串资源,支持多语言)
    "targetAPIVersion": 11,                  // 目标 API 版本
    "compatibleAPIVersion": 9,               // 最低兼容 API 版本
    "debug": false,                          // 是否为调试版本
    "car": {                                 // 车载设备配置(可选)
      "minAPIVersion": 9
    }
  }
}

2.2 关键字段说明

字段 类型 说明
bundleName string 应用唯一标识,采用反向域名格式,安装后不可变更
vendor string 开发者/供应商名称
versionCode number 版本号,整数,用于系统判断版本新旧
versionName string 面向用户的版本字符串
icon resource 应用图标,引用 media 下的图片资源
label resource 应用名称,引用 string 资源以实现多语言
targetAPIVersion number 目标 API 版本,表示应用针对此版本开发
compatibleAPIVersion number 兼容的最低 API 版本,低于此版本的设备无法安装
debug boolean 是否为调试版本,影响签名和调试行为

三、核心配置文件详解 ------ module.json5

module.json5 位于 entry/src/main/module.json5,是模块级别的配置文件,定义了 Ability、权限、窗口行为等。

3.1 完整示例

json5 复制代码
{
  "module": {
    "name": "entry",                        // 模块名
    "type": "entry",                        // 模块类型:entry(主模块)/ feature(动态特性)
    "description": "$string:module_desc",   // 模块描述
    "mainElement": "EntryAbility",          // 入口 Ability 名称
    "deviceTypes": [                        // 支持的设备类型
      "phone",
      "tablet"
    ],
    "deliveryWithInstall": true,            // 是否随应用安装(false 时可按需下载)
    "installationFree": false,              // 是否免安装
    "pages": "$profile:main_pages",         // 页面路由配置文件
    "abilities": [                          // Ability 声明列表
      {
        "name": "EntryAbility",             // Ability 名称
        "srcEntry": "./ets/entryability/EntryAbility.ts",  // 代码入口
        "description": "$string:entry_ability_desc",
        "icon": "$media:ability_icon",
        "label": "$string:entry_ability_label",
        "startWindowIcon": "$media:start_icon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,                   // 是否可被其他应用调用
        "launchType": "singleton",          // 启动模式
        "skills": [                         // Want 匹配规则
          {
            "actions": ["action.system.home"],
            "entities": ["entity.system.home"]
          }
        ]
      }
    ],
    "requestPermissions": [                 // 权限声明
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:internet_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ],
    "window": {                             // 窗口配置
      "designWidth": 720,
      "autoDesignWidth": true
    }
  }
}

3.2 关键字段说明

字段 说明
name 模块名称
type 模块类型:entry(主模块,必有)、feature(动态模块,可延迟下载)
mainElement 入口 Ability 名称,应用启动时首先加载此 Ability
deviceTypes 支持的设备类型数组
pages 页面路由列表,指向 resources/base/profile/ 下的 JSON 文件
abilities Ability 数组,每个 Ability 对应一个相对独立的功能单元
requestPermissions 权限声明数组,敏感权限需声明 reason 说明用途
window 窗口配置(设计宽度等)

3.3 main_pages.json 路由文件

文件路径:entry/src/main/resources/base/profile/main_pages.json

json 复制代码
{
  "src": [
    "pages/Index",
    "pages/Detail",
    "pages/Settings"
  ]
}

该文件列出了模块中的所有页面路径,页面跳转时使用 router.pushUrl 需要目标页面在此注册。

四、Ability 概念与 UIAbility 生命周期

4.1 什么是 Ability

Ability 是 HarmonyOS 中应用的基本功能单元。Stage 模型下主要分为两种:

  • UIAbility:带有 UI 界面的 Ability,是应用与用户交互的主要载体。一个应用可包含多个 UIAbility。
  • ExtensionAbility:无界面的扩展 Ability,提供特定场景的服务(如 FormExtensionAbility、InputMethodExtensionAbility 等)。

本文聚焦 UIAbility。

4.2 UIAbility 生命周期

UIAbility 从创建到销毁经历完整的生命周期,各个阶段都有对应的回调方法。

typescript 复制代码
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';

export default class EntryAbility extends UIAbility {
  onCreate(want, launchParam) {
    console.log('onCreate: Ability 被创建');
    // 全局初始化操作(仅一次):初始化数据库、SDK 等
  }

  onDestroy() {
    console.log('onDestroy: Ability 被销毁');
    // 释放全局资源
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    console.log('onWindowStageCreate: 窗口舞台创建');
    // 加载页面
    windowStage.loadContent('pages/Index', (err, data) => {
      if (err.code) {
        console.error('加载页面失败', JSON.stringify(err));
        return;
      }
      console.log('页面加载成功');
    });
  }

  onWindowStageDestroy() {
    console.log('onWindowStageDestroy: 窗口舞台销毁');
    // 窗口销毁时处理
  }

  onForeground() {
    console.log('onForeground: Ability 切换到前台');
    // 应用从后台回到前台
  }

  onBackground() {
    console.log('onBackground: Ability 切换到后台');
    // 应用切换到后台
  }
}

4.3 生命周期回调触发时机

回调 触发时机 适用场景
onCreate Ability 首次创建时 全局初始化(数据库连接、SDK 初始化)
onWindowStageCreate 窗口创建并加载页面时 设置主页面 loadContent
onForeground Ability 从后台回到前台 恢复任务、刷新数据
onBackground Ability 从前台切到后台 保存状态、暂停任务
onWindowStageDestroy 窗口销毁时 窗口资源释放
onDestroy Ability 被销毁时 释放全局资源、反初始化

4.4 典型生命周期流程

首次启动:

onCreateonWindowStageCreateonForeground

切到后台:

onBackground

从后台返回:

onForeground

退出应用:

onBackgroundonWindowStageDestroyonDestroy

五、启动模式 ------ singleton、multiton、specified

启动模式决定了 UIAbility 在任务栈中的实例化行为。在 module.json5 中通过 launchType 字段配置。

5.1 singleton ------ 单实例模式(默认)

整个系统中只有一个该 Ability 的实例。无论从哪里启动,都会复用已有的实例。

json5 复制代码
{
  "abilities": [{
    "name": "EntryAbility",
    "launchType": "singleton"
  }]
}

行为:

  • 首次启动时创建新实例。
  • 再次启动时复用已有实例,并触发 onNewWant 回调传递新的 Want。
  • 适合主页面、全局单一功能页面。

5.2 multiton ------ 多实例模式

每次启动都创建一个新的 Ability 实例,多个实例独立存在于任务栈中。

json5 复制代码
{
  "abilities": [{
    "name": "DocAbility",
    "launchType": "multiton"
  }]
}

行为:

  • 每次启动都走完整的 onCreateonWindowStageCreateonForeground
  • 不同实例之间相互独立。
  • 适合文档编辑、商品详情等需要多任务并行的页面。

5.3 specified ------ 指定实例模式

通过自定义 Key 来决定是创建新实例还是复用已有实例。需要在 Ability 中重写 onAcceptWant 方法。

json5 复制代码
{
  "abilities": [{
    "name": "ChatAbility",
    "launchType": "specified"
  }]
}

自定义 Key 逻辑:

typescript 复制代码
export default class ChatAbility extends UIAbility {
  onAcceptWant(want: Want): string {
    // 返回的字符串作为该实例的唯一标识
    // 如聊天页中,以联系人 ID 作为 Key,同一联系人的聊天复用同一实例
    return want.parameters?.contactId as string ?? '';
  }
}

行为:

  • 系统根据 onAcceptWant 返回的 Key 查找已有实例。
  • 若存在相同 Key 的实例则复用,触发 onNewWant
  • 若不存在则创建新实例。
  • 适合按特定维度分组的场景(如按联系人、按会话)。

5.4 三种模式对比

模式 实例数 复用行为 适用场景
singleton 全局唯一 复用已有,触发 onNewWant 应用主页、设置页
multiton 每次新建 不复用 文档、商品详情
specified 按 Key 决定 同 Key 复用,不同 Key 新建 聊天、邮件会话

六、Want 详解与跨 Ability 跳转

Want 是 HarmonyOS 中组件间通信的核心对象,用于启动 Ability、传递参数、匹配目标等。

6.1 Want 的结构

typescript 复制代码
interface Want {
  deviceId?: string;        // 目标设备 ID("" 表示本机)
  bundleName?: string;      // 目标应用包名
  abilityName?: string;     // 目标 Ability 名称
  uri?: string;             // 目标 URI(用于隐式匹配)
  type?: string;            // MIME 类型
  action?: string;          // 动作
  entities?: string[];      // 实体类别
  flags?: number;           // 标志位
  parameters?: Record<string, Object>;  // 传递的参数
}

6.2 显式 Want 跳转

明确指定目标应用的 bundleNameabilityName

同一应用内跳转:

typescript 复制代码
import common from '@ohos.app.ability.common';

let context = getContext(this) as common.UIAbilityContext;

let want: Want = {
  deviceId: '',              // 空表示本机
  bundleName: 'com.example.myapp',
  abilityName: 'SecondAbility',
  parameters: {
    from: 'MainPage',
    userId: 123
  }
};

context.startAbility(want).then(() => {
  console.log('跳转成功');
}).catch((err) => {
  console.error('跳转失败', JSON.stringify(err));
});

目标 Ability 接收参数:

typescript 复制代码
export default class SecondAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    let from = want.parameters?.from as string;
    let userId = want.parameters?.userId as number;
    console.log(`来自:${from}, userId:${userId}`);
  }
}

6.3 隐式 Want 跳转

不指定具体 Ability,通过 actionentities 等由系统匹配。

typescript 复制代码
let want: Want = {
  action: 'ohos.want.action.viewData',  // 系统预定义或自定义的 action
  entities: ['entity.system.home'],
  uri: 'https://www.example.com'
};

context.startAbility(want).then(() => {
  // 系统会匹配符合条件的 Ability
}).catch((err) => {
  console.error('无匹配的 Ability', JSON.stringify(err));
});

目标 Ability 需要在 module.json5 中声明对应的 skills

json5 复制代码
{
  "abilities": [{
    "name": "BrowserAbility",
    "skills": [
      {
        "actions": ["ohos.want.action.viewData"],
        "entities": ["entity.system.home"],
        "uris": [
          {
            "scheme": "https",
            "host": "www.example.com"
          }
        ]
      }
    ]
  }]
}

6.4 startAbilityForResult 获取返回结果

启动目标 Ability 后,期望其返回结果时使用 startAbilityForResult

调用方:

typescript 复制代码
let want: Want = {
  bundleName: 'com.example.myapp',
  abilityName: 'SelectAbility'
};

context.startAbilityForResult(want).then((result) => {
  let selected = result.want?.parameters?.selected;
  console.log('返回结果:', selected);
});

目标 Ability 返回结果:

typescript 复制代码
// 在目标 Ability 中
let resultWant: Want = {
  parameters: {
    selected: 'result_data'
  }
};
context.terminateSelfWithResult(resultWant);

七、综合示例 ------ 多 Ability 跳转与模式测试

以下示例创建两个 Ability:MainAbility(主页,singleton)和 DetailAbility(详情页,multiton),实现跳转、参数传递和结果返回。

MainAbility.ets:

typescript 复制代码
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
import common from '@ohos.app.ability.common';

export default class MainAbility extends UIAbility {
  onCreate(want, launchParam) {
    console.log('MainAbility onCreate');
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    windowStage.loadContent('pages/MainPage', (err) => {
      if (err.code) {
        console.error('加载页面失败', JSON.stringify(err));
      }
    });
  }
}

// 在 MainPage 组件中触发跳转
// ...
// let context = getContext(this) as common.UIAbilityContext;
// context.startAbility({
//   deviceId: '',
//   bundleName: 'com.example.myapp',
//   abilityName: 'DetailAbility',
//   parameters: { itemId: 42 }
// });

module.json5 中配置两个 Ability:

json5 复制代码
{
  "abilities": [
    {
      "name": "MainAbility",
      "srcEntry": "./ets/mainability/MainAbility.ts",
      "launchType": "singleton",
      "exported": true
    },
    {
      "name": "DetailAbility",
      "srcEntry": "./ets/detailability/DetailAbility.ts",
      "launchType": "multiton",
      "exported": false
    }
  ]
}

通过此配置,主页始终为单实例,每次打开详情页都创建新实例。

八、常见错误与注意事项

8.1 页面未在 main_pages.json 中注册

使用 router.pushUrl({ url: 'pages/NewPage' }) 跳转时,若 NewPage 未在 main_pages.jsonsrc 数组中声明,将导致白屏或报错。

8.2 Ability 未在 module.json5 中声明

自定义的 Ability 必须在 abilities 数组中声明,否则无法通过 startAbility 调用。

8.3 启动模式配置不匹配预期行为

  • 期望每次打开都是新页面却用了 singleton,导致复用旧实例、丢失新参数。
  • 期望复用同一页面却用了 multiton,导致任务栈堆积多个相同页面。
  • 使用 specified 时忘记重写 onAcceptWant,所有启动行为回退为多实例。

8.4 隐式 Want 匹配失败

  • 未在目标 Ability 的 skills 中声明对应的 actionsuris
  • schemehost 等不匹配。
  • 目标 Ability 的 exportedfalse(隐式跳转要求 exported: true)。

8.5 rawfile 路径错误

rawfile 中的文件路径不包含 rawfile 前缀。如文件 rawfile/config.json,读取时使用 resourceManager.getRawFileContent('config.json')

九、小结

概念 关键点
工程目录 AppScope 全局配置,entry 主模块,resources/base 资源,rawfile 原始文件
app.json5 应用包名、版本、图标、名称等全局配置
module.json5 模块配置:Ability 声明、权限、窗口、页面路由
main_pages.json 页面路由注册文件
UIAbility 生命周期 onCreateonWindowStageCreateonForeground / onBackgroundonDestroy
singleton 单实例,复用已有
multiton 多实例,每次新建
specified 按 Key 决定是否复用,需重写 onAcceptWant
Want 跳转对象:显式(指定 bundleName + abilityName)、隐式(action 匹配)、参数传递

掌握工程结构、配置文件与 Stage 模型核心知识后,你将能够从应用全局视角进行设计,合理组织代码模块,实现多 Ability 间的灵活跳转与数据通信,为后续开发复杂项目铺平道路。


觉得文章有帮助?别忘了:

👍 点赞 👍 -- 给我一点鼓励

⭐ 收藏 ⭐ -- 方便以后查看

🔔 关注 🔔 -- 获取更新通知


标签: #HarmonyOS #Stage模型 #UIAbility #Want #启动模式 #工程目录 #配置文件 #学习笔记 #鸿蒙开发

相关推荐
祭曦念1 小时前
【共创季稿事节】鸿蒙原生 ArkTS 布局:NavRouter + NavDestination 导航布局实战
ubuntu·华为·harmonyos
木咺吟11 小时前
鸿蒙原生应用实战(一):从零搭建快递追踪App——项目初始化与工程架构详解
华为·harmonyos
坚果派·白晓明13 小时前
【鸿蒙PC】SDL3 移植:AtomCode Skills 4 步速通多媒体库适配
c++·华为·ai编程·harmonyos·atomcode·c/c++三方库
风满城3314 小时前
鸿蒙原生应用实战(三):设置与统计页面开发 — 数据驱动的功能模块
harmonyos
xcLeigh15 小时前
鸿蒙平台 KeePass 密码管理器适配实战:从 Windows 到 鸿蒙PC 的 Electron 迁移指南
windows·electron·web·harmonyos·加密算法·keepass
金启攻15 小时前
鸿蒙原生应用开发实战(一):从零搭建“钓点日记“——项目初始化与环境配置全指南
harmonyos
风华圆舞15 小时前
鸿蒙语音识别为什么要区分 startListening 和 stopListening
华为·语音识别·harmonyos
YM52e15 小时前
鸿蒙PC ArkTS 声明合并问题深度解析与最佳实践
学习·华为·harmonyos·鸿蒙·鸿蒙系统
互联网散修15 小时前
鸿蒙实战:网络状态监听与诊断工具
网络·华为·harmonyos·网络状态监听