在游戏中有效地管理内存

在 Android 平台上,系统会尝试尽可能地使用系统内存 (RAM),并在需要时执行各种内存优化来释放空间。这些优化可能会给您的游戏带来负面影响,因为它们可能会减慢游戏运行速度或使游戏终止。如需详细了解这些优化,请参阅主题进程间的内存分配

本页介绍了您可以采取哪些措施来避免内存不足对您的游戏造成影响。

响应 onTrimMemory()

内存不足,并且您的应用可能会被终止时,系统会使用 onTrimMemory() 来通知您的应用。对于许多内存不足的情况,这是您的应用会收到的唯一警告。相对于低内存终止守护进程 (LMK) 来说,此回调的延迟时间比较长,因此快速响应此回调至关重要。

为了响应此回调,请降低内存分配速度,以及所分配内存的数量和大小。onTrimMemory() 会传递一个表示严重程度的常量,但您应响应第一条警告,因为内存分配速度可能比 onTrimMemory() 的响应速度更快。

Kotlin

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
    override fun onTrimMemory(level: Int) {
        when (level) {
            ComponentCallbacks2.TRIM_MEMORY_MODERATE,
                ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
                ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> // Respond to low memory condition
            else -> Unit
        }
    }
}

Java

public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
    public void onTrimMemory(int level) {
        switch (level) {
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
              // Respond to low memory condition
                break;
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
              // Respond to low memory condition
                break;
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
              // Respond to low memory condition
                break;
            default:
                break;

C#

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

class LowMemoryTrigger : MonoBehaviour
{
    private void Start()
    {
        Application.lowMemory += OnLowMemory;
    }
    private void OnLowMemory()
    {
        // Respond to low memory condition (e.g., Resources.UnloadUnusedAssets())
    }
}

使用 Memory Advice API Beta 版

Memory Advice API 是作为 onTrimMemory 的替代方案开发的,在预测即将发生的 LMK 方面具有更高的召回率和精确度。为了实现这一点,该 API 会估算正在使用的内存资源量,并在超出特定阈值时通知应用。该 API 还可以直接向应用报告估算的内存使用量百分比。您可以使用 Memory Advice API 替代 onTrimMemory 事件来管理内存。

如需使用 Memory Advice API,请参阅入门指南

保守地分配内存预算

保守地分配内存预算,以避免耗尽内存。下面列出了一些需要注意的事项:

  • 物理 RAM 的大小:游戏通常会占用设备上物理 RAM 量的 ¼ 到 ½。
  • zRAM 大小上限:zRAM 越大,可供游戏分配的内存可能就越多。此上限可能因设备而异;如需获取此值,请查看 /proc/meminfo 中的 SwapTotal
  • 操作系统的内存用量:设备为系统进程分配的 RAM 越多,留给游戏的内存就越少。内存不足时,系统会优先终止游戏进程,之后如果内存仍然不足,系统才会终止系统进程。
  • 已安装的应用的内存用量:在安装了许多应用的设备上测试您的游戏。社交媒体和聊天应用需要持续运行,并会影响可用内存量。

如果您无法保守地分配内存预算,可以采取一种更灵活的方法。系统内存不足时,请减少游戏使用的内存量。例如,在响应 onTrimMemory() 时为游戏分配分辨率较低的纹理或存储较少的着色器。这种动态的内存分配方式需要开发者做更多工作,尤其是在游戏设计阶段。

避免抖动

可用内存不足,但不至于导致系统终止游戏时,就会出现抖动。在这种情况下,由于 kswapd 已回收游戏仍需要的页面,因此游戏会尝试从内存中重新加载这些页面。但由于空间不足,这些页面会被不断换出(持续发生交换)。System Tracing 会将这种情况报告为一个线程,kswapd 在该线程中持续运行。

抖动的一个特征是帧时间较长,可能为一秒或更长时间。减少游戏的内存占用量可以解决这种情况。

使用可用工具

Android 中包含一系列旨在帮助您了解系统如何管理内存的工具。

Meminfo

此工具用于收集内存统计信息,以便显示已分配的 PSS 内存量,以及使用这些内存的进程所属的类别。

可以通过以下任一方式输出 meminfo 统计信息:

  • 使用命令 adb shell dumpsys meminfo package-name
  • 使用 Android Debug API 中的 MemoryInfo 调用。

PrivateDirty 统计信息中会显示进程内无法分页到磁盘并且不能与任何其他进程共用的 RAM 量。相应进程被终止后,大部分此类内存将可供系统使用。

内存跟踪点

内存跟踪点会跟踪游戏正在使用的 RSS 内存量。计算 RSS 内存用量要比计算 PSS 内存用量快得多。由于计算速度较快,因此 RSS 数据会更精细地显示内存大小的变化,从而有助于更准确地计算峰值内存用量。因此,您会更容易注意到可能导致游戏耗尽内存的峰值。

Perfetto 和长跟踪记录

Perfetto 是一套工具,用于收集设备上的性能和内存信息,并将其显示在基于网页的界面中。它支持任意长度的跟踪记录,因此您可以查看 RSS 的持续变化情况。您还可以对该工具生成的数据执行 SQL 查询,以进行离线处理。通过 System Tracing 应用启用长跟踪记录。确保为跟踪记录启用 memory:Memory 类别。

heapprofd

heapprofd 是 Perfetto 中的一种内存跟踪工具。此工具可使用 malloc 显示内存分配位置,从而有助于您发现内存泄漏。可以使用 Python 脚本来启动 heapprofd,而且由于该工具的开销较低,因此它不会像 Malloc 调试等其他工具一样影响性能。

bugreport

bugreport 是一个日志记录工具,用于确定游戏是否因为内存耗尽而崩溃。此工具的输出远比使用 logcat 得到的结果更详尽。它对于内存调试来说很有用,因为它可以显示游戏是因为内存耗尽而崩溃的,还是被 LMK 终止的。

如需了解更多信息,请参阅获取和阅读错误报告