【共创季稿事节】HarmonyOS 6.1 UIAbility避坑指南:从onCreate到onDestroy的电商多页面实战 承接上篇《应用级状态管理AppStorage与PersistentStorage实战》本文解决电商App最核心的多页面交互问题从商品列表页跳转到详情页、再跳转到购物车页如何实现页面跳转、参数传递、页面栈管理为什么点击商品卡片后没有打开新页面为什么返回时购物车数据没有更新这些问题都指向Stage模型的核心——UIAbility的生命周期与路由机制。所有结论均经过API23环境验证附赶稿专用浏览器模拟方案。一、前言从单页面到多页面的必然跨越上篇写完应用级状态管理后我尝试给电商Demo加一个商品详情页点击列表页的商品卡片跳转到详情页展示完整参数再点击加入购物车返回列表页。结果一运行就踩了三个坑点击卡片没反应报Router page not found错误详情页能打开但接收不到商品ID参数返回列表页后购物车数量没更新查了官方文档才发现HarmonyOS 6.1的Stage模型和之前的FA模型完全不同页面跳转不是靠组件自己而是靠UIAbility承载的路由机制。UIAbility是应用的功能单元一个UIAbility可以包含多个页面而页面路由是UIAbility内部的导航逻辑。本文将基于电商场景把UIAbility的生命周期、页面路由、参数传递彻底拆透帮你避开90%的新手坑。二、核心概念辨析官方没讲透的3组区别很多新手会把UIAbility、Page、Window搞混甚至和Android的Activity划等号我们先把边界理清楚2.1 UIAbility vs Page vs Window维度UIAbility应用组件Page页面Window窗口定位应用功能的独立单元拥有独立的生命周期UIAbility内的单个交互界面UIAbility的显示载体负责绘制UI生命周期独立管理onCreate→onWindowStageCreate→onForeground→onBackground→onDestroy依附于UIAbilityonPageShow→onPageHide→onPageDestroy依附于UIAbilityonWindowStageCreate→onWindowStageDestroy适用场景电商模块、支付模块、用户中心等独立功能商品列表页、详情页、购物车页等单个界面悬浮窗、分屏窗口、全屏窗口等显示形态启动方式通过context.startAbility()启动通过router.pushUrl()在UIAbility内部跳转由系统自动创建无需手动管理2.2 Stage模型路由 vs FA模型路由维度Stage模型API 10当前主流FA模型已逐步废弃路由APIimport { router } from kit.ArkUIimport featureAbility from ohos.ability.featureAbility页面管理基于页面栈支持pushUrl/back/replaceUrl基于Ability栈跳转逻辑复杂参数传递通过params字段传递大小限制128KB通过want传递限制更严格适用场景所有新开发的应用旧应用兼容核心认知我们平时写的Index.ets、DetailPage.ets都是Page页面它们必须依附于一个UIAbility默认是EntryAbility才能运行。页面跳转的本质是在同一个UIAbility内切换不同的Page。三、代码实现API23 电商多页面实战我们基于上篇的电商列表页改造新增商品详情页实现完整的多页面跳转逻辑。3.1 第一步注册页面路径新手必踩坑所有页面必须在main_pages.json中注册否则路由会报Page not found错误。打开entry/src/main/resources/base/profile/main_pages.json{ src: [ pages/Index, // 商品列表页已有 pages/DetailPage // 新增商品详情页 ] }3.2 第二步修改商品列表页Index.ets添加跳转逻辑沿用上篇的Index.ets新增点击商品卡片跳转详情页的逻辑import { router } from kit.ArkUI import { GoodsItem } from ./GoodsItem import { GlobalState } from ../common/GlobalState import { GoodsBean } from ./GoodsItem Entry Component struct Index { StorageLink(cartCounts) counts: number[] [0, 0, 0] StorageLink(pausedId) pausedId: number -1 State goodsList: GoodsBean[] [ { id: 1, title: 【API23新特性】HarmonyOS 6.1 定制款超长商品标题..., price: 5999 }, { id: 2, title: 华为Mate 60 Pro 先锋计划 全网通智能手机..., price: 6999 }, { id: 3, title: 鸿蒙智联认证 智能穿戴手表 长续航蓝牙通话..., price: 1299 } ] // 跳转到商品详情页 goToDetail(goodsId: number): void { router.pushUrl({ url: pages/DetailPage, // 必须和main_pages.json里的路径一致 params: { goodsId: goodsId, // 传递商品ID作为参数 from: Index // 传递来源页面方便埋点/逻辑判断 } }, router.RouterMode.Standard, (err) { if (err) { console.error(跳转详情页失败, err.message) } }) } get totalPrice(): number { return GlobalState.calcTotalPrice(this.goodsList) } build() { Column() { Text(电商商品列表页 (UIAbility多页面实战)) .fontSize(22) .fontWeight(FontWeight.Bold) .margin({ top: 20, bottom: 20 }) List() { ForEach(this.goodsList, (item: GoodsBean, index: number) { ListItem() { // 给商品卡片添加点击事件跳转到详情页 GoodsItem({ goods: item, pausedId: $this.pausedId, count: $this.counts[index] }) .onClick(() { this.goToDetail(item.id) // 点击卡片触发跳转 }) } }, (item: GoodsBean) item.id.toString()) } .width(100%) .layoutWeight(1) // 底部结算栏点击跳转到购物车页后续实现 Row() { Text(总价¥${this.totalPrice}) .fontSize(20) .fontColor(#FF0000) .fontWeight(FontWeight.Bold) Blank() Button(去结算) .backgroundColor(#0A59F7) .fontColor(Color.White) .onClick(() { // 后续实现跳转到结算页 console.log(跳转到结算页) }) } .width(100%) .padding(20) .backgroundColor(#f8f9fa) } .width(100%) .height(100%) .backgroundColor(#f0f0f0) } }3.3 第三步新增商品详情页DetailPage.ets创建entry/src/main/ets/pages/DetailPage.ets实现详情页逻辑import { router } from kit.ArkUI import { GlobalState } from ../common/GlobalState Entry Component struct DetailPage { State goodsId: number 0 // 商品ID从列表页传递过来 State goodsTitle: string State goodsPrice: number 0 StorageLink(cartCounts) counts: number[] [0, 0, 0] aboutToAppear(): void { // 接收从列表页传递过来的参数 const params router.getParams() if (params) { this.goodsId params[goodsId] as number || 0 this.goodsTitle params[goodsTitle] as string || this.goodsPrice params[goodsPrice] as number || 0 console.log(详情页接收到参数, params) } } // 加入购物车 addToCart(): void { if (this.goodsId 0) { const index this.goodsId - 1 // 商品ID从1开始数组索引从0开始 GlobalState.updateCartCount(index, this.counts[index] 1) // 返回列表页 router.back() } } build() { Column() { // 顶部导航栏 Row() { Button( 返回) .backgroundColor(transparent) .fontColor(#0A59F7) .onClick(() { router.back() // 返回上一页 }) Blank() Text(商品详情页) .fontSize(18) .fontWeight(FontWeight.Bold) Blank() } .width(100%) .padding(15) .backgroundColor(#FFFFFF) // 商品信息 Column({ space: 20 }) { // 商品图片占位 Column() .width(100%) .height(250) .backgroundColor(#e8f4ff) .borderRadius(12) .margin({ top: 20 }) Text(this.goodsTitle || 商品${this.goodsId}详情) .fontSize(20) .fontWeight(FontWeight.Bold) .margin({ top: 20 }) Text(¥${this.goodsPrice}) .fontSize(24) .fontColor(#FF0000) .fontWeight(FontWeight.Bold) Text(这是商品详情介绍包含参数、售后、评价等信息...) .fontSize(14) .fontColor(#666666) .margin({ top: 10 }) // 加入购物车按钮 Button(加入购物车) .width(90%) .height(50) .backgroundColor(#0A59F7) .fontColor(Color.White) .fontSize(18) .onClick(() { this.addToCart() }) } .padding(20) .alignItems(HorizontalAlign.Start) } .width(100%) .height(100%) .backgroundColor(#f0f0f0) } }3.4 第四步验证UIAbility生命周期关键调试技巧打开entry/src/main/ets/entryability/EntryAbility.ts添加生命周期日志验证执行顺序import { UIAbility, AbilityConstant, Want } from kit.AbilityKit import { window } from kit.ArkUI import { PersistentStorage } from kit.ArkUI export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { console.log(EntryAbility onCreate启动参数, want.parameters) // 初始化PersistentStorage上篇内容 PersistentStorage.persistProp(cartCounts, [0, 0, 0]) PersistentStorage.persistProp(pausedId, -1) } onWindowStageCreate(windowStage: window.WindowStage): void { console.log(EntryAbility onWindowStageCreate) windowStage.loadContent(pages/Index, (err) { if (err.code) { console.error(加载页面失败, err.message) return } console.log(页面加载成功) }) } onNewWant(want: Want): void { // 当UIAbility已经启动再次被调用时触发比如从通知栏点击进入 console.log(EntryAbility onNewWant新参数, want.parameters) } onForeground(): void { console.log(EntryAbility onForeground进入前台) } onBackground(): void { console.log(EntryAbility onBackground进入后台) } onDestroy(): void { console.log(EntryAbility onDestroy销毁) } }生命周期执行顺序验证首次启动ApponCreate→onWindowStageCreate→onForeground跳转到详情页无UIAbility生命周期变化页面跳转是内部行为按Home键回到桌面onBackground从桌面重新打开ApponForeground划掉App后台进程onDestroy四、原理剖析为什么这样写性能最优4.1 页面栈的最小化刷新router.pushUrl会将新页面压入页面栈返回时弹出栈顶页面。ArkUI只会重建当前显示的页面不会重建整个UIAbility符合最小刷新原则。比如从列表页跳转到详情页列表页的组件不会被销毁返回时直接复用性能极高。4.2 单实例模式的合理使用如果希望购物车页全局只有一个实例不管从哪个页面跳转都不会重复创建可以在module.json5中配置UIAbility的启动模式{ module: { abilities: [ { name: EntryAbility, launchType: singleton, // 单实例模式 srcEntry: ./ets/entryability/EntryAbility.ets } ] } }配置后多次启动EntryAbility都会复用同一个实例触发onNewWant回调而不是重新创建适合购物车、用户中心等全局唯一的页面。4.3 参数传递的最优路径小参数128KB通过router.pushUrl的params传递简单直接大参数/共享数据通过AppStorage中转避免参数超限呼应上篇的应用级状态管理跨Ability传递通过want.parameters传递适合不同UIAbility之间的数据交互五、踩坑记录官方文档没写的5个细节这些坑我花了3小时才踩明白新手可以直接跳过5.1 页面路径必须和main_pages.json完全一致❌ 错误router.pushUrl({ url: pages/detail })少了Page后缀✅ 正确router.pushUrl({ url: pages/DetailPage })和main_pages.json里的src完全一致大小写敏感原因路由系统是根据main_pages.json查找页面的路径不匹配会直接报Page not found错误。5.2 参数大小限制128KB❌ 错误传递一个10MB的商品详情对象作为params✅ 正确只传递商品ID详情数据从网络/AppStorage加载原因params的大小限制为128KB超过会直接抛异常。大对象必须通过AppStorage或数据库中转。5.3onNewWant必须重写当UIAbility配置为singleton模式时再次启动不会触发onCreate而是触发onNewWant。如果需要在启动时接收新参数必须在onNewWant中处理否则参数会丢失。5.4 页面返回时的参数传递❌ 错误从详情页返回时用router.pushUrl跳转到列表页✅ 正确用router.back()返回列表页在onPageShow中读取AppStorage里的更新数据原因pushUrl会创建新的页面实例导致页面栈无限增长最终超过32层的限制抛异常。返回时必须用router.back()。5.5 页面栈深度限制32层HarmonyOS的页面栈默认最多支持32层超过会抛Stack overflow异常。因此在开发中要避免无意义的跳转比如不要在循环中调用pushUrl返回时一定要用back而不是pushUrl。六、总结与延伸本文实现了从单页面到多页面的跨越核心收获有三个UIAbility是Stage模型的核心承载一组相关页面拥有独立的生命周期页面路由基于页面栈pushUrl入栈、back出栈避免页面栈溢出参数传递要根据大小选择合适的路径大对象必须用AppStorage中转至此我们已经覆盖了HarmonyOS 6.1开发的环境搭建→组件基础→状态管理→业务实战→应用级存储→多页面路由全流程。下一篇我们将挑战Stage模型的高级特性——分布式流转讲解如何实现跨设备手机、平板、智慧屏的页面接续、数据同步让我们的电商App真正具备万物互联的能力。