文章目录
- 前言
- UIAbility 是什么
- 4 个关键回调详解
- onCreate:应用启动,初始化就放这
- onWindowStageCreate:窗口创建,加载页面
- onForeground:切到前台,恢复状态
- onBackground:切到后台,保存状态
- WindowStage 生命周期
- 与 UI 生命周期关系
- 实战:页面状态保存
- 写在最后
前言
生命周期回调不是随便写的,每个回调代表应用处于不同状态,搞混了代码逻辑就全乱。
我见过最多的低级 bug 之一:在onCreate里初始化数据库,结果切到后台再回来,数据库又初始化了一遍,数据全乱了。同学,你压根没搞懂 UIAbility 的生命周期啊。
UIAbility 是 HarmonyOS7 应用的基本单元——你可以理解为 Android 的 Activity,但粒度更大。一个应用可以有多个 UIAbility,每个对应最近任务列表里的一条记录。
系统在调度 UIAbility 的时候,会依次调用几个生命周期回调。你的代码写在哪个回调里,直接决定了它的执行时机。写错位置,轻则重复执行,重则数据丢失。今天把这 4 个关键回调掰开了讲。
UIAbility 是什么
一句话:UIAbility 是系统调度的基本单元,提供窗口让 UI 渲染。
所有页面和组件要显示,必须依托一个窗口。UIAbility 就是创建窗口的那个"壳"——你写的@Entry页面,都是通过 UIAbility 的windowStage.loadContent()加载进去的。
一个 UIAbility 可以管理多个页面(通过路由跳转),也可以只有单个页面。具体怎么拆分看业务需要。
4 个关键回调详解
UIAbility 的生命周期回调有 6 个,但最核心的是这 4 个:
onCreate → onWindowStageCreate → onForeground → onBackground ↕ (可反复切换)加上销毁阶段的onWindowStageDestroy和onDestroy,一共 6 个。重点看前 4 个。
onCreate:应用启动,初始化就放这
系统创建 UIAbility 实例时触发,整个生命周期只调一次。
import{UIAbility,AbilityConstant,Want}from'@kit.AbilityKit';import{window}from'@kit.ArkUI';exportdefaultclassEntryAbilityextendsUIAbility{onCreate(want:Want,launchParam:AbilityConstant.LaunchParam):void{console.info('onCreate: 应用启动')// 读取启动参数letparam=want.parameters?.['userId']asstringconsole.info(`启动参数:${param}`)// 初始化全局资源(数据库连接、网络配置等)AppStorage.setOrCreate('globalConfig',{theme:'dark'})}}关键代码讲解:
want参数 —— 启动时传进来的数据,别的 Ability 或系统拉起你时可以通过want.parameters传参launchParam—— 包含启动模式信息,可以判断是冷启动还是热启动AppStorage.setOrCreate()—— 在这里设置全局数据,所有页面都能访问
血泪教训:只放一次性初始化逻辑,别把每次切回前台都要执行的代码放这里。
onCreate只跑一次,切后台再回来不会重新触发。
onWindowStageCreate:窗口创建,加载页面
onCreate之后,系统创建窗口,触发这个回调。加载入口页面就在这里。
onWindowStageCreate(windowStage:window.WindowStage):void{console.info('onWindowStageCreate: 窗口已创建')// 设置窗口属性windowStage.getMainWindow().then((win:window.Window)=>{win.setWindowLayoutFullScreen(true)letsysBarProps:window.SystemBarProperties={statusBarColor:'#00000000',statusBarContentColor:'#000000'}win.setWindowSystemBarProperties(sysBarProps)})// 加载入口页面windowStage.loadContent('pages/Index',(err)=>{if(err.code){console.error(`页面加载失败:${err.code}`)return}console.info('页面加载成功')})}关键代码讲解:
windowStage.getMainWindow()—— 获取主窗口对象,可以设置全屏、状态栏颜色等setWindowLayoutFullScreen(true)—— 设置沉浸式状态栏,页面内容延伸到状态栏区域windowStage.loadContent('pages/Index')——这行最关键,指定要加载的页面路径,路径对应pages/目录下的文件- 页面加载是异步的,回调里可以判断是否成功
很多开发者喜欢在这里初始化数据库,也可以,但注意这只执行一次,别重复初始化。
onForeground:切到前台,恢复状态
应用从后台切回前台时触发。用户看到的界面要刷新、暂停的任务要恢复,都在这里处理。
onForeground():void{console.info('onForeground: 切到前台')// 恢复暂停的任务this.resumeBackgroundTask()// 刷新可能过期的数据this.refreshDataIfNeeded()// 通知所有页面恢复AppStorage.setOrCreate('isForeground',true)}privateresumeBackgroundTask():void{lettaskInfo=AppStorage.get<string>('pausedTask')if(taskInfo){console.info(`恢复任务:${taskInfo}`)}}privaterefreshDataIfNeeded():void{letlastUpdate=AppStorage.get<number>('lastUpdateTime')||0letnow=Date.now()if(now-lastUpdate>30000){console.info('数据已过期,重新拉取')AppStorage.setOrCreate('needRefresh',true)}}关键代码讲解:
- 这个回调每次从后台回到前台都会触发,可以反复调用
AppStorage.setOrCreate('isForeground', true)—— 页面组件可以通过@StorageLink监听这个值,自动响应前后台切换- 数据过期检查 —— 应用在后台可能很久,回到前台时数据可能已经过时,需要判断是否刷新
这才是放"切回前台逻辑"的正确位置,不是onCreate!
onBackground:切到后台,保存状态
用户按 Home 键、切到别的 App,应用进入后台,触发onBackground。保存状态、暂停任务、释放资源,就在这里。
onBackground():void{console.info('onBackground: 切到后台')// 保存当前状态this.saveAppState()// 暂停耗时操作this.pauseOngoingTasks()// 释放不必要的资源this.releaseIdleResources()AppStorage.setOrCreate('isForeground',false)AppStorage.setOrCreate('lastUpdateTime',Date.now())}privatesaveAppState():void{letstate={currentPage:'DetailPage',scrollPosition:256,formData:{name:'测试',age:20}}AppStorage.setOrCreate('appState',JSON.stringify(state))}privatepauseOngoingTasks():void{console.info('暂停进行中的任务')}privatereleaseIdleResources():void{console.info('释放空闲资源')}关键代码讲解:
saveAppState()—— 把关键状态序列化存到 AppStorage,下次回来可以恢复- 切后台后进程随时可能被杀,重要数据必须在这里持久化,不能指望下次
onForeground还能恢复内存中的数据 - 释放资源 —— 比如关闭不必要的文件句柄、取消网络请求等,减少内存占用,降低被系统杀掉的概率
WindowStage 生命周期
WindowStage 也有自己的生命周期,夹在 UIAbility 生命周期之间:
onWindowStageCreate → onWindowStageDestroyonWindowStageCreate:窗口创建时触发,在这里加载页面onWindowStageDestroy:窗口销毁时触发,在 UIAbility 销毁之前执行
如果你在onWindowStageCreate里做了资源分配(比如创建数据库连接),对应的清理逻辑应该放在onWindowStageDestroy里。
与 UI 生命周期关系
UIAbility 生命周期是宏观的,组件生命周期是微观的。它们的关系:
UIAbility.onCreate │ UIAbility.onWindowStageCreate │ ▼ 页面 aboutToAppear ←── 组件 aboutToAppear │ ▼ 页面 onPageShow ←── 用户可见 │ ▼ UIAbility.onForeground │ ─ ─ ─ 用户使用中 ─ ─ ─ │ UIAbility.onBackground │ ▼ 页面 onPageHide ←── 用户不可见 │ ▼ UIAbility.onWindowStageDestroy │ ▼ UIAbility.onDestroy │ ▼ 页面 aboutToDisappear ←── 组件 aboutToDisappear关键理解:UIAbility 生命周期管理的是进程级别的事,组件生命周期管理的是 UI 级别的事。别搞混了。
实战:页面状态保存
最常见的场景:用户在详情页滑到一半,切到别的 App,回来后页面位置丢了。用生命周期回调来保存和恢复:
import{UIAbility,AbilityConstant,Want}from'@kit.AbilityKit';import{window}from'@kit.ArkUI';exportdefaultclassEntryAbilityextendsUIAbility{onCreate(want:Want,launchParam:AbilityConstant.LaunchParam):void{AppStorage.setOrCreate('scrollOffset',0)AppStorage.setOrCreate('currentRoute','Index')}onWindowStageCreate(windowStage:window.WindowStage):void{windowStage.loadContent('pages/Index')}onForeground():void{letsavedOffset=AppStorage.get<number>('scrollOffset')||0if(savedOffset>0){AppStorage.setOrCreate('restoreScroll',true)}}onBackground():void{AppStorage.setOrCreate('isForeground',false)AppStorage.setOrCreate('lastBackgroundTime',Date.now())}}页面侧配合恢复:
@Entry@Componentstruct DetailPage{@StorageLink('scrollOffset')scrollOffset:number=0@StorageLink('restoreScroll')needRestore:boolean=falseprivatescroller:Scroller=newScroller()onPageShow():void{if(this.needRestore&&this.scrollOffset>0){setTimeout(()=>{this.scroller.scrollOffset(this.scrollOffset)this.needRestore=false},100)}}onPageHide():void{this.scrollOffset=this.scroller.currentOffset().yOffset}build(){Scroll(this.scroller){Column(){ForEach([1,2,3,4,5,6,7,8,9,10],(item:number)=>{Text(`第${item}项内容`).height(120)})}}}}关键代码讲解:
onPageHide里保存滚动偏移量到AppStorage—— 切后台时页面先隐藏,这是最后的保存机会onForeground里设置restoreScroll标记 —— 通知页面需要恢复滚动位置onPageShow里执行恢复 —— 页面重新可见时,根据标记和保存的偏移量滚动回去setTimeout延迟 100ms —— 等页面渲染完再滚动,否则可能还没布局好就跳了
写在最后
UIAbility 生命周期就一句话:onCreate 初始化一次,onForeground 恢复状态,onBackground 保存状态,别搞混。
最常犯的错误就是把本该放在onForeground的逻辑写到了onCreate,结果冷启动正常、热启动出 bug。记住onCreate只调一次,前后台切换不会重新触发——这点搞明白了,生命周期的坑就踩得差不多了。