如需有效优化游戏的内存占用,您必须先了解 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_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 {
@Override
public void onTrimMemory(int 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 QPR2、API 级别 37.2 /Build.VERSION_CODES_FULL.CINNAMON_BUN_2及更高版本):表示进程因超出平台内存限制器分配的 cgroup 内存限制 (RssAnon + VmSwap) 而被明确终止。即使设备上仍有充足的物理内存,也可能会发生这种终止。在 Android 17 的早期 build 中,getReason()会返回REASON_OTHER,其中"MemoryLimiter:AnonSwap"位于getDescription()中。
使用可用工具
在开发和质量保证期间,请使用以下平台工具准确测量游戏的内存用量。
meminfo
此工具会收集内存统计信息,以显示分配了多少 PSS 内存以及这些内存用于哪些类别。
通过以下任一方式输出 meminfo 统计信息:
- 使用命令
adb shell dumpsys meminfo package-name。 - 使用 Android Debug API 中的
MemoryInfo调用。
PrivateDirty 统计信息显示进程内无法分页到磁盘且不与其他任何进程共享的 RAM 量。当该进程被终止时,此容量的大部分会释放给系统。
内存跟踪点
内存跟踪点会跟踪游戏使用的 RSS 内存量。计算 RSS 内存用量比计算 PSS 用量快得多。由于 RSS 的计算速度更快,因此它能以更精细的粒度显示内存大小的变化,从而更准确地衡量峰值内存用量。因此,更容易注意到可能导致游戏内存不足的峰值。
Perfetto
Perfetto 是一套工具,用于收集设备上的性能和内存信息,并将其显示在基于 Web 的界面中。它支持任意长度的跟踪记录,因此您可以查看 RSS 随时间的变化情况。您还可以对它生成的数据发出 SQL 查询,以便进行离线处理。从 System Tracing 应用中启用长轨迹。确保为轨迹启用了 memory:Memory 类别。对于开发和测试中的自定义内存插桩,您还可以使用(Beta 版)heapprofd API。
在 Perfetto 中检查 RssAnon 和交换
如需检查游戏的匿名内存和 zRAM 交换影响,请在基于 Web 的界面 (ui.perfetto.dev) 中加载跟踪文件,并遵循以下专为深度内存案例研究设计的分析技术(如需了解详情,请参阅 Perfetto 内存分析案例研究):
1. 在时间轴上直观呈现内存计数器
- 找到您的进程:在导航列表中,搜索游戏的软件包或进程名称。
- 展开轨道组:点击进程行以展开其线程轨道,然后找到名为“Memory”的子组。
- 分析轨道:
- 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 + Swap 内存占用。
如需查看完整的分步教程(包括配置规则和运行时脚本),请参阅如何使用 Unity 工具检查内存。
- Unity Profiler API:您可以通过查询核心引擎指标,在运行时以编程方式估算游戏的非托管内存占用空间:
- 使用 Profiler 类:通过将
Profiler.GetTotalReservedMemoryLong()和Profiler.GetMonoHeapSizeLong()的值相加来跟踪总内存分配。 - 使用
ProfilerRecorder类:动态监控内存类别。如需建立可靠的基准近似值,请提取总预留内存(在发布 build 上)或从中减去 Gfx 预留内存(在开发 build 上),以剔除文件支持的显存组件。
- 使用 Profiler 类:通过将
- Unity 内存分析器:如需离线识别和调试内存泄漏,请捕获内存快照,并检查“All of Memory”(所有内存)部分下的“Resident Memory on Device”(设备上的常驻内存)图表。如需计算大致占用空间,请将以下类别的总和相加:未跟踪、Android 运行时、原生和受管理。
- zRAM 限制:在内存紧张的情况下,Android 内核可以将非活跃内存页压缩到交换空间 (zRAM) 中。由于 Unity 内存分析器无法检测操作系统级交换参数,因此您可能会在内存占用较高的场景中看到细微的占用空间差异。 将您的估算值与 Perfetto 进行交叉对比,以确认确切值。
Unreal
在 Unreal Engine 环境中,您可以将引擎诊断与平台遥测相结合,评估游戏的内存占用情况。如需了解分步说明和性能分析工作流,请参阅使用虚幻引擎检查内存用量。
主要诊断工具和接口包括:
- C++ Diagnostics API:使用
GetMemoryUsedFast进行轻量级内存查询,使用GetStats接口获取硬件级内存统计信息。 - 控制台命令:使用
stat unit和stat unitmax引擎命令监控设备硬件上的实时内存分配趋势。 - Unreal Insights:检查精确到帧的时间轴捕获,以分析平台级指标和自定义内存计数器。