Android平台游戏如何检测打开通知栏等失焦场景
Android 7.0(API 24)之后,下拉展开通知栏/快捷设置面板时,前台Activity不会触发onPause回调——此时Activity仍处于Resumed生命周期状态,只是被系统级窗口覆盖,这就是你之前用onPause做失焦检测失效的根本原因。《狂野飙车9》这类商用游戏的暂停逻辑也没有依赖onPause实现,具体检测方案可以根据你的项目支持的最低版本选择:
实现方案
1. 原生安卓项目通用实现
按系统版本适配,覆盖全场景无隐藏API调用:
- Android 11(API 30)及以上:通过WindowInsets监听状态栏状态,判断逻辑最准确
// 给游戏界面的根View绑定insets监听 gameRootView.setOnApplyWindowInsetsListener { view, insets -> val statusBarVisibleHeight = insets.getInsets(WindowInsets.Type.statusBars()).top // 阈值按设备密度转换为px,普通常驻状态栏高度一般为24~48dp,超过80dp即可判定为下拉展开通知面板 val isNotificationShadeOpened = statusBarVisibleHeight > TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 80f, view.resources.displayMetrics ).toInt() if (isNotificationShadeOpened) { triggerGamePause() // 执行你的游戏暂停逻辑 } insets } - Android 7.0(API 24)~ Android 10(API 29):结合系统UI可见性变化+窗口可见区域判断
gameRootView.setOnSystemUiVisibilityChangeListener { visibility -> val isInFullScreenMode = visibility and View.SYSTEM_UI_FLAG_FULLSCREEN != 0 if (!isInFullScreenMode) { val visibleRect = Rect() gameRootView.getWindowVisibleDisplayFrame(visibleRect) // 排除软键盘弹出场景:软键盘只会改变窗口底部边距,通知栏下拉会让顶部边距大幅增加 val topOffset = visibleRect.top val threshold = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 80f, gameRootView.resources.displayMetrics ).toInt() if (topOffset > threshold) { triggerGamePause() } } } - Android 7.0以下版本:下拉通知栏会正常触发
onPause,原有逻辑可以直接覆盖这个版本区间。
2. 误判场景规避
以上逻辑需要额外排除几个特殊场景,避免错误触发暂停:
- 软键盘弹出:软键盘弹出仅会改变窗口底部inset/边距,顶部边距不会达到阈值,只要同时校验上下边距即可区分
- 系统小弹窗(权限申请、音量调节、电源菜单):这类弹窗不会让状态栏可见高度达到80dp的阈值,不会触发误判
- 分屏/自由小窗模式:提前通过
Activity.isInMultiWindowMode判断应用是否处于多窗口模式,多窗口下的尺寸变化单独处理即可
3. 游戏引擎快速实现
如果使用商业游戏引擎开发,不需要自行编写原生适配逻辑,引擎已经封装了对应回调:
- Unity:直接监听
MonoBehaviour.OnApplicationFocus(bool hasFocus),下拉通知栏时会回调hasFocus = false,直接绑定暂停逻辑即可 - Unreal Engine:监听
FCoreDelegates::ApplicationWillDeactivateDelegate委托,通知栏展开时该委托会正常触发,直接绑定暂停逻辑即可
不要使用反射调用系统隐藏API
StatusBarManager.isNotificationShadeExpanded()实现检测:不同厂商ROM对该隐藏API的实现差异极大,同时反射调用隐藏API会提高应用上架被拒的风险。
内容的提问来源于stack exchange,提问作者Yokowasis
相关产品推荐
相关产品推荐

