如需有效地优化游戏的内存占用量,您必须先了解 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,可让游戏动态响应内存压力,并在运行时捕获详细的内存诊断信息。
响应内存 trim 事件
系统使用 onTrimMemory 向应用通知生命周期事件,这些事件为应用提供了一个很好的机会,让应用可以主动减少内存用量,避免被 低内存终止守护程序 (LMK) 终止,从而为其他应用释放内存。
如果系统在后台终止应用,用户在恢复时会遇到冷启动缓慢的情况。减少后台内存用量有助于防止这些后台终止。
响应 trim 事件时,释放不需要立即使用的大型可重建内存分配:
示例: 响应
TRIM_MEMORY_UI_HIDDEN时,trim 或清除缓存的位图(从本地存储解码)。
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
Android 15(API 级别 35)中引入的 ProfilingManager API 允许
应用在运行时直接捕获以编程方式定义的快照(例如堆
配置文件、系统跟踪记录和 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)。即使设备上剩余的物理内存充足,也可能会发生此终止,这表示直接违反了单个进程限制。
使用可用工具
在开发和质量保证期间使用以下平台工具,准确衡量游戏的内存用量。
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 查询(跟踪记录处理器) 如需进行详细的离线分析,您可以直接在 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 进行交叉对比,以确认确切的值。