如需有效优化游戏的内存占用,您必须先了解 Android 平台如何衡量内存,以及如何使用系统遥测、诊断 API 和分析工具。本指南详细介绍了如何根据新平台指南监控、捕获和分析游戏的内存分配。
了解 RSS 和交换指标
为了有效地分析和调试游戏的内存行为,您必须了解 Android 平台用于内存管理的确切技术指标。如需详细了解此遥测参数在实际应用中的处理和监控方式,请参阅 Android Vitals - 内存用量(匿名 RSS + 交换)文档。
1. 匿名 RSS (RssAnon)
常驻内存大小 (RSS) 用于衡量进程占用的内存中保存在设备物理 RAM 中的部分。RSS 分为文件支持的内存和匿名内存。Android 的内存指标严格来说只关注匿名 RSS:
- 包含内容:由游戏进程直接分配且未链接到存储空间中物理文件的内存页。这些页面包括 Java 或 Kotlin 堆、线程执行堆栈,以及至关重要的原生内存分配(例如自定义 C++ 引擎分配器,或使用原生 malloc 或 new 请求并被游戏逻辑弄脏的内存块)。如需详细了解此指标,请参阅进程内存 (RSS) 词典。
- 重要性:游戏引擎使用大量的原生内存池来处理物理、渲染和逻辑。由于这些池不受文件支持,因此它们完全存在于匿名 RSS 中,并构成了游戏的大部分物理占用空间。
2. 未压缩的交换空间 (VmSwap)
由于闪存磨损和延迟时间限制,Android 不支持传统的基于磁盘的交换空间。而是使用 zRAM(未压缩的交换):
- 包含的内容:当物理 RAM 压力增大时,内核的内存管理守护程序会压缩非活跃的匿名页面,并将它们移到物理 RAM (zRAM) 的专用未压缩部分。
- 指标计算:系统会根据未压缩的大小 (VmSwap) 跟踪此指标,以评估游戏的实际物理内存需求。如果游戏分配了内存,而系统将其交换到 zRAM,则该内存仍计入游戏的总内存占用量。
3. 进程状态
内存用量按 Android Vitals 中的进程状态细分。 对于游戏开发者而言,第三方 SDK 或游戏也可能会意外触发用户感知到的服务或后台服务。
- 包含的内容:前台、可感知服务、后台和缓存。
- 重要性:不同的进程状态对 Android OS 内存管理有不同的影响。如果任何第三方 SDK 无意中触发了后台任务,您可能不知道游戏正在以敏感的进程状态运行。使用
RunningAppProcessInfo监控游戏是否在后台运行。
应用编程接口 (API)
Android 提供了一些系统 API,可让游戏动态响应内存压力,并在运行时捕获详细的内存诊断信息。
响应内存缩减事件
系统会使用 onTrimMemory 通知您的应用生命周期事件,这些事件为您的应用提供了自愿减少内存用量的好机会,从而避免被低内存终止守护程序 (LMK) 终止,以便为其他应用释放内存。
如果系统在后台终止您的应用,用户在恢复应用时会遇到缓慢的冷启动。减少后台内存使用量有助于防止这些后台终止。
在响应 trim 事件时,释放不需要立即使用的大型可重建内存分配:
示例:响应
TRIM_MEMORY_UI_HIDDEN时,剪裁或清除缓存的位图(从本地存储空间解码)。
Kotlin
class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
override fun onTrimMemory(level: Int) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
Java
public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
public void onTrimMemory(int level) {
switch (level) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
}
ProfilingManager
ProfilingManager API 在 Android 15(API 级别 35)中引入,可让应用在运行时直接捕获以编程方式定义的快照(例如堆配置文件、系统轨迹和 Java 堆转储)。
开发者可以在特定场景手动触发捕获,也可以注册自动触发器(例如 TRIGGER_TYPE_ANOMALY),以便在游戏进程违反内存限制器阈值时自动触发捕获。不过,游戏开发者必须考虑现代游戏引擎中的关键限制:
注意:现代游戏引擎(例如 Unity 或 Unreal)通过使用带有 MAP_ANONYMOUS 标志的 mmap 从内核预分配大量虚拟内存块来管理执行性能。然后,引擎使用自定义子分配器(例如 Unity 的原生内存管理器或 Unreal 的 BinnedAllocators)在内部细分和分配内存块。
ApplicationExitInfo
如果游戏因违反单个进程内存限制而在后台终止或被终止,标准 Java 或原生崩溃转储机制(例如 Firebase Crashlytics)不会注册该事件。如需以编程方式查询和记录这些终止情况,开发者应在游戏启动时利用 ApplicationExitInfo API。
- 实现:在启动时,调用
ActivityManager.getHistoricalProcessExitReasons()以检索最近会话的退出原因。 - 主要内存退出原因:
REASON_LOW_MEMORY:表示相应进程已被系统的低内存终止守护程序 (LMK) 终止。当设备范围内的内存压力较高且操作系统必须回收 RAM 时,就会发生此终止。此退出原因表示游戏的后台占用空间过大,无法与其他应用共存。REASON_MEMORY_LIMITER(Android 17 [API 级别 37] 及更高版本):表示进程被专门终止,原因是其超出了平台内存限制器分配的 cgroup 内存限制 (RssAnon + VmSwap)。即使设备上剩余的物理内存充足,也可能会发生此终止情况,这表明直接违反了单个进程限制。
使用可用工具
在开发和 QA 期间使用以下平台工具来准确衡量游戏的内存用量。
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 类别。对于开发和测试中的自定义内存插桩,您还可以使用(Beta 版)heapprofd API。
在 Perfetto 中检查 RssAnon 和交换
如需检查游戏的匿名内存和 zRAM 交换影响,请在 ui.perfetto.dev 的基于网页的界面中加载轨迹文件,然后按照以下分析技巧操作,这些技巧专为深入的内存案例研究而设计(如需了解详情,请参阅 Perfetto 内存分析案例研究):
1. 直观呈现时间轴上的内存计数器
- 找到您的进程:在导航列表中,搜索游戏的软件包或进程名称。
- 展开轨道组:点击进程行以展开其线程轨道,然后找到名为“内存”的子组。
- 分析轨道:
- mem.rss.anon(匿名 RSS):此折线图显示了游戏非受管内存池占用的实时物理 RAM。在场景加载、界面弹出或游戏内容过渡期间监控此时间轴,以检查是否存在较高的分配峰值。
- mem.swap(压缩交换或 VmSwap):此图表绘制的是移至 zRAM 的内存块的预压缩大小。游戏运行期间出现高交换活动表明,游戏运行的设备内存受限,系统正在积极压缩后台资源。
2. 运行 SQL 查询(Trace Processor)如需进行详细的离线分析,您可以直接在 Perfetto 界面控制台中执行 SQL 查询,也可以使用独立的 Trace Processor Python 库来计算统计峰值。
找到匿名 RSS 分配的峰值:
SELECT max(value) / 1024 / 1024 AS max_rss_anon_mb FROM counter JOIN counter_track ON counter.track_id = counter_track.id WHERE counter_track.name = 'mem.rss.anon' AND counter_track.upid IN ( SELECT upid FROM process WHERE name = 'your.game.package.name' );关联任意给定时间戳的 RssAnon 和 VmSwap:
SELECT ts, track.name AS metric_type, value / 1024 / 1024 AS size_mb FROM counter JOIN counter_track track ON counter.track_id = track.id WHERE (track.name = 'mem.rss.anon' OR track.name = 'mem.swap') AND track.upid IN ( SELECT upid FROM process WHERE name = 'your.game.package.name' ) ORDER BY ts ASC;
如需详细了解如何使用 Android Studio 检查轨迹文件,请参阅检查系统轨迹:进程内存 (RSS)。如需详细了解如何编写内存配置文件脚本,请参阅记录原生分配。
heapprofd
heapprofd 是 Perfetto 中的一种内存跟踪工具。此工具可使用 malloc 显示内存分配位置,从而有助于您发现内存泄漏。可以使用 Python 脚本来启动 heapprofd,而且由于该工具的开销较低,因此它不会像 Malloc 调试等其他工具一样影响性能。
bugreport
bugreport 是一个日志记录工具,用于确定游戏是否因为内存耗尽而崩溃。此工具的输出远比使用 logcat 得到的结果更详尽。它对于内存调试来说很有用,因为它可以显示游戏是因为内存不足而崩溃的,还是被 LMK 终止的。
如需了解详情,请参阅获取和阅读 bug 报告。
游戏引擎工具
虽然平台级日志和系统遥测对于跟踪操作系统阈值和合规性至关重要,但游戏引擎专用工具可帮助您将分配直接归因于游戏对象、脚本行为和活动场景层次结构。
Unity
在 Unity 引擎环境中,您可以使用 Unity 的原生分析工具和类,在运行时以高可靠性(通常显示与真实操作系统级值相比小于 10% 的差异)密切估计 Android 匿名 RSS + 交换内存占用空间。
如需查看完整的分步教程(包括配置规则和运行时脚本),请参阅如何使用 Unity 工具检查内存。
- Unity 分析器 API:您可以通过查询核心引擎指标,以编程方式在运行时大致了解游戏的非托管内存占用情况:
- 使用 Profiler 类:通过对
Profiler.GetTotalReservedMemoryLong()和Profiler.GetMonoHeapSizeLong()的值求和来跟踪总内存分配量。 - 使用
ProfilerRecorder类:动态监控内存类别。为了建立可靠的基准近似值,请提取总预留内存(在正式版 build 上)或从中减去 Gfx 预留内存(在开发版 build 上),以剥离文件支持的显存组件。
- 使用 Profiler 类:通过对
- Unity 内存分析器:如需离线识别和调试内存泄漏,请捕获内存快照,然后检查“所有内存”部分下的“设备上的常驻内存”图表。如需计算大致占用空间,请将以下类别的总数相加:未跟踪、Android 运行时、原生和受管理。
- zRAM 限制:在内存紧张的情况下,Android 内核可以将不活跃的内存页压缩到交换空间 (zRAM) 中。由于 Unity 内存分析器无法检测操作系统级交换参数,因此在内存使用量大的场景中,您可能会看到细微的内存占用差异。将估算值与 Perfetto 进行交叉对比,以确认确切值。
Unreal
在 Unreal Engine 环境中,您可以将引擎诊断与平台遥测相结合,评估游戏的内存占用情况。如需获取分步说明和分析工作流,请参阅使用 Unreal Engine 检查内存用量。
主要诊断工具和接口包括:
- C++ 诊断 API:使用
GetMemoryUsedFast进行轻量级内存查询,并使用GetStats接口获取硬件级内存统计信息。 - 控制台命令:使用
stat unit和stat unitmax引擎命令监控设备硬件上的实时内存分配趋势。 - Unreal Insights:检查精确到帧的时间轴捕获,以分析平台级指标和自定义内存计数器。