記憶體用量最佳化

記憶體最佳化是確保效能順暢、防止應用程式當機,以及維持系統穩定性和平台健全狀態的關鍵。雖然每個應用程式都應監控及最佳化記憶體用量,但電視裝置的內容應用程式面臨的特定挑戰,與手持裝置的典型 Android 應用程式不同。

記憶體用量偏高可能會導致應用程式和系統行為異常,包括:

  • 應用程式本身可能會變慢或延遲,最糟的情況是遭到終止。
  • 使用者可見的系統服務 (音量控制、圖片設定資訊主頁、語音助理等) 變得非常緩慢,甚至可能完全無法運作。
  • 低記憶體終止 (LMK) 精靈程序可能會因記憶體壓力過大而終止最不重要的程序,然後這些元件可能會在不久後重新啟動,進而引發資源爭用,直接影響前景應用程式。
  • 轉換至啟動器可能會大幅延遲,且在轉換完成前,前景應用程式會顯示為沒有回應。
  • 系統可能會開始使用直接回收,暫時停止執行緒,等待記憶體配置。任何執行緒都可能發生這種情況,例如主執行緒或編解碼器相關執行緒,可能導致音訊和影片影格遺失,以及 UI 故障。

電視裝置的記憶體注意事項

電視裝置的記憶體通常比手機或平板電腦少得多。舉例來說,電視上顯示的設定可能是 1 GB 的 RAM 和 1080p 的影片解析度。同時,大多數電視應用程式都有類似的功能,因此實作方式和常見挑戰也十分相似。這兩種情況會造成其他裝置類型和應用程式不會遇到的問題:

  • 媒體 TV 應用程式通常由格線狀圖片檢視畫面全螢幕背景圖片組成,需要在短時間內將大量圖片載入記憶體
  • 電視應用程式會播放多媒體串流,因此需要分配一定量的記憶體來播放影片和音訊,並需要相當多的媒體緩衝區,才能確保播放過程順暢。
  • 如果實作方式不正確,其他媒體功能 (例如搜尋、變更集數、變更音軌等) 可能會增加記憶體壓力。

瞭解電視裝置

本指南主要著重於應用程式記憶體用量,以及低 RAM 裝置的記憶體目標。

在電視裝置上,請考慮下列特性:

  • 裝置記憶體:裝置安裝的隨機存取記憶體 (RAM) 容量。
  • 裝置 UI 解析度:裝置用於算繪 OS 和應用程式 UI 的解析度,通常低於裝置影片解析度。
  • 影片解析度:裝置可播放影片的最高解析度。

這會導致不同裝置類型分類,以及這些裝置應如何使用記憶體。

電視裝置摘要

裝置記憶體 裝置影片解析度 裝置 UI 解析度 isLowRAMDevice()
1 GB 1080p 720p
1.5 GB 2160p 1080p
≥1.5 GB 1080p 720p 或 1080p 否*
≥2 GB 2160p 1080p 否*

低 RAM 電視裝置

這些裝置記憶體不足,會將 ActivityManager.isLowRAMDevice() 回報為 true。在低 RAM 電視裝置上執行的應用程式,需要實作額外的記憶體控制措施

我們認為具有下列特徵的裝置屬於這類裝置:

  • 1 GB 裝置:1 GB RAM、720p/HD (1280x720) 使用者介面解析度、 1080p/FullHD (1920x1080) 影片解析度
  • 1.5 GB 裝置:1.5 GB RAM、1080p/FullHD (1920x1080) UI 解析度、2160p/UltraHD/4K (3840x2160) 影片解析度
  • 由於記憶體限制,OEM 定義 ActivityManager.isLowRAMDevice() 標記的其他情況。

一般電視裝置

這些裝置不會出現如此嚴重的記憶體不足情況。我們認為這些裝置具有下列特徵:

  • ≥1.5 GB 的 RAM、720p 或 1080p 的 UI,以及 1080p 的影片解析度
  • 至少 2 GB 的 RAM、1080p 使用者介面,以及 1080p 或 2160p 影片解析度

