
HarmonyOS 原生应用实战从项目结构拆解“律愈”五音疗愈 App说明本文只讨论技术实现和产品结构五音疗愈内容用于放松辅助场景不替代医学诊断。1. 项目做了什么“律愈”是一个 HarmonyOS 原生五音疗愈应用核心体验可以概括为四条主线首页按时辰推荐疗愈 Session并提供快速入口。疗愈页播放当前五音对应的本地合成音频同时展示 Canvas 粒子动画和进度。辨证页通过 8 个问题计算五音倾向推荐对应疗愈内容。桌面卡片在 2x4 / 4x4 卡片上完成播放、暂停、上一首、下一首等控制。这个项目不是单纯堆 UI而是把“数据模型、推荐算法、音频播放、后台保活、卡片通信、响应式布局”都串起来了很适合作为 HarmonyOS 原生项目案例。2. 工程目录结构项目采用 Stage 模型核心代码集中在entry/src/main/etsyulv ├─ AppScope │ └─ app.json5 ├─ entry │ ├─ src/main/module.json5 │ ├─ src/main/ets │ │ ├─ common │ │ ├─ data │ │ │ ├─ models/WuYinData.ets │ │ │ └─ repositories/YulvStore.ets │ │ ├─ domain │ │ │ ├─ services/audio │ │ │ ├─ services/animation │ │ │ └─ usecases/TriageEngine.ets │ │ ├─ entryability │ │ ├─ entryformability │ │ ├─ pages/Index.ets │ │ ├─ presentation/components │ │ └─ widget/pages/YulvPlayerCard.ets │ └─ src/main/resources └─ build-profile.json5我比较喜欢这个结构的原因是它有明确分层data放五音元数据、Session 列表和偏好存储。domain放推荐计算、音频服务、动画控制器。presentation放页面组件。entryability和entryformability负责系统能力入口。这样写的好处是页面不会把所有逻辑都塞进一个Index.ets后续要单独改音频、卡片或算法时风险会小很多。3. 应用能力配置在entry/src/main/module.json5中应用声明了三个关键能力{ module: { name: entry, type: entry, mainElement: EntryAbility, deviceTypes: [phone, tablet, 2in1], requestPermissions: [ { name: ohos.permission.KEEP_BACKGROUND_RUNNING } ], abilities: [ { name: EntryAbility, srcEntry: ./ets/entryability/EntryAbility.ets, launchType: singleton, backgroundModes: [audioPlayback] } ], extensionAbilities: [ { name: EntryFormAbility, srcEntry: ./ets/entryformability/EntryFormAbility.ets, type: form } ] } }这里有几个重点phone/tablet/2in1表示项目一开始就考虑了多设备。KEEP_BACKGROUND_RUNNING和audioPlayback是后台音频能力的基础。EntryFormAbility负责桌面卡片生命周期。4. 页面总体架构主页面Index.ets承担了五个 Tab 的组合Index.ets首页 HomeTab疗愈 HealTab辨证 TriageTab疗愈库 LibTab我的 ProfileTabIndexHeroCardIndexToneStripIndexQuickActionsHealCanvasToneAudioPlayerTriageEngineYulvStore从工程视角看这不是“一个页面写到底”的做法而是把可以复用的区域拆成组件。例如IndexHeroCard今日时辰推荐。IndexToneStrip五音选择条。IndexQuickActions首页快速操作入口。HealCanvas疗愈视觉动画。5. 首页快速入口设计首页的快速入口来自homeQuickActions()privatehomeQuickActions():QuickActionItem[]{return[{icon:$r(app.media.yulv_action_jiao),title:疏肝解郁,desc:角音疏泄条达,action:jiao},{icon:$r(app.media.yulv_action_triage),title:快速辨证,desc:8 题智能匹配,action:triage},{icon:$r(app.media.yulv_action_breathe),title:呼吸训练,desc:4-7-8 平复焦虑,action:breathe},{icon:$r(app.media.yulv_action_library),title:疗愈库,desc:五音场景按需选,action:lib}];}这个小函数的价值在于UI 不直接写死跳转逻辑而是用action做分发。privatehandleQuickAction(action:string):void{if(actionjiao){constssessionsForTone(jiao)[0];if(s){this.startHealWithSession(s);}return;}if(actiontriage){this.selectedTabIndex2;this.tabController.changeIndex(2);return;}if(actionbreathe){this.openFullBreath();return;}if(actionlib){this.selectedTabIndex3;this.tabController.changeIndex(3);}}这样后续新增入口时只需要补一项数据和一个 action 分支。6. 技术亮点总结这个项目最值得写成系列文章的地方有四个ArkTS 数据建模比较完整五音、脏腑、情绪、颜色、频率、Session 都有独立模型。推荐算法轻量但可解释每道题给五音打分结果可以直接展示给用户。音频链路完整本地生成 WAV、AVPlayer 播放、后台音频、AVSession、卡片控制都有覆盖。UI 不是简单列表有 Canvas 动画、响应式断点、沉浸式系统栏、桌面卡片。后续文章我会继续拆解这些模块数据模型、辨证引擎、Preferences 持久化、AVPlayer、后台音频、Canvas 动画、呼吸训练、桌面卡片和多端适配。9. 更完整的工程拆解为什么这不是一个普通 Demo如果只看页面效果律愈像是一个“音乐 问卷 卡片”的组合应用但从工程角度看它更像一个小型 HarmonyOS 原生应用样板。它同时覆盖了 Stage 模型、Ability 生命周期、ArkTS 组件状态、端侧存储、媒体播放、后台任务、桌面卡片和响应式布局。很多初学项目只写了 pages/Index.ets所有逻辑都堆在一个文件里短期能跑后期一加功能就会互相影响。律愈的价值在于把这些能力拆成了可讲清楚的模块。下面是我建议你在文章中强调的工程边界ext 页面层只负责状态绑定、布局、交互入口 领域层负责辨证算法、呼吸节奏、音频控制 数据层负责五音元数据和 Preferences 存储 系统层负责 Ability、FormAbility、后台音频和系统栏这种边界设计也方便写系列文章。每篇文章都可以围绕一个模块展开不会变成“我做了一个 App”的流水账。10. 主页面状态为什么集中在 Index.etsIndex.ets 中有很多 State包括当前 Tab、当前五音、当前 Session、播放状态、辨证状态、统计数据和断点状态。这种写法在中小型 ArkTS 项目里是合理的因为这些状态本质上都服务于主页面的展示和切换。s State private selectedTabIndex: number 0; State private currentTone: ToneKey gong; State private selectedSession: SessionItem sessionsForTone(gong)[0]; State private playing: boolean false; State private progressSec: number 0; State private triageResult: TriageResult | null null; State private currentBp: BpSize sm;这里不建议一开始就引入复杂状态管理库。原因很简单项目的共享状态并没有跨多个独立页面树传播大多数状态只在当前入口页面和子组件之间传递。对于这个阶段State Prop 回调函数 已经足够。11. 首页到疗愈页的核心跳转链路用户点击“开始今日疗愈”或某个 Session 后页面并不是直接播放而是先统一进入 startHealWithSession()s private startHealWithSession(session: SessionItem): void { void this.toneAudio?.pause(); this.currentTone session.tone; this.selectedSession session; this.progressSec 0; this.playing false; this.stopHealTimer(); this.selectedTabIndex 1; this.tabController.changeIndex(1); this.startBreathGuide(); }这个函数相当于“进入疗愈页”的唯一入口。它做了五件事停止旧音频避免两个音频状态冲突。更新当前音色和 Session。重置进度和播放状态。切换到疗愈 Tab。启动呼吸引导。这样的入口函数非常适合在文章里作为工程实践讲解因为它比单纯贴 UI 代码更有价值。12. 适合发布前补充的验证清单 ext首页时辰推荐是否随当前小时变化点击五音条是否能进入对应疗愈 Session播放、暂停、上一首、下一首是否会重置进度完成一轮疗愈后 Preferences 是否累计分钟数桌面卡片是否能控制同一套音频服务平板宽屏下 Tabs 是否切换为侧边栏onDestroy / aboutToDisappear 是否释放音频与定时器这部分可以让文章更像“实战复盘”而不是只介绍代码结构。14. 深度实现复盘项目结构总览这一篇文章如果只停留在“代码在哪里”价值还不够。真正值得写清楚的是这个模块在律愈项目里承担什么职责、它和其他模块怎么协作、哪些地方容易踩坑以及后续怎么从 Demo 走向可维护产品。模块边界可以这样概括主题项目结构总览 核心模块EntryAbility、Index.ets、data/domain/presentation 分层 主调用链EntryAbility.onCreate - loadContent(pages/Index) - Index.aboutToAppear 主要风险把页面、音频、卡片、存储都写在一个文件里会导致后期无法维护 扩展方向继续拆出 view model、统一路由表、补充单元测试和 HAP 调试文档这段可以直接放在文章中作为“读者先看这里”的摘要。CSDN 读者通常不是只想看最终效果他们更关心自己复制代码时应该先理解哪条主线。15. 建议补充的完整实现步骤为了让文章更像教程可以把实现拆成可操作步骤1. 先确认模块输入页面状态、用户操作、系统 Want 或本地数据 2. 再确认模块输出UI 展示、播放器动作、Preferences 写入或卡片刷新 3. 把纯业务逻辑放在 data/domain 层避免直接写进 ArkUI build 4. 页面只负责状态绑定和事件分发 5. 对异步操作增加 try/catch 和默认值兜底 6. 最后补充验证清单说明如何判断功能真的跑通对应到律愈项目最小闭环可以写成下面这种伪代码// 1. 页面收到用户操作privateonUserAction():void{constinputthis.collectCurrentState();voidthis.runUseCase(input);}// 2. 调用领域服务或仓储privateasyncrunUseCase(input:YulvInput):Promisevoid{try{constresultawaitYulvDomainService.execute(input);this.applyResultToUi(result);}catch(_e){this.showToast(操作失败请稍后重试);}}// 3. 页面只消费结果不关心底层细节privateapplyResultToUi(result:YulvResult):void{this.currentToneresult.tone;this.playingresult.playing;}这不是要求项目里必须存在这些类而是告诉读者页面、服务、结果应用应该分清楚。16. 代码讲解时应该强调的 ArkTS 细节HarmonyOS ArkTS 和普通前端代码有一些差异文章里建议主动说明1. 强类型ToneKey、SessionItem、TriageResult 这些类型能减少运行期错误 2. 状态驱动State 改变后 UI 自动刷新 3. 生命周期aboutToAppear、aboutToDisappear、onCreate、onDestroy 要成对考虑 4. 异步 APIPreferences、AVPlayer、Form 更新都要 await 或 catch 5. 资源释放音频文件、定时器、窗口监听、AVSession 都不能只创建不释放可以在文章中用下面的模式讲解状态StateprivatecurrentTone:ToneKeygong;Stateprivateplaying:booleanfalse;privateswitchTone(tone:ToneKey):void{this.currentTonetone;this.playingfalse;}重点不是这几行代码本身而是说明UI 显示、按钮状态、Canvas 颜色、音频频率都可以围绕 currentTone 变化。17. 调试与验收清单每篇文章最后建议加一段验收清单让读者知道自己跑通没有1. 入口函数是否被触发 2. 页面状态是否更新 3. 异步 API 是否执行成功 4. 异常时是否有兜底不会白屏 5. 切换页面后定时器、监听器、播放器是否释放 6. 手机、平板、2in1 下布局是否正常 7. 重新打开应用后本地状态是否符合预期如果文章面向比赛、课程设计或项目答辩还可以补充“演示脚本”演示 1首页时辰推荐说明数据模型如何驱动 UI 演示 2进入疗愈页说明音频、进度、呼吸和 Canvas 如何协同 演示 3完成辨证说明 8 题评分和本地保存 演示 4添加桌面卡片说明 FormAbility 与主应用通信 演示 5切到后台播放说明 AVSession 和后台任务18. 从 Demo 到产品还差什么当前律愈已经适合写实战文章但如果要继续产品化建议补这些能力内容层真实疗愈音轨、更多 Session、内容标签、收藏 算法层问卷权重调试、历史趋势、个性化推荐 工程层单元测试、错误日志、模块 README、构建说明 体验层深色模式、横屏布局、无障碍字体、播放失败提示 发布层签名配置、隐私说明、应用截图、版本更新日志这段可以作为文章结尾让读者感觉项目不是一次性 Demo而是有继续迭代的方向。体、播放失败提示发布层签名配置、隐私说明、应用截图、版本更新日志这段可以作为文章结尾让读者感觉项目不是一次性 Demo而是有继续迭代的方向。