HarmonyOS应用开发实战:猫猫大作战-`onForeground` 和 `onBackground` 的触发时机、与页面生命周期 `onPageSh

HarmonyOS应用开发实战:猫猫大作战-`onForeground` 和 `onBackground` 的触发时机、与页面生命周期 `onPageSh

前言

在移动应用开发中,前后台切换是最高频的系统事件之一——用户按下 Home 键、接听电话、拉下通知栏、切换应用,都会触发前台/后台状态变化。对于游戏应用而言,前后台切换的正确处理至关重要:切后台时需要暂停游戏、保存状态、释放资源;回到前台时需要恢复画面、继续计时。

本文以「猫猫大作战」的EntryAbility为锚点,讲解onForegroundonBackground的触发时机、与页面生命周期onPageShow/onPageHide的配合、以及前后台切换时的游戏暂停/恢复完整实现。

提示:本系列不讲 ArkTS 基础语法与环境搭建,假设你已跟完第 1–74 篇。本篇是阶段三第 75 篇。

一、onForeground/onBackground 回调

1.1 回调签名

export default class EntryAbility extends UIAbility { onForeground(): void { // Ability 的 UI 即将变为可见 } onBackground(): void { // Ability 的 UI 已经完全不可见 } }

1.2 触发场景

场景触发的回调说明
应用冷启动onForeground首次进入前台
按 Home 键onBackground进入后台
从最近任务切回onForeground回到前台
来电/通知栏onBackgroundonForeground短暂进出后台
切换应用onBackground切换到其他应用
锁屏/解锁onBackgroundonForeground锁屏时进入后台

1.3 猫猫大作战中的前后台处理

import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit'; import { window } from '@kit.ArkUI'; import { hilog } from '@kit.PerformanceAnalysisKit'; export default class EntryAbility extends UIAbility { onForeground(): void { hilog.info(0xFF00, 'EntryAbility', 'onForeground — 回到前台'); // 通知游戏页面恢复运行 AppStorage.setOrCreate('appForeground', true); } onBackground(): void { hilog.info(0xFF00, 'EntryAbility', 'onBackground — 进入后台'); // 通知游戏页面暂停运行 AppStorage.setOrCreate('appBackground', true); } }

二、前后台链路完整时序

2.1 冷启动 → 后台 → 前台

冷启动(首次): onCreate → onWindowStageCreate → loadContent → Index.aboutToAppear → Index.build → Index.onDidBuild → onForeground → Index.onPageShow ← 页面可见 按 Home 键: → onBackground → Index.onPageHide ← 页面不可见 从最近任务回到前台: → onForeground → Index.onPageShow ← 页面重新可见

2.2 Ability 级 vs 页面级的对应关系

维度Ability 级页面级
回调onForeground/onBackgroundonPageShow/onPageHide
作用域整个应用单页面
触发次数一次(前后台切换)每次页面显示/隐藏
跨页面跳转不触发✅ 触发 onPageHide/onPageShow
适合用途系统资源申请/释放页面数据刷新/暂停

三、项目实战:游戏暂停与恢复

3.1 数据流设计

EntryAbility Index (页面) │ │ │ onBackground() │ ├── AppStorage.set('bg',true)──► aboutToDisappear 不触发 │ │ onPageHide() 触发 │ │ → clearTimers() │ │ → gameState = PAUSED │ │ │ onForeground() │ ├── AppStorage.set('fg',true)──► onPageShow() 触发 │ → 恢复定时器 │ → 继续游戏

3.2 Index.ets 中的处理

@Entry @Component struct Index { @State gameState: GameState = GameState.IDLE; @State score: number = 0; // ... 其他状态变量 // 页面显示时回调(每次可见都触发) onPageShow() { hilog.info(0xFF00, 'Index', 'onPageShow — 页面可见'); // 检查是否从后台恢复 const wasBackground = AppStorage.get<boolean>('appForeground'); if (wasBackground && this.gameState === GameState.PAUSED) { // 从后台回来且之前被暂停 → 恢复游戏 this.resumeGame(); AppStorage.setOrCreate('appForeground', false); } } // 页面隐藏时回调(每次不可见都触发) onPageHide() { hilog.info(0xFF00, 'Index', 'onPageHide — 页面不可见'); // 如果正在游戏 → 自动暂停 if (this.gameState === GameState.PLAYING) { this.pauseGame(); } // 清除后台标志 AppStorage.setOrCreate('appForeground', false); AppStorage.setOrCreate('appBackground', false); } // 暂停游戏 pauseGame() { if (this.gameState === GameState.PLAYING) { this.gameState = GameState.PAUSED; this.clearTimers(); hilog.info(0xFF00, 'Index', '游戏已暂停'); } } // 恢复游戏 resumeGame() { if (this.gameState === GameState.PAUSED) { // 重新启动定时器 this.startGameTimers(); this.gameState = GameState.PLAYING; hilog.info(0xFF00, 'Index', '游戏已恢复'); } } // 清空定时器 clearTimers() { // ... 同第 65 篇实现 } // 启动定时器 startGameTimers() { this.gameLoopTimer = setInterval(() => { if (this.gameState !== GameState.PLAYING) return; this.cats = this.gameEngine.updateCats(); this.score = this.gameEngine.getScore(); }, 100); this.spawnTimer = setInterval(() => { if (this.gameState !== GameState.PLAYING) return; this.gameEngine.autoSpawnCat(); this.cats = this.gameEngine.getAllCats(); }, 2000); this.timeTimer = setInterval(() => { if (this.gameState === GameState.PLAYING) { this.gameTime++; } }, 1000); } }

3.3 onPageShow/onPageHide 完整示例

@Entry @Component struct Index { @State gameState: GameState = GameState.IDLE; @State score: number = 0; @State cats: Cat[] = []; @State gameTime: number = 0; private gameEngine: GameEngine = new GameEngine(); private gameLoopTimer: number = -1; private spawnTimer: number = -1; private timeTimer: number = -1; // 页面生命周期 — 每次显示触发 onPageShow() { const resumed = AppStorage.get<boolean>('appForeground') ?? false; hilog.info(0xFF00, 'Index', `onPageShow: gameState=${this.gameState}, resumed=${resumed}`); } // 页面生命周期 — 每次隐藏触发 onPageHide() { hilog.info(0xFF00, 'Index', `onPageHide: gameState=${this.gameState}`); if (this.gameState === GameState.PLAYING) { this.gameState = GameState.PAUSED; this.clearTimers(); } } aboutToDisappear() { this.clearTimers(); } }

四、三种暂停机制的对比

4.1 设计对比

机制触发条件实现位置覆盖场景
onBackgroundAbility 进入后台EntryAbility系统级强制暂停(来电等)
onPageHide页面不再可见Index.ets跳转页面、后台切换
aboutToDisappear页面销毁Index.ets退出页面兜底清理

4.2 三层保险策略

第一层:EntryAbility.onBackground() └── 通知所有页面暂停(安全性最高,但粒度粗) 第二层:Index.onPageHide() └── 页面级暂停(精准控制,建议主要在此处理) 第三层:Index.aboutToDisappear() └── 兜底清理(确保定时器不泄漏)
// 第一层:Ability 级 — 全局通知 export default class EntryAbility extends UIAbility { onBackground(): void { AppStorage.setOrCreate('systemPause', true); } onForeground(): void { AppStorage.setOrCreate('systemResume', true); } } // 第二层:页面级 — 精确控制 onPageHide() { this.pauseGame(); } onPageShow() { const sysResume = AppStorage.get<boolean>('systemResume'); if (sysResume && this.gameState === GameState.PAUSED) { this.resumeGame(); AppStorage.setOrCreate('systemResume', false); } } // 第三层:销毁级 — 兜底 aboutToDisappear() { this.clearTimers(); }

五、保存与恢复游戏状态

5.1 切后台时保存进度

onPageHide() { if (this.gameState === GameState.PLAYING) { this.gameState = GameState.PAUSED; this.clearTimers(); // 保存游戏快照到 AppStorage(恢复时使用) AppStorage.setOrCreate('savedGameState', { score: this.score, cats: this.gameEngine.getAllCats(), gameTime: this.gameTime, combo: this.gameEngine.getCombo() }); } }

5.2 回前台时恢复进度

onPageShow() { const savedState = AppStorage.get<SavedGameState>('savedGameState'); if (savedState && this.gameState === GameState.PAUSED) { // 恢复游戏快照 this.score = savedState.score; this.cats = savedState.cats; this.gameTime = savedState.gameTime; // 恢复引擎状态 this.gameEngine.restoreState(savedState); // 恢复定时器 this.startGameTimers(); this.gameState = GameState.PLAYING; } }

六、前后台切换的日志验证

// 在 DevEco Studio 日志中查看 const TAG = 'Lifecycle'; const DOMAIN = 0xFF00; // 冷启动 09:15:23.101 EntryAbility: Ability onCreate 09:15:23.156 EntryAbility: Ability onWindowStageCreate 09:15:23.289 Index: Index onDidBuild 09:15:23.302 EntryAbility: Ability onForeground 09:15:23.310 Index: onPageShow // 按 Home 键 09:16:12.101 EntryAbility: Ability onBackground 09:16:12.110 Index: onPageHide // 回到前台 09:17:05.234 EntryAbility: Ability onForeground 09:17:05.240 Index: onPageShow

七、常见踩坑

7.1 坑一:混淆 onForeground 与 onPageShow

区别onForegroundonPageShow
触发者EntryAbilityIndex 页面
触发时机应用切到前台页面变为可见
导航切换不触发✅ 触发
适合用途系统级(权限申请)页面级(数据刷新)

7.2 坑二:onPageHide 中做耗时操作

// 🚫 错误:onPageHide 中保存数据库 onPageHide() { await db.saveGameRecord(this.score); // ❌ onPageHide 不应有 await } // ✅ 正确:用异步任务或直接设置标志 onPageHide() { AppStorage.setOrCreate('needSaveScore', this.score); }

7.3 坑三:前后台切换时动画状态未恢复

// 🚫 切后台时不暂停动画 → 回前台时动画状态错乱 // ✅ 正确做法:在 onPageHide 暂停所有动画 onPageHide() { // 暂停 animateTo 动画 this.getUIContext()?.animateTo({ duration: 0 }, () => { this.animationProgress = this.currentProgress; }); }

八、前后台切换与 WindowStage 事件

onForeground/onBackgroundwindowStageEvent的对应关系:

时序Ability 回调WindowStage 事件说明
onForegroundSHOWN → ACTIVE → RESUMED前台→可交互
INACTIVE失焦
PAUSED前台不可交互
onBackgroundHIDDEN进入后台
// 完整的前后台感知方案 export default class EntryAbility extends UIAbility { onForeground(): void { // Ability 级通知 AppStorage.setOrCreate('appInForeground', true); } onBackground(): void { AppStorage.setOrCreate('appInForeground', false); } }

九、总结

onForegroundonBackground是 Ability 级的前后台回调,配合页面级的onPageShow/onPageHide,可以构建从系统层到页面层的多层前后台处理机制。

核心要点

  • onForeground在 Ability 进入前台时触发,onBackground进入后台时触发
  • onPageShow/onPageHide在页面级别感知显隐,导航切换也会触发
  • 游戏场景推荐三层保险:Ability 通知 + onPageHide 暂停 + aboutToDisappear 兜底
  • 切后台时必须暂停定时器和动画,回到前台时恢复
  • onPageHide 中禁止耗时操作,仅做状态标记

下一篇预告:第 76 篇将深入module.json5的完整配置——模块参数、Ability 注册、权限声明与多设备适配。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

  • UIAbility 生命周期官方文档
  • 自定义组件生命周期(aboutToAppear/pageShow)
  • ApplicationContext 监听 Ability 生命周期
  • AppStorage 全局存储
  • 第 65 篇:aboutToDisappear 资源释放
  • 开源鸿蒙跨平台社区
  • 第 76 篇:module.json5 配置