這不代表應用程式不需留意這些裝置的記憶體用量,因為某些特定記憶體誤用情形仍可能耗盡可用記憶體,導致效能不佳。

低 RAM 電視裝置的記憶體目標

在這些裝置上測量記憶體時,我們強烈建議使用 Android Studio 記憶體分析器監控記憶體的每個部分。電視應用程式應分析記憶體用量,並努力將類別置於本節定義的門檻以下。

記憶體分析器

「記憶體的計算方式」 一節會詳細說明報表中的記憶體數據。電視應用程式的門檻定義著重於三種記憶體類別:

  • 匿名 + 交換:由 Android Studio 中的 Java + Native + Stack 分配記憶體組成。
  • 圖像:直接在剖析器工具中回報。通常由圖像紋理組成。
  • 檔案:在 Android Studio 中會以「程式碼」和「其他」類別回報。

根據這些定義,下表列出各類型記憶體群組應使用的最大值:

記憶體類型 目的 使用量目標 (1 GB)
Anonymous + Swap (Java + Native + Stack) 用於分配、媒體緩衝區、變數和其他需要大量記憶體的工作。 應小於 160 MB
圖像 GPU 用於紋理和顯示相關緩衝區 30 到 40 MB
檔案 用於記憶體中的程式碼頁面和檔案。 60 至 80 MB

記憶體總量上限 (匿名 + 交換空間 + 圖像 + 檔案) 不得超過下列限制:

  • 1 GB 低 RAM 裝置的總記憶體用量為 280 MB (匿名 + 交換空間 + 圖像 + 檔案)。

強烈建議不要超過下列限制:

  • (匿名 + 交換 + 圖像) 的記憶體用量為 200 MB

檔案記憶體

請注意,檔案備份記憶體的一般指引如下:

  • 一般來說,作業系統記憶體管理機制會妥善處理檔案記憶體。
  • 目前我們尚未發現這是造成記憶體壓力的主要原因。

不過,處理一般檔案記憶體時:

  • 請勿在建構作業中加入未使用的程式庫,並盡可能使用程式庫的小型子集,而非完整程式庫。
  • 不要將大型檔案保留在記憶體中,完成後請立即釋放。
  • 盡量縮減編譯後的程式碼大小 (適用於 Java 和 Kotlin 類別),請參閱「縮減、模糊處理及最佳化應用程式」指南。

特定電視節目推薦

本節提供具體建議,說明如何最佳化電視裝置的記憶體用量。

圖形記憶體

使用適當的圖片格式和解析度。

  • 請勿載入解析度高於裝置 UI 解析度的圖片。 舉例來說,在 720p 的 UI 裝置上,1080p 的圖片應縮小為 720p。
  • 盡可能使用硬體支援的點陣圖
    • 在 Glide 等程式庫中,啟用預設為停用的 Downsampler.ALLOW_HARDWARE_CONFIG 功能。啟用這項設定可避免重複點陣圖,否則點陣圖會同時存在於圖形記憶體和匿名記憶體中。
  • 避免中繼算繪和重新算繪
    • 您可以使用 Android GPU 檢查器找出這些問題:
    • 在「材質」部分中,尋找最終算繪的步驟,而不是形成最終算繪的元素,這通常稱為「中間算繪」
    • 對於 Android SDK 應用程式,您通常可以使用版面配置標記 forceHasOverlappedRendering:false 停用這個版面配置的中間算繪,藉此移除這些項目。
    • 如需相關資源,請參閱「避免重疊算繪」。
  • 盡可能避免載入預留位置圖片,並使用 @android:color/@color 做為預留位置紋理。
  • 如果可以在離線狀態下合成多張圖片,請避免在裝置上合成圖片。建議載入獨立圖片,而非從下載的圖片組合圖片
  • 請參閱「處理點陣圖」指南,瞭解如何更妥善地處理點陣圖。

