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/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.getboolean(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.getboolean(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.getboolean(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.getSavedGameState(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/onBackground和windowStageEvent的对应关系时序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); } }九、总结onForeground和onBackground是 Ability 级的前后台回调配合页面级的onPageShow/onPageHide可以构建从系统层到页面层的多层前后台处理机制。核心要点onForeground在 Ability 进入前台时触发onBackground进入后台时触发onPageShow/onPageHide在页面级别感知显隐导航切换也会触发游戏场景推荐三层保险Ability 通知 onPageHide 暂停 aboutToDisappear 兜底切后台时必须暂停定时器和动画回到前台时恢复onPageHide 中禁止耗时操作仅做状态标记下一篇预告第 76 篇将深入module.json5的完整配置——模块参数、Ability 注册、权限声明与多设备适配。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源UIAbility 生命周期官方文档自定义组件生命周期aboutToAppear/pageShowApplicationContext 监听 Ability 生命周期AppStorage 全局存储第 65 篇aboutToDisappear 资源释放开源鸿蒙跨平台社区第 76 篇module.json5 配置