前言在 HarmonyOS 应用开发中架构分层往往比写好单个组件更关键。一个分层清晰、职责明确的项目能在后续迭代中保持稳定性和可维护性。本文将以开源鸿蒙笔友通信应用xiexin为蓝本详细剖析其“入口-页面-模型-数据“四层解耦设计讲解每个层级的职责边界、依赖关系与最佳实践。xiexin 是一款基于 HarmonyOS ArkTS/ArkUI 的社交通信类应用围绕“笔友关系与书信往来“的核心场景提供完整体验。整个项目代码量约 3500 行麻雀虽小却五脏俱全是学习 HarmonyOS Stage 模型架构设计的绝佳案例。提示本文假设你已经了解 HarmonyOS Stage 模型的基本概念。如果还不熟悉建议先阅读HarmonyOS Stage 模型概述。一、项目整体目录结构xiexin 项目采用标准 HarmonyOS 工程组织方式根目录下的关键文件和目录如下xiexin/ ├── AppScope/ # 应用级全局配置 │ ├── app.json5 # 应用清单bundleName/versionCode/icon/label │ └── resources/ # 应用级资源图标、字符串 ├── entry/ # 主模块包含所有业务代码 │ ├── src/ │ │ └── main/ │ │ ├── ets/ # ArkTS 源码核心业务 │ │ ├── resources/ # 模块级资源字符串、颜色、媒体、路由表 │ │ └── module.json5 # 模块清单abilities、permissions │ ├── build-profile.json5 # 模块构建配置 │ └── oh-package.json5 # 模块包描述 ├── build-profile.json5 # 工程级构建配置签名、products、modules ├── oh-package.json5 # 工程级包描述devDependencies └── hvigorfile.ts # hvigor 构建脚本这种组织方式遵循 HarmonyOS 官方推荐的工程结构AppScope负责应用全局属性entry模块承载具体业务而build-profile.json5系列文件控制构建行为。1.1 entry/src/main/ets 的内部分层进入主模块的 ArkTS 源码目录可以看到一个清晰的内部分层entry/src/main/ets/ ├── common/ # 常量与公共配置层 │ └── Constants.ets # AppColors 设计令牌、LetterStatus/Frequency 等枚举、日期工具 ├── components/ # 表现层复用组件 │ └── CommonComponents.ets # Avatar/LetterCard/PenPalItem/EmptyState 等 ├── database/ # 数据访问层 │ └── DataStore.ets # AppStorage 状态管理、CRUD 封装、mock 数据 ├── entryability/ # 入口与能力层 │ └── EntryAbility.ets # UIAbility 子类、生命周期回调 ├── model/ # 领域模型层 │ └── Models.ets # Letter/PenPal/UserProfile/WriteStats 等领域实体 └── pages/ # 页面层 ├── Index.ets # 首页 Tab 容器 ├── SplashPage.ets # 启动引导页 ├── AddPenPalPage.ets # 新增笔友表单 ├── PenPalDetailPage.ets # 笔友详情 ├── ComposePage.ets # 写信编辑器 ├── ReadLetterPage.ets # 信件列表阅读 ├── EditProfilePage.ets # 个人资料编辑 └── StatsPage.ets # 数据统计概览这种分层的核心思想是职责单一、依赖单向。下面我们逐一拆解每一层的具体职责。二、四层架构设计详解上图展示了 xiexin 的四层架构依赖方向严格自上而下入口层 → 页面层 → 模型层 → 数据层。表现层组件则独立存在供页面层调用。2.1 入口层EntryAbility入口层是应用与系统交互的第一道关卡承担三个核心职责声明应用入口通过module.json5的mainElement: EntryAbility注册为应用启动入口处理生命周期响应系统的onCreate/onForeground/onBackground等回调加载首屏内容通过windowStage.loadContent()加载初始页面xiexin 的 EntryAbility 实现非常简洁// entry/src/main/ets/entryability/EntryAbility.ets import { AbilityConstant, UIAbility, Want } from kit.AbilityKit; import { hilog } from kit.PerformanceAnalysisKit; import { window } from kit.ArkUI; const TAG: string EntryAbility; const DOMAIN: number 0xFF00; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { hilog.info(DOMAIN, TAG, %{public}s, Ability onCreate); } onWindowStageCreate(windowStage: window.WindowStage): void { hilog.info(DOMAIN, TAG, %{public}s, Ability onWindowStageCreate); windowStage.loadContent(pages/Index, (err) { if (err.code) { hilog.error(DOMAIN, TAG, Failed to load content. Cause: %{public}s, JSON.stringify(err) ?? ); return; } hilog.info(DOMAIN, TAG, Succeeded in loading the content.); }); } onForeground(): void { hilog.info(DOMAIN, TAG, %{public}s, Ability onForeground); } onBackground(): void { hilog.info(DOMAIN, TAG, %{public}s, Ability onBackground); } }可以看到EntryAbility 只做三件事日志记录、首屏加载、生命周期回调占位。所有业务逻辑都被推到下游层级保持了入口层的“轻薄“特性。提示hilog的DOMAIN参数必须是0x0000到0xFFFF范围内的十六进制数用于标识日志来源。%{public}s表示明文输出避免敏感信息泄露问题。2.2 页面层pages页面层是用户交互的直接载体每个.ets文件对应一个Entry Component页面组件。xiexin 共有 8 个页面按职责可分为四类页面分类页面文件职责描述容器页Index.ets首页 Tab 容器承载笔友列表与功能入口引导页SplashPage.ets启动引导三屏切换表单页AddPenPalPage.ets/EditProfilePage.ets笔友新增、个人资料编辑详情页PenPalDetailPage.ets/ComposePage.ets/ReadLetterPage.ets/StatsPage.ets笔友详情、写信编辑器、信件阅读、数据统计页面层的设计原则是单一职责、低耦合。每个页面只负责自己领域内的交互逻辑跨页面的数据共享通过 AppStorage 完成。以 SplashPage 为例// entry/src/main/ets/pages/SplashPage.ets Entry Component struct SplashPage { State currentPage: number 0; State fadeOpacity: number 0; private guidePages: GuidePage[] [ { title: 用心书写, subtitle: 每一封信都值得被认真对待, desc: 在这里写信是一种仪式。放慢节奏用文字传递温度与思念。, emoji: ✉️ }, { title: 慢慢等待, subtitle: 等待回信是最美的期待, desc: 两人只能写信联系不能即时回复。每封信都承载着对方的心意。, emoji: ⏳ }, { title: 深度连接, subtitle: 少而精深而暖, desc: 设置联系频率每天一次或每周一次让每封信都弥足珍贵。, emoji: } ]; aboutToAppear(): void { setTimeout(() { this.fadeOpacity 1; }, 100); } build() { Stack() { Column().width(100%).height(100%).backgroundColor(AppColors.PRIMARY_BG) // ... 引导卡片、指示器、按钮 ... } } }SplashPage 的所有状态都是页面级State不依赖 AppStorage。这种设计让引导页可以独立测试也避免了冷启动时与其他页面争抢 AppStorage 资源。2.3 模型层model模型层定义应用的核心领域实体。xiexin 的Models.ets集中定义了用户、笔友、信件、统计、时间线等 6 个领域类// entry/src/main/ets/model/Models.ets export class UserProfile { id: number 0; name: string ; signature: string ; signatureSuffix: string ; totalLetters: number 0; totalWords: number 0; totalPenPals: number 0; streakDays: number 0; longestLetter: number 0; favoriteWriteTime: string ; mostUsedHonorific: string ; longestWaitDays: number 0; totalWaitDays: number 0; } export class PenPal { id: number 0; name: string ; avatar: string ; signature: string ; relationStage: RelationStage RelationStage.NEW; frequency: Frequency Frequency.WEEKLY; totalLetters: number 0; daysSinceMet: number 0; lastLetterAt: number 0; lastLetterPreview: string ; lastLetterStatus: LetterStatus LetterStatus.CAN_WRITE; isLastLetterSender: boolean false; metAt: number 0; } export class Letter { id: number 0; penPalId: number 0; penPalName: string ; honorific: string ; recipientName: string ; greeting: string ; body: string ; closing: string ; signature: string ; signatureSuffix: string ; isSender: boolean false; status: LetterStatus LetterStatus.WAIT_REPLY; paperStyle: PaperStyle PaperStyle.PLAIN; wordCount: number 0; isRead: boolean false; createdAt: number 0; }模型层使用class而非interface定义实体这是 ArkUI 响应式系统的要求只有类实例才能被Observed装饰器追踪属性变化。每个字段都显式声明了默认值避免undefined引发渲染异常。提示ArkUI 的状态管理依赖代理对象Proxy对interface类型无法生效。如果你需要在Observed类中嵌套其他类记得给嵌套类也加上Observed。2.4 数据层database数据层是整个架构的“地基“承担状态持久化、数据读写、跨页面共享三大职责。xiexin 的DataStore.ets通过静态方法实现了一个轻量级的“门面模式“// entry/src/main/ets/database/DataStore.ets export class DataStore { static initializeData(): void { const penPals: PenPal[] DataStore.createMockPenPals(); AppStorage.setOrCreatePenPal[](penPals, penPals); const letters: Letter[] DataStore.createMockLetters(); AppStorage.setOrCreateLetter[](letters, letters); const profile: UserProfile DataStore.createMockProfile(); AppStorage.setOrCreateUserProfile(userProfile, profile); const stats: WriteStats DataStore.createMockStats(); AppStorage.setOrCreateWriteStats(writeStats, stats); AppStorage.setOrCreateInviteRecord[](invites, []); AppStorage.setOrCreateboolean(hasSeenSplash, false); AppStorage.setOrCreatenumber(currentTab, 0); } // 添加新信件 static addLetter(letter: Letter): void { const letters: Letter[] AppStorage.getLetter[](letters) ?? []; letter.id letters.length 0 ? Math.max(...letters.map((l: Letter) l.id)) 1 : 1; letters.unshift(letter); AppStorage.setLetter[](letters, letters); // 同步更新笔友的最近一封信字段 const penPals: PenPal[] AppStorage.getPenPal[](penPals) ?? []; const idx penPals.findIndex((p: PenPal) p.id letter.penPalId); if (idx 0) { penPals[idx].totalLetters 1; penPals[idx].lastLetterAt letter.createdAt; penPals[idx].lastLetterPreview letter.body.substring(0, 30); penPals[idx].lastLetterStatus LetterStatus.WAITING_OTHER; penPals[idx].isLastLetterSender true; AppStorage.setPenPal[](penPals, penPals); } } }数据层的关键设计决策包括统一入口所有 AppStorage 读写都通过 DataStore避免散落各处难维护业务联动addLetter同时更新 letters 和 penPals保证数据一致性ID 自增策略用Math.max(...ids) 1生成新 ID简单有效三、表现层与组件复用表现层位于components/CommonComponents.ets集中放置可复用的 UI 控件。xiexin 选择“单文件多组件“的组织方式把 Avatar、LetterCard、PenPalItem、EmptyState 等组件都放在一个 146 行的文件里。这种设计的取舍如下优势组件之间的耦合可见调试时只读一个文件小型项目避免了文件爆炸劣势当组件数量超过 10 个时单文件可读性下降多人协作易产生合并冲突如果你打算扩展 xiexin建议按“组件类型“拆分components/ ├── avatars/ │ ├── Avatar.ets │ └── GroupAvatar.ets ├── cards/ │ ├── LetterCard.ets │ └── StatsCard.ets ├── lists/ │ ├── PenPalItem.ets │ └── LetterItem.ets └── states/ └── EmptyState.ets四、配置文件体系除了 ArkTS 源码xiexin 的架构还包含一系列 JSON5 配置文件它们各司其职// AppScope/app.json5应用级全局配置 { app: { bundleName: com.xiexin.letter, vendor: xiexin, versionCode: 1000000, versionName: 1.0.0, icon: $media:app_icon, label: $string:app_name } }// entry/src/main/module.json5模块清单 { module: { name: entry, type: entry, mainElement: EntryAbility, deviceTypes: [phone, tablet, 2in1], pages: $profile:main_pages, abilities: [ { name: EntryAbility, srcEntry: ./ets/entryability/EntryAbility.ets, startWindowIcon: $media:startIcon, startWindowBackground: $color:start_window_background, exported: true, skills: [ { entities: [entity.system.home], actions: [action.system.home] } ] } ], requestPermissions: [ { name: ohos.permission.INTERNET } ] } }// entry/src/main/resources/base/profile/main_pages.json路由表 { src: [ pages/Index, pages/SplashPage, pages/ComposePage, pages/ReadLetterPage, pages/PenPalDetailPage, pages/AddPenPalPage, pages/StatsPage, pages/EditProfilePage ] }这三个配置文件之间存在严格的契约关系module.json5的mainElement必须指向abilities数组中某个 ability 的namemodule.json5的pages字段指向路由表main_pages.jsonmain_pages.json的src数组中每个路径必须对应一个真实的.ets页面文件EntryAbility通过loadContent(pages/Index)加载的页面必须存在于路由表任何一个环节断裂应用都无法正常启动。五、依赖关系与数据流向理解了各层职责后我们来看看它们之间的依赖关系与数据流向。xiexin 的依赖方向是单向自上而下的graph TD EA[EntryAbility 入口层] --|loadContent| PAGES[Pages 页面层] PAGES --|import| MODELS[Models 模型层] PAGES --|call static methods| DS[DataStore 数据层] PAGES --|use| CC[CommonComponents 表现层] DS --|CRUD| MODELS CC --|render with| MODELS DS --|read/write| AS[AppStorage 全局状态]数据流向遵循“页面 → DataStore → AppStorage → 渲染“的循环页面写入用户在ComposePage写完信点击发送调用DataStore.addLetter(letter)数据层更新addLetter内部调用AppStorage.set(letters, newLetters)状态广播AppStorage 自动通知所有StorageLink(letters)的订阅者页面刷新Index页面订阅了 letters自动重新渲染笔友列表这种数据流的核心优势是单向、可追溯、易调试。所有状态变更都经过 DataStore 这个“瓶颈“我们可以在 DataStore 的每个方法里加埋点就能完整还原用户操作链路。六、设计令牌与枚举体系xiexin 的common/Constants.ets文件是整个项目的“约定中心“包含三部分内容6.1 AppColors 设计令牌export class AppColors { static readonly PRIMARY_BG: string #FAF6F0; static readonly SECONDARY_BG: string #F0EBE3; static readonly PAPER_BG: string #FFFDF7; static readonly PRIMARY: string #8B6914; static readonly SECONDARY: string #C4956A; static readonly TEXT_PRIMARY: string #2D2A26; static readonly TEXT_SECONDARY: string #7A746B; static readonly ACCENT: string #B8860B; static readonly DIVIDER: string #E8E2D8; static readonly SUCCESS: string #4CAF50; // ... }设计令牌Design Tokens是设计系统的核心统一管理颜色、字号、间距等视觉常量。xiexin 用static readonly字段实现访问方式为AppColors.PRIMARY。提示更规范的做法是把颜色定义到resources/base/element/color.json然后在代码里用$r(app.color.primary)引用。这样能自动适配深浅色模式。本文的“四层解耦“暂不展开这一话题后续文章会专门讲解。6.2 业务枚举export enum LetterStatus { WAIT_REPLY 0, // 等待你回信 WAITING_OTHER 1, // 等待对方回信 REPLIED 2, // 已回信 CAN_WRITE 3 // 可以写信 } export enum Frequency { DAILY 1, WEEKLY 7, BIWEEKLY 14, MONTHLY_TWICE 15, MONTHLY 30 } export enum RelationStage { NEW 0, // 初识 FAMILIAR 1, // 熟悉 INTIMATE 2 // 亲密 } export enum PaperStyle { PLAIN 0, // 素白 LINED 1, // 信笺线 INK_BORDER 2, // 水墨边 PETAL 3 // 花瓣边 }这些枚举定义了笔友通信场景的核心状态空间。把枚举集中放在 Constants.ets 中避免了散落各处的“魔法数字“也让 UI 层和数据层的约定更加显式。6.3 日期工具函数export function daysBetween(d1: Date, d2: Date): number { const diff Math.abs(d2.getTime() - d1.getTime()); return Math.floor(diff / (1000 * 60 * 60 * 24)); } export function formatTimeDiff(date: Date): string { const now new Date(); const days daysBetween(date, now); if (days 0) return 今天; else if (days 1) return 昨天; else if (days 7) return ${days}天前; else if (days 30) { const weeks Math.floor(days / 7); return ${weeks}周前; } else { return formatShortDate(date); } }这些工具函数被多个页面共享调用放在 Constants.ets 里能避免循环依赖问题。七、构建与依赖管理xiexin 的构建配置分为工程级和模块级两层// 工程级 build-profile.json5 { app: { signingConfigs: [/* 签名配置 */], products: [ { name: default, signingConfig: default, targetSdkVersion: 6.0.2(22), compatibleSdkVersion: 6.0.2(22), runtimeOS: HarmonyOS } ], buildModeSet: [ { name: debug }, { name: release } ] }, modules: [ { name: entry, srcPath: ./entry, targets: [/* ... */] } ] }// 工程级 oh-package.json5 { modelVersion: 6.0.2, description: Please describe the basic information., dependencies: {}, devDependencies: { ohos/hypium: 1.0.25, ohos/hamock: 1.0.0 } }可以看到xiexin 在工程级只引入了两个开发依赖ohos/hypiumHarmonyOS 官方单元测试框架ohos/hamock组件 Mock 工具配合 Hypium 测试 UI 组件这反映了 xiexin 项目对测试可测性的重视。整个 DataStore 都设计为静态方法正是为了方便在测试用例里直接调用而无需实例化。八、四层架构的边界与例外虽然“四层解耦“听起来很美但在实际项目中总会遇到边界模糊的灰色地带。xiexin 也不例外下面列出几个值得注意的取舍8.1 DataStore 是否承担了过多职责目前的DataStore.ets同时负责AppStorage 读写Mock 数据生成createMockPenPals等业务联动逻辑addLetter同步更新 penPals聚合查询getTimelineForPenPal如果项目继续扩展建议把 Mock 数据生成独立到mock/MockDataFactory.ets把聚合查询独立到query/LetterQueryService.ets让 DataStore 回归纯粹的 CRUD 职责。8.2 Constants.ets 文件的膨胀风险Constants.ets已经包含 201 行内容涵盖设计令牌、枚举、字典、工具函数。随着业务增长这个文件会进一步膨胀。建议按内容类型拆分common/ ├── AppColors.ets # 设计令牌 ├── Enums.ets # 业务枚举 ├── Dicts.ets # 称谓/问候/署名字典 ├── DateUtils.ets # 日期工具 └── Constants.ets # 路由键、其他常量8.3 组件层与页面层的耦合CommonComponents.ets中的PenPalItem组件直接依赖了PenPal模型类。这种依赖是合理的但如果未来PenPalItem需要在不同场景下展示不同字段应该考虑用Prop解耦。九、与官方推荐的工程结构对比HarmonyOS 官方文档应用程序包结构中给出了推荐的工程结构。xiexin 的实现与官方推荐基本一致但有几处细微差异维度官方推荐xiexin 实现评价AppScope 配置app.json5完全一致✓模块入口module.json5 abilities完全一致✓源码组织ets 目录分层四层分层✓ 创新点路由表resources/base/profile完全一致✓资源引用r/rawfile完全一致✓测试框架ohos/hypium完全一致✓总体来看xiexin 是一个非常贴近 HarmonyOS 官方推荐实践的项目对新手学习 HarmonyOS 工程结构具有很高的参考价值。十、从 xiexin 看鸿蒙 Stage 模型的精髓xiexin 项目虽小却完美体现了 HarmonyOS Stage 模型的设计精髓配置驱动从 app.json5 到 module.json5 到 main_pages.json全部用配置文件描述应用结构避免硬编码Ability 解耦UIAbility 只负责生命周期与窗口管理UI 内容由独立的页面组件承载资源化设计颜色、字符串、图片全部走 resources 目录通过$r引用为多语言、深浅色模式预留扩展空间状态集中管理AppStorage 作为全局状态枢纽配合StorageLink实现跨页面数据同步理解了这些设计哲学你就掌握了 HarmonyOS 应用开发的“骨架“。后续的文章将深入每一层的具体实现从入口层到数据层逐一剖析 xiexin 是如何一步步实现“笔友通信“这个核心场景的。总结本文以开源鸿蒙笔友通信应用 xiexin 为例详细剖析了其“入口-页面-模型-数据“四层解耦设计。我们看到入口层EntryAbility保持轻薄只做生命周期回调与首屏加载页面层pages按职责拆分每个页面单一职责模型层model用 class 定义领域实体配合 Observed 实现响应式数据层DataStore作为门面统一管理 AppStorage保证数据一致性四层之间通过单向依赖、配置契约、状态广播三大机制协同既保证了代码的可维护性又为后续扩展预留了充足空间。下一篇文章我们将深入入口层剖析 EntryAbility 的六个生命周期回调的时序与职责。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.netHarmonyOS Stage 模型概述https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/stage-model-development-overviewHarmonyOS 应用程序包结构https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/application-package-structure-stageHarmonyOS 应用配置文件概述https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/application-configuration-file-overview-stageHarmonyOS ArkUI 状态管理概述https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management-overviewHarmonyOS AppStorage 全局状态https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-appstorageHarmonyOS 资源分类与访问https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/resource-categories-and-accessHarmonyOS hilog 日志使用https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hilog