Anon+Swap memory

Android Studio 記憶體分析器中的「匿名 + 交換」是由「原生」、「Java」和「堆疊」配置組成。請使用 ActivityManager.isLowMemoryDevice() 檢查裝置是否受到記憶體限制,並按照這些指南調整。

  • 媒體:
    • 根據裝置 RAM 和影片播放解析度指定媒體緩衝區的可變大小。這應該會計入 1 分鐘的影片播放時間:
      1. 1 GB / 1080p 40 至 60 MB
      2. 1.5 GB / 1080p 的60 到 80 MB
      3. 1.5 GB / 2160p 80 到 100 MB
      4. 2 GB / 2160p:100 至 120 MB
    • 變更劇集時釋出媒體記憶體配置,避免匿名記憶體總量增加。
    • 應用程式停止時,立即釋放並停止媒體資源:使用活動生命週期回呼處理音訊和影片資源。如果您不是音訊應用程式,請在活動發生 onStop()停止播放,儲存所有正在執行的工作,並設定要釋出的資源。以便日後排定工作時間。請參閱「工作和鬧鐘」部分。
    • 影片搜尋時請注意緩衝區的記憶體:開發人員通常會在搜尋時分配額外 15 到 60 秒的未來內容,讓影片準備就緒供使用者觀看,但這會造成額外的記憶體負擔。一般來說,在使用者選取新的影片位置前,請勿緩衝處理超過 5 秒的未來緩衝區。如果必須在搜尋時預先緩衝額外時間,請務必:
      • 預先配置搜尋緩衝區並重複使用。
      • 緩衝區空間不得超過 15 到 25 MB (視裝置記憶體而定)。
  • 分配:
    • 請參閱圖形記憶體指南,確保不會在匿名記憶體中重複圖片
      • 圖片通常會佔用最多記憶體,因此重複使用圖片可能會對裝置造成很大的負擔。尤其是在大量瀏覽圖片格狀檢視畫面時,更是如此。
    • 在移動畫面時,請捨棄參照來釋放配置: 請確保沒有留下點陣圖和物件的參照。
  • 程式庫:
    • 新增程式庫時,請剖析程式庫的記憶體配置,因為程式庫也可能會載入其他程式庫,進而配置記憶體並建立 Bindings
  • 網路:
    • 請勿在應用程式啟動期間執行封鎖網路呼叫,這會減緩應用程式啟動時間,並在啟動時產生額外的記憶體負擔,而應用程式載入時記憶體特別受限。先顯示載入或啟動畫面,等 UI 就位後再發出網路要求。

繫結

繫結將其他應用程式帶入記憶體,或增加繫結應用程式的記憶體消耗量 (如果該應用程式已在記憶體中),以利 API 呼叫,因此會造成額外的記憶體負擔。因此,前景應用程式的可用記憶體會減少。繫結服務時,請留意繫結的使用時間和方式。請務必在不需要繫結時立即釋出繫結

常見繫結和最佳做法:

  • Play Integrity API:用於檢查裝置完整性
    • 在載入畫面後和媒體播放前檢查裝置完整性
    • 播放內容前,請先釋放 PlayIntegrity 的參照 StandardIntegrityManager
  • Play Billing Library:用於透過 Google Play 管理訂閱項目和購買交易
  • GMS FontsProvider
    • 在 RAM 較低的裝置上,建議使用獨立字型,而非字型供應程式,因為下載字型的成本很高,且 FontsProvider 會繫結服務來執行這項操作。
  • Google 助理程式庫:有時用於搜尋和應用程式內搜尋,請盡可能取代這個程式庫。
    • 適用於 Leanback 應用程式:使用 Gboard 文字轉語音功能或 androidx.leanback 程式庫。
      • 請按照搜尋指南導入搜尋功能。
      • 注意:leanback 已淘汰,應用程式應改用 TV Compose。
    • 適用於 Compose 應用程式
      • 使用 Gboard 文字轉語音功能導入語音搜尋。
    • 導入「待播清單」,讓使用者能探索應用程式中的媒體內容。

