减少内存用量

了解 Android 内存管理并设置用于衡量游戏内存用量的工具后,下一步就是主动减少和优化内存用量。遵守 Android 的严格限制有助于防止系统关闭游戏、缩短启动时间,并确保游戏在所有设备上都能顺畅运行。

本指南提供了一些实用技巧,可用于缩减游戏的内存占用空间,重点介绍了资源级优化、引擎专用配置和内存管理最佳实践。

减少 Unity 中的内存

由于 Unity 的架构设计,一旦原生块分配器和托管堆扩展,即使在释放资源后,引擎也倾向于保留这些内存页以供重用,而不是立即将其返回给操作系统 (OS)。具体来说,虚拟地址空间(预留内存)在进程的整个生命周期内都处于预留状态,而物理内存 (RSS) 不会立即回收,直到发生多次垃圾回收 (GC) 和内存整理周期后才会回收。因此,即使实际用量下降,临时内存峰值也可能会导致驻留内存长时间保持较高水平。此行为会增加低端设备上出现内存不足 (OOM) 崩溃的风险,并降低整体运行时稳定性。

因此,必须通过以下三个核心支柱来优化 Unity 内存,以适应这些引擎行为:

  • 控制有效内存用量,从一开始就防止出现内存峰值。
  • 管理纹理格式和着色器变体,以确保不会实例化不必要的资源和原生资源。
  • 重构了运行时代码结构,以消除受管堆上的不必要分配,从而最大限度地减少 GC 频率和堆扩展。

如需了解详情,请参阅 Unity 内存优化

减少 Unreal Engine 中的内存

在 Unreal Engine 中,高保真渲染流水线和复杂的对象依赖关系图会显著增加匿名 RSS 和文件支持的内存压力。特别是,依赖硬引用和深层蓝图继承层次结构会强制将未使用的关联资源加载到内存中。此外,过多的着色器排列、未优化的纹理流式传输池和未压缩的 ELF 重定位表也会导致基准内存占用量过高,从而增加低内存终止 (LMK) 的风险。

因此,必须通过以下三个核心支柱来优化 Unreal Engine 内存,以适应这些引擎行为:

  • 分离数据和逻辑,将硬引用或强引用替换为软引用或弱引用。
  • 剥离未使用的移动照明功能和排列选项,以最大限度减少流水线状态对象 (PSO) 和冗余渲染目标,同时应用 ASTC 压缩并使用设备配置文件自定义纹理流池。
  • 启用 RELR 和 APS 重定位表压缩功能,以缩小 ELF 二进制文件大小并减少运行时物理内存占用。

如需了解详情,请参阅 Unreal 内存优化

多进程优化

缓存进程的内存用量不计入内存限制,因为其对活跃应用没有影响。在单独的隔离进程中运行该服务有助于主进程尽快过渡到缓存状态,从而提高游戏的健康度。

如需了解详情,请参阅如何跟踪进程状态和内存如何使用 Unity 隔离服务进程以及如何使用 Unreal 隔离服务进程

减少用户感知到的服务中的内存用量

您的游戏可能需要在用户感知到的服务中运行逻辑,以实现完成大型下载或后台语音聊天系统等使用情形。这些策略可帮助您在这些场景中管理和减少内存用量。

大型下载的策略

这些策略可能适用于您希望在用户最小化游戏后继续进行的大型下载。

1. 隔离下载流程

