減少記憶體用量

瞭解 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. 隔離下載程序

原因:請確保 OS 能立即回收應用程式未使用的記憶體,方法是在獨立程序中執行下載作業,因為記憶體分配器可能會保留記憶體集區頁面,即使釋放陣列後,匿名 RSS 記憶體仍可能人為偏高。明確終止或結束服務和程序時,記憶體會回傳至 OS 集區,主要程序不會受到影響。

  • 在 Unity 中:將下載作業卸載至以自訂資訊清單中的 android:process=":downloader" 等程序宣告的原生 Android Service,並使用 Unity 的 AndroidJavaClass JNI 叫用該程序。下載完成後,請務必終止程序。如需更詳細的指引,請參閱「在 Unity 中以獨立程序執行可察覺的服務」。

  • 在 Unreal 中:使用 Unreal 外掛程式語言宣告自訂 Android Service (例如 android:process=":downloader"),並透過 C++ JNI 觸發。下載完成後,請務必終止程序。 如需更詳細的指引,請參閱「Run a perceptible service in a separate process with Unreal」。

  • 適用於原生 Android:AndroidManifest 中宣告 Service,並使用 android:process=":downloader" 等程序。在這個獨立程序中執行下載作業,並在下載完成時呼叫 Process.killProcess(Process.myPid())

好處:縮短記憶體保留時間,並在釋出較大主程序使用的記憶體時,允許下載作業繼續進行。

2. 直接將下載內容串流至磁碟

做法:使用固定大小的小型可重複使用緩衝區,直接將資料從網路插座串流至磁碟,而不是先將網路回應累積到大型陣列中,再寫入資料。

  • 在 Unity 中:請避免使用 DownloadHandlerBuffer 處理資產組合或大型檔案,因為這會分配與檔案大小相等的原生記憶體緩衝區 (匿名 RSS 記憶體)。請改用 DownloadHandlerFile,在背景執行緒上將位元組原生串流至磁碟。

  • 在 Unreal 中:將傳入的資料區塊直接導入 FArchive (使用 Unreal 的檔案管理工具備份的檔案) 中,而不是將來自 IHttpRequest 的酬載附加到 TArray<uint8> 中。SetResponseBodyReceiveStream()

  • 適用於 Android 原生:InputStream 管道傳送至 FileOutputStream,使用集區緩衝區,而非在 HTTP 回應中呼叫 .readBytes().string()

這有什麼幫助:減少記憶體尖峰用量

3. 串流下載的檔案解壓縮作業

做法:如果下載內容經過壓縮,請將網路輸入串流包裝在串流解壓縮器 (例如 ZipInputStream) 中,而不是下載檔案、載入 RAM,然後解壓縮。

這有什麼幫助:減少記憶體尖峰用量

4. 委派給 OS

做法:如要完全避免管理背景記憶體,請將工作委派給 Android 的原生 API。

  • WorkManager 是建議使用的現代化包裝函式,可包裝 OS 層級的 JobScheduler。在 Android 14 以上版本中,WorkManager會自動將使用者觸發的下載作業視為使用者啟動的資料移轉 (UIDT) 工作。這會在應用程式的程序中執行,因此您仍須直接將資料串流至磁碟,才能盡量減少記憶體用量。如果系統資源不足,UIDT 可讓 OS 順利暫停及繼續下載,避免應用程式因記憶體不足而當機。

  • DownloadManager 會在獨立的系統程序中執行,不會將下載作業的記憶體用量歸給您的應用程式。檔案下載完成並可供使用時,您的應用程式會收到廣播通知。

這項功能有何助益: WorkManager 可協助處理記憶體不足的情況,而 DownloadManager 則可減少應用程式的記憶體用量。

輔助服務策略

這些策略可能適用於遊戲與主要遊戲程序並行執行的輔助服務,例如背景語音通訊。

1. 隔離程序

內容:將語音聊天解決方案等功能與主要遊戲引擎分離。舉例來說,您可以在指派給個別程序的 Android 前景服務中,執行麥克風擷取和網路串流 (在資訊清單中宣告,例如使用 android:process=":voice")。

這項功能如何提供協助:應用程式縮小時,較耗資源的主要遊戲程序可以進入優先順序較低的快取狀態,而較輕量的輔助服務則會繼續處於使用者可察覺的服務狀態。

2. 修剪未使用的程序內記憶體

做法:如果輔助服務與遊戲引擎的整合程度太深,無法分離,請在遊戲背景執行/縮小後,盡快減少程序負擔。建議清除材質快取、卸載非必要場景、將引擎刻度和算繪率降至 0,並明確呼叫垃圾回收。

好處:減少遊戲未在前台執行時不必要的記憶體用量。