如要有效最佳化遊戲的記憶體用量,您必須先瞭解 Android 平台如何測量記憶體,以及如何使用系統遙測、診斷 API 和剖析工具。本指南詳細說明如何根據新平台指南,監控、擷取及分析遊戲的記憶體配置。
瞭解 RSS 和更換指標
如要有效分析及偵錯遊戲的記憶體行為,您必須瞭解 Android 平台用於強制執行記憶體限制的確切技術指標。如要深入瞭解這個遙測參數在實際環境中的處理和監控方式,請參閱「Android Vitals - 記憶體用量 (anonymous RSS + swap)」說明文件。
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 作業系統記憶體管理有不同的影響。如果任何第三方 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 {
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 或原生當機 dump 檔機制 (例如 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 記憶體量。計算記憶體用量的速度比計算 PSS 用量來得快。RSS 的計算速度較快,因此能更精細地分析記憶體大小的變化,以更準確地測量尖峰記憶體用量。因此,您會更容易注意到造成遊戲記憶體不足的尖峰用量。
Perfetto
Perfetto 是一套工具,用於收集裝置上的效能和記憶體資訊,並將其顯示在網頁式的使用者介面中。這項工具支援任意長度的追蹤記錄,因此您可以查看 RSS 隨著時間的變化情形。也可以針對這項工具產生的資料執行 SQL 查詢,以進行離線處理。透過系統追蹤應用程式啟用長期追蹤記錄,並確認已啟用追蹤記錄的 memory:Memory 類別。如要在開發和測試期間進行自訂記憶體檢測,也可以使用 (Beta 版) heapprofd API。
在 Perfetto 中檢查 RssAnon 並進行交換
如要檢查遊戲的匿名記憶體和 zRAM 交換影響,請在 ui.perfetto.dev 的網頁式 UI 中載入追蹤記錄檔,並按照下列分析技術操作,這些技術專為深入研究記憶體案例而設計 (詳情請參閱「Perfetto 記憶體分析案例研究」):
1. 在時間軸上顯示記憶體計數器
- 找出程序:在導覽清單中,搜尋遊戲的套件或程序名稱。
- 展開軌跡群組:按一下程序列展開其執行緒軌跡,然後找到名為「記憶體」的子群組。
- 分析音軌:
- mem.rss.anon (匿名 RSS):這條折線圖顯示遊戲未受管理記憶體集區占用的即時實體 RAM。在場景載入、UI 彈出式視窗或遊戲過程轉換期間,監控這個時間軸,檢查是否有高分配尖峰。
- mem.swap (壓縮交換空間或 VmSwap):這個圖表會繪製移至 zRAM 的記憶體區塊壓縮前大小。如果遊戲期間出現大量交換活動,表示遊戲是在記憶體受限的裝置上執行,且系統正在積極壓縮背景資產。
2. 執行 SQL 查詢 (追蹤處理器) 如要進行詳細的離線分析,您可以直接在 Perfetto UI 控制台中執行 SQL 查詢,或使用獨立的追蹤處理器 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 顯示記憶體分配位置,可協助發現記憶體流失的情況。heapprofd 可以使用 Python 指令碼啟動,而且由於這項工具造成的負擔較低,因此不像 Malloc Debug 等其他工具會影響效能。
bugreport
bugreport 是一款記錄工具,可協助您瞭解遊戲是否因記憶體不足而當機。相較於 logcat,這項工具的輸出內容更為詳細。這對於記憶體偵錯很有幫助,因為這項工具會顯示遊戲是否因記憶體不足而當機,或是因 LMK 而終止。
詳情請參閱「擷取及查看錯誤報告」。
遊戲引擎工具
雖然平台層級的記錄和系統遙測資料對於追蹤 OS 門檻和法規遵循情況至關重要,但遊戲引擎專屬工具可協助您將分配作業直接歸因於遊戲物件、指令碼行為和作用中場景階層。
Unity
在 Unity 引擎環境中,您可以使用 Unity 的原生分析工具和類別,在執行階段以高可靠性 (通常與真實的 OS 層級值相比,差異小於 10%) 密切估算 Android 匿名 RSS + Swap 記憶體用量。
如需完整的逐步教學課程,包括設定規則和執行階段指令碼,請參閱「如何使用 Unity 工具檢查記憶體」。
- Unity 分析器 API:您可以查詢核心引擎指標,以程式輔助方式在執行階段估算遊戲的非受管理記憶體用量:
- 使用 Profiler 類別:將
Profiler.GetTotalReservedMemoryLong()和Profiler.GetMonoHeapSizeLong()的值加總,即可追蹤總記憶體配置。 - 使用
ProfilerRecorder類別:動態監控記憶體類別。如要建立可靠的基準近似值,請擷取「Total Reserved Memory」(在發布版本中) 或從中減去「Gfx Reserved Memory」(在開發版本中),以剝除檔案支援的圖形記憶體元件。
- 使用 Profiler 類別:將
- Unity 記憶體分析器:如要離線找出並偵錯記憶體流失問題,請擷取記憶體快照,然後檢查「All of Memory」部分下方的「Resident Memory on Device」圖表。如要計算概略足跡,請加總下列類別的總計:未追蹤、Android Runtime、原生和受管理。
- zRAM 限制:在記憶體不足的情況下,Android 核心可將閒置記憶體頁面壓縮到交換空間 (zRAM)。由於 Unity 記憶體分析器無法偵測作業系統層級的交換參數,因此在記憶體用量大的場景中,您可能會發現記憶體用量略有差異。交叉比對預估值與 Perfetto,確認確切值。