【HarmonyOS学习笔记】2026-07-06 | 应用程序框架基础

📅 2026/7/8 2:34:29
【HarmonyOS学习笔记】2026-07-06 | 应用程序框架基础
【HarmonyOS学习笔记】2026-07-06 | 应用程序框架基础date: 2026-07-06tags: [HarmonyOS, UIAbility, 生命周期, AppStorage, StorageProp, StorageLink, ForEach, 预览器, WindowStage]1️⃣ 学习内容概览本节学习开发者学堂中应用程序框架基础章节核心内容是 UIAbility——HarmonyOS 应用运行时的基本单元。学习围绕 UIAbility 的生命周期管理、前台/后台启动机制、WindowStage 按需创建、AppStorage 全局状态管理、StorageProp/StorageLink 数据绑定、ForEach 渲染机制等知识点展开。学习过程中通过 UIAbilityLifeCycle 项目实操遇到了多个关键问题尤其是预览器与实机的差异导致排查走了弯路最终通过实测纠正了理论推论。2️⃣ 核心知识点与实操问题一、UIAbility 与 ArkUI 的层级关系UIAbility 是什么UIAbility 是 HarmonyOS应用框架层的核心概念是系统级的应用运行单元不是 ArkTS 语法层面的东西。ArkTS 写代码的语言和声明式 UI 框架UIAbility 应用运行时的生命周期管理器三层架构应用框架层UIAbility管理生命周期、窗口、页面加载 UI 层ArkUI/ArkTSEntry/Component/Navigation/State 等 配置层module.json5 / main_pages.json / route_map.json之前实操中EntryAbility.ets就是 UIAbilityloadContent()是它加载页面的方式。之前学的是UI 层怎么画页面现在学的是应用层怎么管理页面。通俗类比UIAbility 整个商店的经理管开门、关门、安排店面Entry 页面 店里的某个展厅顾客看到的东西Component 组件 展厅里的货架具体 UI 元素ArkTS 货架的设计语言怎么画货架、怎么摆商品经理UIAbility负责开店时把展厅Entry 页面布置好但经理不直接管货架组件——那是展厅内部的事。二、两套生命周期的区别重点UIAbility 生命周期组件生命周期所属层应用框架层UI 层定义位置EntryAbility.etsComponent struct 内回调onCreate → onWindowStageCreate → onForeground → onBackground → onDestroyaboutToAppear → aboutToDisappear粒度整个应用/Ability 级单个 UI 组件级触发者系统ArkUI 框架时序关系UIAbility.onCreate() → UIAbility.onWindowStageCreate() → loadContent(pages/LoginPage) → LoginPage.aboutToAppear() → 页面渲染 → UIAbility.onForeground() 切后台 → UIAbility.onBackground() 切前台 → UIAbility.onForeground() 退出 → LoginPage.aboutToDisappear() → UIAbility.onDestroy()三、UIAbility 生命周期详解前台启动startAbility用户点击 App 图标时的完整流程onCreate() → onWindowStageCreate() → onForeground()UIAbility 实例创建 ✅WindowStage 创建 ✅页面加载并显示 ✅后台启动startAbilityByCall代码调用的后台启动不经过前台onCreate() → onBackground()UIAbility 实例创建 ✅WindowStage不创建❌页面不存在 ❌后台启动拉到前台onNewWant() → onWindowStageCreate() → onForeground()此时才创建 WindowStage准备显示 UI。WindowStage 的按需创建机制创建顺序UIAbility → WindowStage按需 销毁顺序WindowStage → UIAbility - 没创建过就不需要销毁 - 创建了就必须在 UIAbility 销毁前先销毁通俗类比你雇了一个经理UIAbility来管仓库经理已经到岗了onCreate但还没有安排店面WindowStage因为现在只需要在仓库干活不需要开门营业。前台切后台 vs 后台启动 vs 销毁重建三个容易混淆的概念前台切后台按Home后台启动startAbilityByCall销毁重建UIAbility 实例✅ 保持不变✅ 新创建✅ 新创建旧的已销毁WindowStage✅ 保持不变❌ 从未创建✅ 重新创建页面组件✅ 保持不变❌ 不存在✅ 重新创建回调只有 onBackground()onCreate() → onBackground()onCreate() → … → onDestroy()旧实例本质状态切换从零创建死了重生三句话总结前台切后台 状态切换活着但休息后台启动 从出生就在后台从未开过店销毁重建 死了重生完全新的实例关键证据前台切后台后再切回来之前的输入内容、滚动位置等状态都在——如果销毁重建了这些状态都会丢失。WindowStage 的销毁规则场景是否需要销毁 WindowStage后台启动 → 直接关闭❌ 不需要从未创建过后台启动 → 拉前台 → 关闭✅ 先销毁 WindowStage再销毁 UIAbility前台启动 → 关闭✅ 先销毁 WindowStage再销毁 UIAbilitystartAbilityByCall 后台启动的实际使用场景场景说明音乐播放器App A 调用音乐服务 UIAbility 后台播放用户继续用 App A后台数据同步App A 调用同步服务 UIAbility 在后台同步用户可以做别的事后台下载浏览器调用下载管理 UIAbility 后台下载用户可以离开浏览器智能家居控制定时任务触发 UIAbility 后台检查温度、控制空调不需要弹界面共同特征需要干活但不需要给用户看界面。startAbility()startAbilityByCall()启动位置前台后台创建窗口✅ 立即创建❌ 不创建用户可见✅ 立即可见❌ 不可见设计意图“我要看到界面”“我要你干活不用给我看”关键认知一个应用可以有多个 UIAbility一个音乐 App 可能有 ├── UIAbility A主界面 ← 用户点 App 图标 → 前台启动startAbility └── UIAbility B播放服务 ← UIAbility A 调用 → 后台启动startAbilityByCall四、AppStorage 与 StorageProp 数据流StorageProp 代码拆解StorageProp(modelDataList)modelDataList:ModelData[][];// StorageProp(key) 变量名: 类型 默认值部分含义StorageProp装饰器从 AppStorage 单向读取数据modelDataListAppStorage 中的 keyModelData[]变量类型 []默认值AppStorage 中无此 key 时使用StorageProp vs StorageLink重点对比项StoragePropStorageLink方向单向全局 → 本地双向全局 ↔ 本地本地修改是否同步全局❌ 否✅ 是全局修改是否同步本地✅ 是✅ 是类比只读副本可写引用StorageProp 的初始化逻辑组件加载 ├── AppStorage 中有 modelDataList ? │ ├── ✅ 有 → 读取 AppStorage 中的值 │ └── ❌ 没有 → 使用默认值 [] │ 并且会在 AppStorage 中注册这个 key └── 后续 AppStorage 更新 → 自动同步到组件状态管理装饰器全家族按作用域从小到大State组件内→ Prop/Link父子间→ Provide/Consume跨层级→ StorageProp/StorageLink全局通俗类比AppStorage 公司公告栏StorageProp 看了一眼公告栏记在脑子里公告栏更新你重新看但你改自己笔记不影响公告栏StorageLink 直接在公告栏上改你改了所有人都能看到AppStorage 初始化位置与 UIAbility 生命周期的配合UIAbilityLifeCycle 项目的完整数据流用户操作启动/切后台/切前台/退出 │ ▼ 系统触发 UIAbility 生命周期回调 │ ▼ modelDataList.push(new ModelData(事件名, 时间)) ← 往数组追加记录 │ ▼ AppStorage.setOrCreate(modelDataList, modelDataList) ← 推送到全局仓库 │ ▼ StorageProp(modelDataList) 自动感知变化 ← 组件自动更新 │ ▼ ForEach 重新渲染列表 ← 用户看到新增的记录EntryAbility.ets 中的实现模式letmodelDataList:ModelData[][];// 模块级变量所有方法共享onCreate(){modelDataList.push(newModelData(Ability Create,newDate().toLocaleString()));AppStorage.setOrCreate(modelDataList,modelDataList);// 每次都推送}// 其他生命周期回调同样的模式push → setOrCreate回调push 的记录同步到 AppStorageonCreate()‘Ability Create’✅onDestroy()‘Ability Destroy’✅onWindowStageCreate()‘WindowStage Create’✅onWindowStageDestroy()‘WindowStage Destroy’✅onForeground()‘Ability Foreground’✅onBackground()‘Ability Background’✅项目设计意图用 UI 可视化地展示 UIAbility 的生命周期调用过程。每次生命周期触发就追加一条记录到列表用户可以在界面上实时看到回调的触发情况。模块级变量 vs 实例变量letmodelDataList:ModelData[][];// 模块级整个模块只有一份跨生命周期持续积累// vsprivatemodelDataList:ModelData[][];// 实例级每个 UIAbility 实例各自一份销毁就没了这里用模块级变量是合理的——需要跨生命周期持续积累记录。AppStorage 初始化的三种常见位置位置适用场景EntryAbility.onCreate()应用启动时就确定的全局数据如本项目的生命周期记录入口页面 aboutToAppear()与 UI 强相关的全局数据初始化onWindowStageCreate()需要窗口创建后才初始化的数据五、ForEach 的 key 生成与重绘机制字符串 number 自动转换JSON.stringify(modelData)index// {title:Ability Create,time:2026-7-6} 0// {title:Ability Create,time:2026-7-6}0 (string ✅)ForEach 的 key 生成函数要求返回 string 类型字符串 number自动转为 string没有问题。key 变化 ≠ “重绘”而是销毁重建纠正理解概念含义代价UI 更新组件存在数据变了界面跟着变低局部刷新销毁重建旧组件销毁新组件从零创建高完全重建ForEach 的渲染逻辑ForEach 重新遍历数据源 → 为每一项生成新 key → 和旧的 key 对比 ├── 旧 key 不在新 key 列表中 → 销毁旧组件 ├── 新 key 不在旧 key 列表中 → 创建新组件 └── 新旧 key 相同 → 复用旧组件不重建但数据更新会触发 UI 更新key 相同→ 组件复用数据变了只做 UI 更新轻量key 不同→ 旧组件销毁新组件重建重量key 依赖 index 的隐患// 当前写法同时依赖数据内容和索引位置(modelData:ModelData,index:number)JSON.stringify(modelData)index原数组[A, B, C] → key: A0, B1, C2 头部插入 D[D, A, B, C] → key: D0, A1, B2, C3 A/B/C 的 key 全变了 → 三个组件全部销毁重建原则key 应只依赖数据的唯一标识不依赖数组索引。更好的写法// 有唯一 ID(modelData:ModelData)modelData.id.toString()// 无 ID 时依赖内容(modelData:ModelData)modelData.timemodelData.title六、预览器与实机的差异实测纠正问题起因修改了 onCreate 中的AppStorage.setOrCreate为[...modelDataList]展开写法后ListItem 仍然不显示且连日志都没有。思考链路看到代码中AppStorage.setOrCreate(modelDataList, modelDataList)传的是原数组引用推测push()不改变引用AppStorage 引用比较检测不到变化所以 UI 不更新建议改为[...modelDataList]创建新数组但改了 onCreate 后仍然不显示且连日志都没有发现日志都没有 onCreate根本没执行意识到一直在用预览器测试预览器不执行 UIAbility 生命周期切换到实机测试一切正常ListItem 正常显示更进一步的发现用原来的写法不展开数组也能正常工作预览器Previewer的局限性功能预览器实机/模拟器渲染 Entry 组件✅✅执行 UIAbility 生命周期❌✅AppStorage.setOrCreate❌✅hilog 日志输出❌✅StorageProp 读取 AppStorage 数据❌读到默认值✅涉及 UIAbility 的功能必须用模拟器或实机验证预览器无法验证。AppStorage 数组引用问题——实测纠正之前的理论推论push()修改原数组引用不变AppStorage 用比较检测不到变化。实测结论AppStorage.setOrCreate(modelDataList, modelDataList)不展开也能正常工作说明 AppStorage 内部不是简单的引用比较可能做了深层内容比较。所以[...modelDataList]展开写法不是必须的原来的写法就是对的。3️⃣ 为什么这些问题会出现刨根问底 我的理解UIAbility 的隐形管理之前一直用着 EntryAbility 但没意识到它的存在属于用而未识。UIAbility 在幕后管理生命周期和窗口开发者如果不主动关注很容易只看到 UI 层而忽略框架层。WindowStage 按需设计WindowStage 不是随 UIAbility 自动创建的而是按需创建。这打破了UIAbility 一定有界面的直觉。后台启动不需要界面所以不创建窗口这是合理的资源节省设计。两套生命周期容易混淆UIAbility 生命周期应用级和组件生命周期UI 级是两个不同层次但都叫生命周期容易混为一谈。关键区别在于粒度和触发者前者是系统管理整个 Ability后者是框架管理单个组件。前台切后台 ≠ 销毁重建的直觉陷阱看到后台启动不触发 onWindowStageCreate容易联想到前台切后台也销毁了界面。但两者本质不同——前者是从未创建后者是状态保持。关键证据是切回来时状态还在。AppStorage 的检测机制比预想智能理论推论认为 push 不改变引用AppStorage 检测不到变化。但实测表明 AppStorage 内部可能做了深层比较原写法就能工作。这提醒我们理论推论需要实测验证。预览器的局限性预览器只渲染 UI不执行 UIAbility这是造成数据不显示的根本原因。排查时先怀疑代码逻辑最终发现是工具差异——这个教训很深刻。4️⃣ 实操验证记录#问题现象排查过程解决1UIAbility 与 ArkTS 关系以为 UIAbility 是 ArkTS 语法结构回顾之前实操的 EntryAbility.ets、loadContent认识到 UIAbility 是应用框架层不是语法层2后台启动不创建 WindowStage教材说不触发 onWindowStageCreate第一反应是创建了又销毁了实际是从未创建WindowStage 按需创建3前台切后台是否销毁重建怀疑切后台 销毁重建想到切回来状态还在不可能销毁前台切后台是状态切换不是销毁重建4startAbilityByCall 使用场景不理解什么时候会后台启动思考谁会调用 → 别的应用/系统后台服务场景音乐、下载、同步等5StorageProp 含义不理解这行代码的作用搜索项目无 AppStorage 初始化StorageProp 单向绑定默认值兜底6AppStorage 初始化位置不知在哪里初始化按教材补全 setOrCreate 调用在 UIAbility 生命周期回调中 push setOrCreate7ForEach key 生成疑问字符串number和重绘机制分析 ForEach 渲染逻辑key 变化销毁重建≠重绘key 应稳定唯一8预览器不显示 ListItem改了数组展开写法仍不显示无日志发现 onCreate 未执行 → 预览器不执行 UIAbility切换实机测试发现原写法也能工作5️⃣ 最终结论最佳实践维度建议UIAbility 认知UIAbility 是应用框架层管理生命周期和窗口ArkUI 是 UI 层管理组件渲染两套生命周期UIAbility 生命周期管应用级组件生命周期管 UI 级不可混淆WindowStage按需创建后台启动不需要销毁规则创建过就需销毁没创建过就不需要前台切后台纯状态切换onForeground ↔ onBackground不涉及销毁切回状态恢复后台启动startAbilityByCall 场景后台服务一个应用可有多个 UIAbility 各司其职StorageProp/StorageLink单向 vs 双向搞清方向AppStorage 是全局仓库需在某处 setOrCreate 初始化状态管理装饰器按作用域State → Prop/Link → Provide/Consume → StorageProp/StorageLinkForEach keykey 应稳定唯一避免依赖 indexkey 变化销毁重建代价高于 UI 更新预览器局限不执行 UIAbility 生命周期涉及 UIAbility 的功能必须实机/模拟器验证实测验证理论推论需实测验证AppStorage 数组引用问题实测不成立// UIAbility 生命周期中 AppStorage 数据推送的标准模式 let modelDataList: ModelData[] []; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { modelDataList.push(new ModelData(Ability Create, new Date().toLocaleString())); AppStorage.setOrCreate(modelDataList, modelDataList); } onWindowStageCreate(windowStage: window.WindowStage): void { modelDataList.push(new ModelData(WindowStage Create, new Date().toLocaleString())); AppStorage.setOrCreate(modelDataList, modelDataList); windowStage.loadContent(pages/Index, (err) { ... }); } onForeground(): void { modelDataList.push(new ModelData(Ability Foreground, new Date().toLocaleString())); AppStorage.setOrCreate(modelDataList, modelDataList); } onBackground(): void { modelDataList.push(new ModelData(Ability Background, new Date().toLocaleString())); AppStorage.setOrCreate(modelDataList, modelDataList); } }核心原则UIAbility 是应用的隐形管理者它管理生命周期、窗口和全局状态但开发者往往只看到 UI 层而忽略框架层。理解 UIAbility 的存在、WindowStage 的按需创建、两套生命周期的区别是排查应用级问题白屏、数据不显示、生命周期异常的关键起点。涉及 UIAbility 的功能不可依赖预览器验证必须实机测试。学习小结今天系统学习了 UIAbility 这一应用框架层的核心概念搞清了它与 ArkUI 的层级关系、两套生命周期的区别、WindowStage 的按需创建机制、前台切后台与后台启动的本质差异、AppStorage 与 StorageProp 的数据流全链路、ForEach 的 key 与渲染机制。最大收获有两个一是预览器不执行 UIAbility这一关键认知解释了之前 ListItem 不显示的根本原因二是理论推论需要实测验证AppStorage 数组引用问题实测不成立原写法就能工作提醒自己不能仅凭逻辑推理下结论。框架的默认行为WindowStage 按需创建、AppStorage 智能检测、预览器跳过 UIAbility都需要开发者显式理解和验证。懿路向前 · AI辅助整理2026-07-06