原因:通过在单独的进程中执行下载操作,确保操作系统可以立即回收应用未使用的内存,因为内存分配器可能会保留内存池页面,即使在释放数组后,也会使匿名 RSS 内存人为地保持较高水平。当您明确终止或退出服务和进程时,内存会返回到操作系统池,而您的主进程不会受到影响。

  • 在 Unity 中:将下载任务分流到使用自定义清单中的 android:process=":downloader" 等进程声明的原生 Android Service,并使用 Unity 的 AndroidJavaClass JNI 调用它。请务必在下载完成后终止该进程。如需更详细的指导,请参阅使用 Unity 在单独的进程中运行可感知服务

  • 在 Unreal 中:使用 Unreal 插件语言声明一个自定义 Android Service(例如 android:process=":downloader"),并通过 C++ JNI 触发它。请务必在下载完成后终止该进程。 如需更详细的指导,请参阅使用 Unreal 在单独的进程中运行可感知的服务

  • 对于原生 Android:在 AndroidManifest 中声明一个 Service,并使用 android:process=":downloader" 等进程。在此隔离进程中运行下载,并在下载完成后调用 Process.killProcess(Process.myPid())

此方法如何提供帮助:缩短内存占用时长,并允许在释放较大主进程所用内存的同时继续下载。

2. 将下载内容直接流式传输到磁盘

内容:使用小型固定大小的可重复使用缓冲区,而不是将网络响应累积到大型数组中,然后再写入磁盘,从而直接将数据从网络套接字流式传输到磁盘。

  • 在 Unity 中:避免将 DownloadHandlerBuffer 用于资源包或大型文件,因为它会分配相当于文件大小的本地内存缓冲区(匿名 RSS 内存)。请改为使用 DownloadHandlerFile 在后台线程上将字节原生传输到磁盘。

  • 在 Unreal 中:使用 SetResponseBodyReceiveStream() 将传入的数据块直接管道传输到 FArchive(使用 Unreal 的文件管理器的文件支持的归档)中,而不是将来自 IHttpRequest 的载荷附加到 TArray<uint8> 中。

  • 对于原生 Android:使用缓冲池将 InputStream 通过管道传输到 FileOutputStream,而不是对 HTTP 响应调用 .readBytes().string()

此功能有何帮助:减少内存使用量峰值

3. 流式下载文件解压缩

原因:如果下载内容是压缩的,请将网络输入流封装在流式解压缩器(如 ZipInputStream)中,而不是下载文件、将其加载到 RAM 中,然后再提取。

此功能有何帮助:减少内存使用量峰值

4. 委托给操作系统

原因:为了完全避免管理后台内存,请将工作委托给 Android 的原生 API。

  • WorkManager 是围绕操作系统级 JobScheduler 的现代推荐封装容器。对于 Android 14 及更高版本,WorkManager 会自动将用户触发的下载操作作为用户发起的数据传输 (UIDT) 作业来处理。此方法在应用的进程内运行,因此您仍必须直接将数据流式传输到磁盘,以最大限度地减少内存用量。如果系统资源变得有限,UIDT 允许操作系统以优雅的方式暂停和恢复下载,从而保护您的应用免受低内存崩溃的影响。

  • DownloadManager 在单独的系统进程中运行,不会将下载的内存用量归因于您的应用。当文件下载完毕并准备就绪时,您的应用会收到广播通知。

此功能有何帮助WorkManager 有助于处理内存不足的情况,DownloadManager 减少应用的内存用量。

辅助服务的策略

这些策略可能适用于游戏与主游戏进程并行运行的辅助服务,例如后台语音聊天。

1. 隔离进程

内容:将功能(例如语音聊天解决方案)与主游戏引擎分离。例如,您可以在分配给单独进程(在清单中声明,例如使用 android:process=":voice")的 Android 前台服务中运行麦克风捕获和网络流式传输。

此功能有何帮助:当应用最小化时,繁重的主游戏进程可以降为较低优先级的缓存状态,而较轻的辅助服务则继续处于用户感知到的服务状态。

2. 减少未使用的进程内内存

原因:如果辅助服务与游戏引擎的集成程度过深,无法分离,请尝试在游戏进入后台/最小化后立即尽可能减轻进程的负担。考虑刷新纹理缓存、卸载非必要场景、将引擎 tick 和渲染速率降至 0,并显式调用垃圾回收。

此功能有何帮助:当游戏未处于前台时,可减少不必要的内存用量。