如何检测Android锁屏事件并降低渲染帧率省电
解读
面试官真正想考察的是:
- 你对 Android 生命周期 与 Unity 渲染循环 的打通能力;
- 能否在 零 Java 反射 或 低反射 的前提下,用 C#→Java 双向通信 准确监听锁屏;
- 锁屏后如何把 Unity 的 实时渲染 降到 极低帧率(甚至 0 帧)而不触发 ANR,且恢复时能瞬间回到正常帧率;
- 是否了解国内 厂商 ROM 差异(小米、华为、OPPO 对锁屏广播的裁剪)及 Android 13+ 权限变更;
- 能否给出 可热更、无侵入 的工程级方案,方便策划后期通过表格开关。
知识点
-
Android 锁屏事件来源
Intent.ACTION_SCREEN_OFF/ACTION_SCREEN_ON(最可靠,无需额外权限);ACTION_USER_PRESENT(解锁后触发,可辅助校验);PowerManager.isInteractive()(Android 5.0+,用于双保险判断);- 国内 ROM 特殊广播:小米
miui.intent.SCREEN_OFF,华为hw.intent.action.SCREEN_OFF,需白名单注册。
-
Unity→Java 通信
- UnityPlayer.UnitySendMessage(Java→C#);
- AndroidJavaClass/AndroidJavaObject(C#→Java);
- jar 打包成 aar,放
Plugins/Android,避免反射带来的 il2cpp 裁剪 风险; - Android 13 后台启动限制:锁屏广播接收器必须采用 动态注册(
registerReceiver),静态注册在 targetSdk=33 时会被系统忽略。
-
渲染帧率控制
Application.targetFrameRate(仅建议值,实际受平台 vsync 限制);OnDemandRendering.renderFrameInterval(Unity 2019.3+,真正跳过渲染而不降逻辑帧,省电效果最佳);QualitySettings.vSyncCount = 0配合targetFrameRate = 5可把 GPU 占用压到 <3%;- XR 项目 需额外调用
XRSettings.showDeviceView = false关闭 HMD 渲染,否则单眼 90 FPS 依然耗电。
-
恢复策略
- 收到
ACTION_SCREEN_ON后 延迟 200 ms 再恢复帧率,避免国内 ROM 锁屏动画未结束导致闪帧; - 热更字段 暴露
LockScreenFps与NormalFps,方便线上通过表格动态调优; - IL2CPP 裁剪 场景下,需在
link.xml中保留UnityEngine.AndroidJNIModule及自定义广播接收器。
- 收到
答案
- 在
Plugins/Android新建LockScreenReceiver.java:
package com.company.product;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import com.unity3d.player.UnityPlayer;
public class LockScreenReceiver extends BroadcastReceiver {
private static final String GAME_OBJ = "AndroidMgr"; // Unity 场景内挂的 GameObject
private static final String OFF_METHOD = "OnScreenOff";
private static final String ON_METHOD = "OnScreenOn";
public static void register(Context ctx){
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
ctx.registerReceiver(new LockScreenReceiver(), filter);
}
@Override
public void onReceive(Context ctx, Intent intent){
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())){
UnityPlayer.UnitySendMessage(GAME_OBJ, OFF_METHOD, "");
}else if(Intent.ACTION_SCREEN_ON.equals(intent.getAction())){
UnityPlayer.UnitySendMessage(GAME_OBJ, ON_METHOD, "");
}
}
}
- 在 Unity 侧
AndroidMgr.cs:
public class AndroidMgr : MonoBehaviour
{
void Start()
{
using(var jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
var activity = jc.GetStatic<AndroidJavaObject>("currentActivity");
using(var receiver = new AndroidJavaClass("com.company.product.LockScreenReceiver"))
{
receiver.CallStatic("register", activity);
}
}
}
void OnScreenOff()
{
OnDemandRendering.renderFrameInterval = 12; // 60→5 FPS
QualitySettings.vSyncCount = 0;
Application.targetFrameRate = 5;
// 如果是 XR
if(XRSettings.loadedDeviceName == "Oculus")
XRSettings.showDeviceView = false;
}
void OnScreenOn()
{
OnDemandRendering.renderFrameInterval = 1;
QualitySettings.vSyncCount = 1;
Application.targetFrameRate = 60;
if(XRSettings.loadedDeviceName == "Oculus")
XRSettings.showDeviceView = true;
}
}
- 打包验证:
- 使用 Android Studio Logcat 过滤
Unity标签,确认锁屏瞬间打印OnScreenOff; - 用 Unity Profiler 观察 GPU Usage 从 35% 降到 2%,电池电流下降 120 mA 即达标;
- 在 小米 13(MIUI14) 与 华为 Mate50(HarmonyOS 3) 双机验证无 ANR,恢复点击无黑帧。
- 使用 Android Studio Logcat 过滤
拓展思考
- 双端统一:iOS 端可通过
UIApplication.Notifications.UIApplicationWillResignActive与WillEnterForeground做同样降帧,代码层封装成 PlatformAdapter,保证业务层无宏定义。 - 后台下载:若锁屏时仍有 资源热更 任务,需把渲染帧率与 网络线程 分离,避免
OnDemandRendering拖慢UnityWebRequest的 TLS 握手。 - Android 14 前瞻:谷歌计划限制 后台应用注册前台广播,需提前把接收器迁移到 前台 Service 或使用 WorkManager 监听
USER_UNLOCKED,否则 targetSdk=34 后收不到广播。 - 性能黑榜:部分国内 SDK(语音、推送)会在锁屏后 强行唤醒 绘制浮窗,需在
AndroidManifest.xml中 移除它们的悬浮窗权限,否则省电策略失效。 - 热更兼容:若项目使用 Lua 热更,把
OnScreenOff/OnScreenOn注册成 Lua 函数,策划可通过表格配置 锁屏时是否暂停计时器、关闭粒子,实现 零包体 调优。