前景服務

前景服務是與通知相關聯的特殊服務類型。這項通知會顯示在手機和平板電腦的通知匣中,但電視裝置的通知匣與這些裝置不同。即使前景服務很有用,因為應用程式在背景執行時可以保持運作,但電視應用程式仍須遵守下列指南:

在 Android TV 和 Google TV 中,前景服務只能在使用者離開應用程式後繼續執行:

工作和鬧鐘

WorkManager 是最先進的 Android API,可排程背景週期性工作。WorkManager 會在適用時使用新的 JobScheduler (SDK 23 以上),否則會使用舊的 AlarmManager。如要在電視上執行排定時間的工作,請遵循下列最佳做法:

  • 在 SDK 23 以上版本中,請避免使用 AlarmManager API,尤其是 AlarmManager.set()AlarmManager.setExact() 和類似方法,因為這些方法無法讓系統決定執行作業的適當時間 (例如裝置閒置時)。
  • 在 RAM 較少的裝置上,請避免執行工作,除非是絕對必要。如有需要,請WorkRequest 僅在播放完畢後使用 WorkManager 更新建議,並盡量在應用程式仍開啟時執行這項操作。
  • 定義 WorkManager Constraints,讓系統在適當時間執行作業:

Kotlin

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresStorageNotLow(true)
    .setRequiresDeviceIdle(true)
    .build()

Java

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresStorageNotLow(true)
    .setRequiresDeviceIdle(true)
    .build()
  • 如果必須定期執行作業 (例如根據使用者在其他裝置上應用程式中的內容觀看活動,更新「待播清單」),請將作業的記憶體用量維持在 30 MB 以下,以減少記憶體用量。

一般記憶體注意事項

下列指南提供 Android 應用程式開發的一般資訊:

  • 盡量減少物件配置、最佳化物件重複使用情形,並及時取消配置任何未使用的物件。
    • 請勿保留對物件的參照,尤其是點陣圖。
    • 請避免使用 System.gc() 和直接釋放記憶體呼叫,因為這會干擾系統的記憶體處理程序。舉例來說,在採用 zRAM 的裝置中,強制呼叫 gc() 會因記憶體壓縮和解壓縮而暫時增加記憶體用量。
    • 使用 LazyList,例如在 Compose 的目錄瀏覽器中示範的 RecyclerView,或在現已淘汰的 Leanback UI 工具包中,重複使用檢視區塊,而不重新建立清單元素。
    • 在本機快取從外部內容供應商讀取的元素,這些元素不太可能變更,並定義更新間隔,避免分配額外的外部記憶體。
  • 檢查是否有記憶體流失的問題。
    • 注意典型的記憶體流失案例,例如匿名執行緒內的參照、從未釋放的視訊緩衝區重新配置,以及其他類似情況。
    • 使用記憶體快照資料偵錯記憶體流失問題。
  • 產生基準設定檔,盡量減少在冷啟動時執行應用程式所需的即時編譯量。

瞭解直接記憶體回收

Android TV 應用程式要求記憶體時,如果系統壓力過大,Android 基礎的 Linux 核心可能就必須使用直接記憶體回收

這個程序包括完全暫停所有分配執行緒,等待釋放記憶體頁面。如果背景回收作業無法主動維護足夠的記憶體集區,就會發生這種情況。

這可能會導致使用者體驗明顯暫停或卡頓,因為系統會暫停分配執行緒,直到有足夠的記憶體可用為止。就這個意義而言,分配執行緒不限於應用程式程式碼呼叫 (例如 malloc());例如,記憶體需要分配給程式碼頁面中的頁面。

工具摘要