【鸿蒙原生开发会议随记 Pro】工欲善其事 HarmonyOS 6 开发环境搭建与工程结构设计
如何在一个全新的原生系统中,构建一个高内聚、低耦合、可扩展的工程结构。
文章目录
对于有你来说,搭建环境和创建项目看似是肌肉记忆,但在 HarmonyOS NEXT 这个全新的生态里,很多规则已经改变了。如果你依然沿用以前写 Android 或者旧版鸿蒙 FA 模型的思维,很可能会在项目中期陷入重构的泥潭。
今天,我不只是教你点几个按钮,我们要深度探讨一个商业级应用的骨架该如何搭建。这不仅关乎代码写得顺不顺手,更关乎你的应用能不能承载未来复杂的业务增长。
我们要解决的核心问题是:如何在一个全新的原生系统中,构建一个高内聚、低耦合、可扩展的工程结构。

一、 为什么我们坚定选择 Stage 模型
当你打开 DevEco Studio,点击 Create Project 时,系统或许会给你几个模版选择。这时候,请毫不犹豫地选择 Application 并确保 Model 选的是 Stage。
可能你会问,为什么不是 FA 模型?你看,Stage 模型是 HarmonyOS NEXT 应用开发的主流模式,它在设计之初就考虑到了复杂的应用场景。相比于 FA 模型,Stage 模型引入了 AbilityStage、WindowStage 等概念,将应用的能力生命周期和界面显示生命周期做了分离。这意味着什么?这意味着当你的应用在多设备流转,或者在分屏、悬浮窗这些复杂场景下运行时,Stage 模型能提供更精细的控制力。
对于我们的会议随记 Pro 来说,未来我们可能会做平板适配,甚至手表联动,Stage 模型提供的UIAbility组件分离机制,能让我们更从容地处理这些多窗口逻辑。我们在创建项目时,包名(Bundle Name)建议使用反向域名格式,比如 com.yourname.meetingpro,这个 ID 将是你在鸿蒙生态中的唯一身份证,一旦上架就不能修改,所以请慎重敲下每一个字符。
项目创建完成后,不要急着写代码。我们先来做一件甚至比写代码更重要的事情:规划目录结构。
二、商业级工程目录的划分哲学
很多新手开发者的习惯是,把所有页面都扔在 pages 目录下,把所有工具类都扔在 utils 里。这种做法在 Demo 阶段没问题,但一旦业务复杂起来,比如我们的应用涉及录音、数据库、项目管理、联系人统计等多个模块,这种扁平的结构就会变成一场灾难。你会在找一个文件上浪费五分钟,修改一个功能导致另一个功能崩溃。
我们需要引入**模块化(Modularization)**的思维。
在鸿蒙开发中,官方提供了 HAP(Harmony Ability Package)和 HSP(Harmony Shared Package)的概念。HAP 是应用的主入口,可以独立安装;HSP 是动态共享包,用于多个 HAP 之间共享代码。考虑到我们是独立开发,初期为了降低构建复杂度和部署成本,我们暂时不需要搞多个 HAP,我们采用单 HAP 内部模块化的策略。
我们把代码逻辑分为三层:功能层(Features)、数据层(Data) 和 公共基础层(Commons)。
请打开你的工程,在 entry/src/main/ets 目录下,删掉默认的结构,建立如下的目录树。你看,我们这样做,能让代码的归属权清晰到极致。
entry/src/main/ets/
├── commons/ # 【公共基础层】底座,与业务无关
│ ├── base/ # 基类封装
│ ├── constants/ # 全局常量
│ ├── utils/ # 通用工具链
│ └── components/ # 全局通用UI组件
├── data/ # 【数据层】应用的心脏
│ ├── db/ # 数据库管理(RdbStore)
│ ├── models/ # 业务实体定义
│ └── preferences/ # 轻量级存储
├── features/ # 【功能层】按业务垂直切分
│ ├── record/ # 录音模块(核心)
│ ├── project/ # 项目模块
│ ├── contact/ # 人脉模块
│ ├── dashboard/ # 统计看板模块
│ └── settings/ # 设置模块
├── pages/ # 【路由层】页面的物理入口
│ ├── Index.ets # App 壳子
│ └── SplashPage.ets # 启动页
└── entryability/ # Ability 生命周期
└── EntryAbility.ts
为什么要这么分?
Commons 层 是我们的军火库。这里存放的是就算换个 App 也能直接用的代码。比如时间格式化工具,无论是做会议 App 还是跑步 App,逻辑都是一样的;再比如全局的 Loading 组件、统一的 Log 打印工具。把它们抽离出来,能保证业务逻辑的纯粹。
Data 层 是数据的唯一真理来源。我们规定:UI 层绝对不能直接操作数据库。所有的增删改查,必须通过 data 层暴露的方法来调用。这样设计的好处是,如果未来有一天,你想把本地数据库换成云端数据库,你只需要修改 data 目录下的代码,而上层的 UI 界面完全不需要感知。这就是架构设计中常说的关注点分离。
Features 层 是我们业务的主战场。我们将 App 拆解为录音、项目、人脉等独立的业务域。在 features/record 目录下,我们可以进一步划分 views(视图)、viewmodel(逻辑)、model(该模块独有的数据模型)。这种高内聚的结构,让你在开发录音功能时,视线可以完全聚焦在 record 目录下,不会被其他无关代码干扰。
三、 公共基础层的核心封装
架构搭好了,我们先来填补 Commons 层 的内容,这是整个 App 的地基。
在 commons/base 目录下,我强烈建议你封装一个 BaseViewModel。在鸿蒙的 ArkUI 范式中,虽然是声明式 UI,但我们依然需要一个地方来处理业务逻辑,将状态(State)和视图(View)解耦。
我们定义一个泛型基类,用来处理所有页面通用的逻辑,比如加载状态的控制、错误信息的提示。
// commons/base/BaseViewModel.ts
import { Observable } from '../utils/Observable'; // 假设你有个简易的观察者工具
export class BaseViewModel {
/**
* 页面加载状态
* true: 正在加载
* false: 加载完成
*/
isLoading: boolean = false;
/**
* 错误信息
* 空字符串表示无错误
*/
errorMessage: string = '';
/**
* 统一处理异步任务,自动管理 Loading 状态
* @param task 异步任务函数
*/
async launch<T>(task: () => Promise<T>): Promise<T | null> {
try {
this.isLoading = true;
const result = await task();
return result;
} catch (error) {
console.error(`Task failed: ${JSON.stringify(error)}`);
this.errorMessage = '操作失败,请重试';
return null;
} finally {
this.isLoading = false;
}
}
}
你看,有了这个基类,以后我们在写具体的业务逻辑(比如 RecordViewModel)时,只需要继承它。当我们要从数据库读取录音列表时,直接调用 this.launch(async () => { ... }),Loading 状态的切换就会自动完成,不用每次都手写 isLoading = true 和 false。这就是封装的价值,它帮我们消灭了重复代码。
此外,在 commons/constants 下,我们要建立一个 AppConfig.ts 或 Breakpoints.ts。鸿蒙提倡一次开发,多端部署,所以我们最好提前定义好断点(Breakpoints)常量,比如 sm、md、lg,对应手机、折叠屏和平板。虽然初期我们只适配手机,但预留这个配置,体现的是你作为资深开发的远见。
四、 module.json5 深度解读与配置
工程结构搞定后,我们需要把目光移向那个不起眼但至关重要的文件module.json5。
在 Stage 模型中,它是整个模块的控制中枢。很多新手遇到的坑,比如“为什么录音没声音”、“为什么切到后台就挂了”、“为什么图标改不过来”,根源都在这个文件里。
请打开 entry/src/main/module.json5,我们要重点配置三个部分:权限(Permissions)、能力(Abilities) 和 后台模式(Background Modes)。
首先是权限。会议随记 Pro 是一个录音应用,麦克风权限是我们的生命线。不仅如此,为了实现“会议写入系统日历”的功能,我们还需要日历的读写权限。在 requestPermissions 数组中,我们必须显式声明这些需求。这里有一个常识性的细节:从 API 9 开始,系统对隐私权限管控非常严格,你不仅要声明 name,还必须提供 reason(申请理由)和 usedScene(使用场景)。
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:permission_reason_microphone",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.READ_CALENDAR",
"reason": "$string:permission_reason_calendar",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.WRITE_CALENDAR",
"reason": "$string:permission_reason_calendar",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
注意那个 $string:permission_reason_microphone,千万不要在这里直接写死字符串。你需要在 resources/base/element/string.json 里定义它,支持多语言。如果你的理由写得含糊不清,比如只写“需要权限”,在应用上架审核时是会被直接驳回的。我们要写得诚恳且具体,比如:“需要使用麦克风录制会议音频以便生成笔记”。
接下来是后台模式。这是专业录音应用和玩具应用的分水岭。如果不配置这个,用户在录音时稍微切出去回个微信,或者锁屏放进口袋,系统为了省电很可能会杀掉你的录音进程。为了保证录音的连续性,我们需要申请长时任务。
在 module 对象下,添加 backgroundModes 字段:
代码段
"module": {
// ... 其他配置
"backgroundModes": [
"audioRecording",
"audioPlayback"
]
}
这行配置向系统宣告:我的应用涉及后台录音和后台播放,请给我颁发“免死金牌”。当然,有了这个声明还不够,我们后续在代码中还需要调用后台任务管理的 API 来实时申请,但这行配置是入场券。
最后是 EntryAbility 的配置。在 abilities 数组中,你会看到一个名为 EntryAbility 的对象。它是我们应用的主入口。特别要注意 skills 字段的配置,它决定了你的应用能不能在桌面上被点击图标启动。
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
这段配置的意思是,这个 Ability 具备处理“系统桌面”发出的“回家(Home)”动作的能力,换句话说,它就是那个点击图标后第一个跳出来的界面。
五、 资源配置颜色与字符串的规范化
在写 UI 代码前,我们最后要约束的是资源文件。
我见过太多只有 3 年经验的开发者,喜欢在代码里硬编码颜色值,比如 #0A59F7。这在项目初期看似快,到了后期要适配深色模式时,就是一场灾难。
我们要利用鸿蒙强大的资源限定词系统。请在 resources/base/element 下确认 color.json 的存在。我们要定义一套语义化的颜色系统。
你看,我们这样做:不要定义 blue_color,而要定义 brand_color(品牌色);不要定义 black_text,而要定义 text_primary(一级文本色)。
{
"color": [
{
"name": "brand_color",
"value": "#0A59F7"
},
{
"name": "text_primary",
"value": "#1A1A1A"
},
{
"name": "bg_page",
"value": "#F1F3F5"
}
]
}
然后,在 resources/dark/element/color.json(如果没有 dark 目录就新建一个)中,重新定义这些名字,但赋予深色的值。
{
"color": [
{
"name": "brand_color",
"value": "#5C9DFF"
},
{
"name": "text_primary",
"value": "#E6E6E6"
},
{
"name": "bg_page",
"value": "#000000"
}
]
}
这样,我们在写代码时只引用 $r('app.color.text_primary')。当用户把手机系统切换到深色模式时,鸿蒙系统会自动帮我们切换颜色值,整个 App 瞬间适配,一行代码都不用改。这就是规范化工程结构的威力。
六、 总结
到这里,我们已经完成了一次完美的起跑。
我们没有急着画界面,而是先修好了路。我们确定了 Stage 模型 的技术路线,搭建了包含 Features、Data、Commons 三层架构的工程目录,封装了通用的 BaseViewModel,并在 module.json5 中拿到了录音和后台运行的关键权限。
现在的工程,虽然运行起来只有一个空白的页面,但它的骨架是强壮的,逻辑是清晰的。它已经做好了承载复杂业务的准备。
更多推荐





所有评论(0)