HarmonyOS应用开发实战:猫猫大作战-`onForeground` 和 `onBackground` 的触发时机、与页面生命周期 `onPageSh
前言
在移动应用开发中,前后台切换是最高频的系统事件之一——用户按下 Home 键、接听电话、拉下通知栏、切换应用,都会触发前台/后台状态变化。对于游戏应用而言,前后台切换的正确处理至关重要:切后台时需要暂停游戏、保存状态、释放资源;回到前台时需要恢复画面、继续计时。
本文以「猫猫大作战」的EntryAbility为锚点,讲解onForeground和onBackground的触发时机、与页面生命周期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 | 回到前台 |
| 来电/通知栏 | onBackground→onForeground | 短暂进出后台 |
| 切换应用 | onBackground | 切换到其他应用 |
| 锁屏/解锁 | onBackground→onForeground | 锁屏时进入后台 |
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/onBackground | onPageShow/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 设计对比
| 机制 | 触发条件 | 实现位置 | 覆盖场景 |
|---|---|---|---|
| onBackground | Ability 进入后台 | 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
| 区别 | onForeground | onPageShow |
|---|---|---|
| 触发者 | EntryAbility | Index 页面 |
| 触发时机 | 应用切到前台 | 页面变为可见 |
| 导航切换 | 不触发 | ✅ 触发 |
| 适合用途 | 系统级(权限申请) | 页面级(数据刷新) |
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/onBackground和windowStageEvent的对应关系:
| 时序 | Ability 回调 | WindowStage 事件 | 说明 |
|---|---|---|---|
| ① | onForeground | SHOWN → ACTIVE → RESUMED | 前台→可交互 |
| ② | — | INACTIVE | 失焦 |
| ③ | — | PAUSED | 前台不可交互 |
| ④ | onBackground | HIDDEN | 进入后台 |
// 完整的前后台感知方案 export default class EntryAbility extends UIAbility { onForeground(): void { // Ability 级通知 AppStorage.setOrCreate('appInForeground', true); } onBackground(): void { AppStorage.setOrCreate('appInForeground', false); } }九、总结
onForeground和onBackground是 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 配置