搞鸿蒙Stage模型开发时,不少从Android转过来的开发者容易在生命周期上翻车。比如应用退后台再回来定位一直转、切几个页面后返回数据全丢失,这些问题的根源往往是回调时机没搞清楚。本文将结合真实踩坑经历,梳理组件、页面、UIAbility三层的生命周期回调顺序和常见错误,并给出正确写法。
一、生命周期分层概览
Stage模型的生命周期分为四层,每层有独立的回调集合:
- 组件级:@Component装饰的ArkTS组件,有aboutToAppear和aboutToDisappear;LazyForEach复用场景还有aboutToReuse。
- 页面级:@Entry装饰的页面组件,额外拥有onPageShow、onPageHide、onBackPress。
- UIAbility级:应用的功能单元,核心回调包括onCreate、onWindowStageCreate、onForeground、onBackground、onWindowStageDestroy、onDestroy。
- 进程/应用级:通过ApplicationStateChangeCallback或appManager监听整个进程或系统应用的前后台切换。
层级之间不是独立的:UIAbility销毁时其内部的页面和组件也会销毁,但页面隐藏不一定代表UIAbility进入后台。理解嵌套关系是避免诡异Bug的第一步。
二、组件生命周期基础
每个@Component组件都有aboutToAppear和aboutToDisappear:- @Component
- export struct MyComponent {
- aboutToAppear() {
- // build()之前执行,适合轻量初始化
- console.info('组件即将创建');
- }
- aboutToDisappear() {
- // 组件即将移除,清理资源
- console.info('组件即将销毁');
- }
- build() {
- Column() { Text('Hello') }
- }
- }
复制代码 注意aboutToAppear会阻塞build(),不要在里面做重操作(如读大文件),否则页面会卡顿。LazyForEach复用组件时还会回调aboutToReuse,需要重置组件状态,否则滑动时可能出现数据闪烁。
三、页面级回调:不止首次进入
@Entry页面多了三个回调:- @Entry
- @Component
- struct MyPage {
- aboutToAppear() { /* 首次创建 */ }
- onPageShow() {
- // 每次页面显示都触发,包括从后台切回来
- console.info('页面显示');
- this.refreshData();
- }
- onPageHide() {
- console.info('页面隐藏');
- }
- onBackPress(): boolean {
- // 返回true表示自己处理返回键,不再向上传递
- if (this.hasUnsavedData) {
- this.showSaveDialog();
- return true;
- }
- return false;
- }
- build() { /* ... */ }
- }
复制代码 常见误区:onPageShow不仅在首次进入时调用,每次从后台切回来也会触发。如果只想做一次初始化,应放在aboutToAppear中,否则重复执行刷新逻辑可能导致性能浪费或状态错乱。
四、UIAbility生命周期:核心回调顺序
UIAbility是Stage模型的核心,其回调顺序直接影响应用行为:- export default class MyAbility extends UIAbility {
- onCreate(want, launchParam) {
- hilog.info(0xF811, 'MyAbility', 'onCreate');
- // 非UI初始化:数据库连接、全局配置等
- }
- onWindowStageCreate(windowStage) {
- hilog.info(0xF811, 'MyAbility', 'onWindowStageCreate');
- // 窗口创建后加载页面
- windowStage.loadContent('pages/Index', (err) => {
- if (err.code) hilog.error(0xF811, 'MyAbility', '加载失败');
- });
- }
- onForeground() {
- hilog.info(0xF811, 'MyAbility', 'onForeground');
- // 申请资源:定位、恢复动画等
- }
- onBackground() {
- hilog.info(0xF811, 'MyAbility', 'onBackground');
- // 释放资源:关闭定位、暂停动画等
- }
- onWindowStageDestroy() {
- hilog.info(0xF811, 'MyAbility', 'onWindowStageDestroy');
- // 取消窗口事件订阅
- }
- onDestroy() {
- hilog.info(0xF811, 'MyAbility', 'onDestroy');
- // 保存数据、释放全局资源
- }
- }
复制代码 关键点:
1. onCreate先于onWindowStageCreate执行。在onCreate中不能操作UI(如loadContent或弹窗),否则会崩溃。
2. onForeground和onBackground不是严格“对称”。onForeground触发时UI可能还未完全就绪,不能执行startAbility等依赖前台状态的操作;onBackground触发时已进入后台,可以安全清理资源。
3. API 20后新增了onWillForeground、onDidForeground、onWillBackground、onDidBackground四个细粒度回调,可用于启动耗时打点等场景。
五、全局监听:ApplicationStateChangeCallback与appManager
如果需要在非UIAbility的代码(如工具类、Service)中感知前后台切换,可以使用ApplicationStateChangeCallback:- let applicationStateChangeCallback = {
- onApplicationForeground() {
- console.info('进程即将进入前台');
- // 注意:此时还不能执行依赖前台状态的操作
- },
- onApplicationBackground() {
- console.info('进程已进入后台');
- }
- };
- let applicationContext = this.context.getApplicationContext();
- try {
- applicationContext.on('applicationStateChange', applicationStateChangeCallback);
- } catch (e) {
- console.error('注册失败');
- }
复制代码 注意:该监听的是当前进程的前后台切换,不是整个应用。多进程应用每个进程需单独注册。另外,onApplicationForeground回调触发时进程“即将”进入前台,仍需等待UIAbility的onForeground执行后才能做需要前台状态的操作。
如果需要监听其他应用的启动/退出(如做应用管理器),使用appManager:- import { appManager } from '@kit.AbilityKit';
- let observer = {
- onAppStarted(data) { console.info('应用启动', data.bundleName); },
- onAppStopped(data) { console.info('应用退出', data.bundleName); },
- onForegroundApplicationChanged(data) { console.info('前台应用变化', JSON.stringify(data)); }
- };
- try {
- let id = appManager.on('applicationState', observer);
- } catch (e) {
- console.error('注册失败', e.code, e.message);
- }
复制代码 需要在module.json5中声明权限ohos.permission.RUNNING_STATE_OBSERVER,否则回调不会触发。
六、踩坑记录
坑一:onDestroy不一定会被调用
官方文档明确:onDestroy仅在UIAbility正常退出时触发,低内存终止进程等异常情况不会执行。因此关键数据切不可依赖onDestroy保存,正确做法是在onBackground中落盘。- // 错误
- onDestroy() { this.saveUserData(); } // 系统杀进程时不执行
- // 正确
- onBackground() { this.saveUserData(); this.releaseMemoryCache(); }
复制代码 临时缓存可以放在onDestroy,但重要数据必须更早保存。API 10新增的onPrepareToTerminate回调在系统准备终止进程前会触发,但低内存强杀仍然走不到,所以还是以onBackground为准。
坑二:applicationStateChange的回调时机与预期不同
onApplicationForeground触发时进程“即将”进入前台,此时startAbility等依赖前台状态的操作无法执行。必须等到UIAbility的onForeground执行完毕。
坑三:WindowStage的SHOWN事件与UIAbility的onForeground顺序
WindowStageEventType.SHOWN表示窗口显示,发生在UIAbility.onForeground之前。在单Ability应用中两者看似同时,但多Ability或弹窗场景下顺序不同:SHOWN先,onForeground后。如果操作依赖UI布局,应放到页面的onPageShow中(onPageShow在onForeground之后触发)。
七、启动模式与onNewWant
UIAbility有三种启动模式:
- singleton(默认):只有一个实例,重复启动触发onNewWant。
- multiton:每次启动创建新实例,走完整生命周期。
- specified:根据标识决定复用或新建。
当应用已存在(非首次启动)再次被拉起时,不会走onCreate,而是走onNewWant。典型场景:通知栏点击跳转。如果未处理onNewWant,第二次点击时页面不会刷新。正确做法:- onNewWant(want, launchParam) {
- console.info('收到新的Want', want.abilityName);
- // 刷新页面逻辑
- }
复制代码
八、调试技巧
在每个生命周期回调中使用hilog输出标签,配合DevEco Studio的Log面板或命令行hilog -T LifecycleDemo查看调用顺序:- const TAG = 'LifecycleDemo';
- onCreate() { hilog.info(0xF811, TAG, 'Ability onCreate'); }
- onWindowStageCreate() { hilog.info(0xF811, TAG, 'Ability onWindowStageCreate'); }
- onForeground() { hilog.info(0xF811, TAG, 'Ability onForeground'); }
- // 页面内
- aboutToAppear() { console.info('Page aboutToAppear'); }
- onPageShow() { console.info('Page onPageShow'); }
复制代码 通过日志验证顺序:应用冷启动到前台时,aboutToAppear -> build -> onPageShow -> onCreate -> onWindowStageCreate -> onForeground;后台切回前台时,onPageShow -> onForeground;前后台切换时,onPageHide -> onBackground;正常退出时,aboutToDisappear -> onWindowStageDestroy -> onDestroy。
九、总结原则
资源申请和释放要成对出现,申请越晚,释放越早。例如定位在onForeground申请,在onBackground释放;数据库在onCreate初始化,在onDestroy关闭。注意使用applicationContext.on('applicationStateChange')后必须在onDestroy中调用off()反注册,否则内存泄漏。
参考官方文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/application-lifecycle(API 12后新增的onWillForeground等回调可查阅最新文档)。掌握这些生命周期细节,能避免大量运行时Bug,让应用状态管理更稳定。 |