OnApplicationPause与OnApplicationFocus的触发差异
解读
在国内 Unity 面试中,这两个生命周期钩子常被混为一谈。面试官真正想确认的是:
- 你是否亲手在真机(iOS/Android)上验证过它们的触发路径,而不是只看过文档;
- 能否准确区分“系统级挂起”与“应用级失去焦点”,并据此写出不丢档、不闪退、不耗流量的健壮逻辑;
- 对国产 ROM 深度定制行为(如 MIUI、EMUI、ColorOS 的激进省电策略)有没有踩坑经验。
答不出“国产机锁屏后 5 秒即杀后台”这类差异,基本会被判定为“没上线过项目”。
知识点
-
触发源头
- OnApplicationPause(bool pauseStatus) 由操作系统 AMS(ActivityManagerService) 回调,真正暂停了 Unity 的主线程和渲染线程;pauseStatus=true 时,CPU 被系统冻结,Time.realtimeSinceStartup 不再递增。
- OnApplicationFocus(bool hasFocus) 由** UnityPlayer 的窗口焦点变化** 回调,主线程仍在跑,只是用户看不到界面(例如下拉状态栏、分屏、权限弹窗)。
-
国内真机差异
- 锁屏:原生 AOSP 会先发 Focus=false,500 ms 后再 Pause=true;华为/小米部分机型 10 秒内直接杀进程,导致你可能收不到 Pause=true。
- Home 键退后台:原生顺序 Focus=false → Pause=true;OPPO 在 ColorOS 12 上会在 Pause=true 后 3 秒强制冻结 GPU 上下文,若此时 GL 资源未释放,再次启动会黑屏。
- 权限弹窗(相机/定位):只触发 Focus=false,不会触发 Pause;很多新人把“存档”写在 Pause 里,结果用户点个拍照权限就把存档丢了。
-
Unity 版本差异
- 2020 LTS 以前,WebGL 平台没有 Pause 概念,Pause 回调被强行映射为 PageVisibility API 的 hidden 事件;2021.2 以后才拆成独立宏。
- Editor 下按 Alt+Tab 只会触发 Focus,不会触发 Pause;若用 Editor 模拟逻辑,必须手动调用 UnityEditor.EditorApplication.pauseStateChanged 才能完整测试。
-
执行线程
两者都在主线程回调,但 Pause 回调时渲染线程已阻塞,因此不能在 OnApplicationPause 里调用 Graphics.Blit、RenderTexture.GetTemporary 等 GPU API,否则会直接崩溃。 -
时间片与帧率
Pause=true 后,Unity 不再调用 Update、FixedUpdate、协程,但yield return www 的 Ticker 仍由系统网络线程驱动;若依赖协程下载热更包,必须改写到自定义线程或 AndroidJavaProxy 回调,否则锁屏 5 分钟后网络被系统挂起,协程永远停在 99%。
答案
先给出结论再补场景:
“OnApplicationPause 表示应用生命周期被系统挂起,主线程冻结、GPU 上下文丢失;OnApplicationFocus 表示窗口焦点变化,主线程仍在运行。国内安卓 ROM 常在锁屏后跳过 Focus 直接 Pause,并在数秒内杀后台,因此存档、网络、热更状态机必须写在 Pause=true 里,而暂停音效、隐藏 UI 动画可放在 Focus=false 以兼顾权限弹窗场景。另需注意 Pause 回调里禁止任何 GPU 操作,并手动停止协程与 Timer,防止国产系统冻结后异常重启。”
拓展思考
- 双保险存档:在 Focus=false 时把内存数据写到循环队列缓存,Pause=true 时 fsync 到持久化文件,可解决“权限弹窗→用户直接杀进程”的丢档问题。
- 热更下载续传:利用 Android 的 DownloadManager.Service 把下载任务抛到系统进程,锁屏后仍由系统托管;回到游戏后在 Focus=true 里查询 DownloadManager 的 COLUMN_BYTES_DOWNLOADED_SO_FAR,避免 Unity 协程被冻结导致的 99% 假死。
- GPU 上下文保护:Pause=true 时把 RenderTexture.active 设为 null,并调用 GL.InvalidateState(),可显著降低 ColorOS 冻结 GPU 后再次启动的黑屏概率。
- 后台音频播放:如果产品需求要求锁屏后继续播放 BGM,需要把 AudioSource 迁移到 Android 前台 Service 并申请 media_button 事件监听,此时 Unity 的 Pause 已失去意义,需自定义 Java 层回调,通过 UnityPlayer.UnitySendMessage 把播放状态传回 C#。