如何检测Android锁屏事件并降低渲染帧率省电

解读

面试官真正想考察的是:

  1. 你对 Android 生命周期Unity 渲染循环 的打通能力;
  2. 能否在 零 Java 反射低反射 的前提下,用 C#→Java 双向通信 准确监听锁屏;
  3. 锁屏后如何把 Unity 的 实时渲染 降到 极低帧率(甚至 0 帧)而不触发 ANR,且恢复时能瞬间回到正常帧率;
  4. 是否了解国内 厂商 ROM 差异(小米、华为、OPPO 对锁屏广播的裁剪)及 Android 13+ 权限变更
  5. 能否给出 可热更无侵入 的工程级方案,方便策划后期通过表格开关。

知识点

  1. 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,需白名单注册。
  2. Unity→Java 通信

    • UnityPlayer.UnitySendMessage(Java→C#);
    • AndroidJavaClass/AndroidJavaObject(C#→Java);
    • jar 打包成 aar,放 Plugins/Android,避免反射带来的 il2cpp 裁剪 风险;
    • Android 13 后台启动限制:锁屏广播接收器必须采用 动态注册registerReceiver),静态注册在 targetSdk=33 时会被系统忽略。
  3. 渲染帧率控制

    • Application.targetFrameRate(仅建议值,实际受平台 vsync 限制);
    • OnDemandRendering.renderFrameInterval(Unity 2019.3+,真正跳过渲染而不降逻辑帧,省电效果最佳);
    • QualitySettings.vSyncCount = 0 配合 targetFrameRate = 5 可把 GPU 占用压到 <3%
    • XR 项目 需额外调用 XRSettings.showDeviceView = false 关闭 HMD 渲染,否则单眼 90 FPS 依然耗电。
  4. 恢复策略

    • 收到 ACTION_SCREEN_ON延迟 200 ms 再恢复帧率,避免国内 ROM 锁屏动画未结束导致闪帧;
    • 热更字段 暴露 LockScreenFpsNormalFps,方便线上通过表格动态调优;
    • IL2CPP 裁剪 场景下,需在 link.xml 中保留 UnityEngine.AndroidJNIModule 及自定义广播接收器。

答案

  1. 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, "");
        }
    }
}
  1. 在 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;
    }
}
  1. 打包验证:
    • 使用 Android Studio Logcat 过滤 Unity 标签,确认锁屏瞬间打印 OnScreenOff
    • Unity Profiler 观察 GPU Usage 从 35% 降到 2%,电池电流下降 120 mA 即达标;
    • 小米 13(MIUI14)华为 Mate50(HarmonyOS 3) 双机验证无 ANR,恢复点击无黑帧。

拓展思考

  1. 双端统一:iOS 端可通过 UIApplication.Notifications.UIApplicationWillResignActiveWillEnterForeground 做同样降帧,代码层封装成 PlatformAdapter,保证业务层无宏定义。
  2. 后台下载:若锁屏时仍有 资源热更 任务,需把渲染帧率与 网络线程 分离,避免 OnDemandRendering 拖慢 UnityWebRequest 的 TLS 握手。
  3. Android 14 前瞻:谷歌计划限制 后台应用注册前台广播,需提前把接收器迁移到 前台 Service 或使用 WorkManager 监听 USER_UNLOCKED,否则 targetSdk=34 后收不到广播。
  4. 性能黑榜:部分国内 SDK(语音、推送)会在锁屏后 强行唤醒 绘制浮窗,需在 AndroidManifest.xml移除它们的悬浮窗权限,否则省电策略失效。
  5. 热更兼容:若项目使用 Lua 热更,把 OnScreenOff/OnScreenOn 注册成 Lua 函数,策划可通过表格配置 锁屏时是否暂停计时器、关闭粒子,实现 零包体 调优。