記憶體最佳化是確保效能順暢、防止應用程式當機,以及維持系統穩定性和平台健全狀態的關鍵。雖然每個應用程式都應監控及最佳化記憶體用量,但電視裝置的內容應用程式面臨的特定挑戰,與手持裝置的典型 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功能。啟用這項設定可避免重複點陣圖,否則點陣圖會同時存在於圖形記憶體和匿名記憶體中。
- 在 Glide 等程式庫中,啟用預設為停用的
- 避免中繼算繪和重新算繪
- 您可以使用 Android GPU 檢查器找出這些問題:
- 在「材質」部分中,尋找最終算繪的步驟,而不是形成最終算繪的元素,這通常稱為「中間算繪」。
- 對於 Android SDK 應用程式,您通常可以使用版面配置標記
forceHasOverlappedRendering:false停用這個版面配置的中間算繪,藉此移除這些項目。 - 如需相關資源,請參閱「避免重疊算繪」。
- 盡可能避免載入預留位置圖片,並使用
@android:color/或@color做為預留位置紋理。 - 如果可以在離線狀態下合成多張圖片,請避免在裝置上合成圖片。建議載入獨立圖片,而非從下載的圖片組合圖片
- 請參閱「處理點陣圖」指南,瞭解如何更妥善地處理點陣圖。
Anon+Swap memory
Android Studio 記憶體分析器中的「匿名 + 交換」是由「原生」、「Java」和「堆疊」配置組成。請使用 ActivityManager.isLowMemoryDevice() 檢查裝置是否受到記憶體限制,並按照這些指南調整。
- 媒體:
- 根據裝置 RAM 和影片播放解析度,指定媒體緩衝區的可變大小。這應該會計入 1 分鐘的影片播放時間:
- 1 GB / 1080p 40 至 60 MB
- 1.5 GB / 1080p 的60 到 80 MB
- 1.5 GB / 2160p 80 到 100 MB
- 2 GB / 2160p:100 至 120 MB
- 變更劇集時釋出媒體記憶體配置,避免匿名記憶體總量增加。
- 應用程式停止時,立即釋放並停止媒體資源:使用活動生命週期回呼處理音訊和影片資源。如果您不是音訊應用程式,請在活動發生
onStop()時停止播放,儲存所有正在執行的工作,並設定要釋出的資源。以便日後排定工作時間。請參閱「工作和鬧鐘」部分。- 您可以使用生命週期感知元件 (例如
LiveData和LifecycleOwner),處理活動生命週期呼叫。 - 如要讓工作感知生命週期,也可以使用 Kotlin 協同程式和 Kotlin Flow。
- 您可以使用生命週期感知元件 (例如
- 影片搜尋時請注意緩衝區的記憶體:開發人員通常會在搜尋時分配額外 15 到 60 秒的未來內容,讓影片準備就緒供使用者觀看,但這會造成額外的記憶體負擔。一般來說,在使用者選取新的影片位置前,請勿緩衝處理超過 5 秒的未來緩衝區。如果必須在搜尋時預先緩衝額外時間,請務必:
- 預先配置搜尋緩衝區並重複使用。
- 緩衝區空間不得超過 15 到 25 MB (視裝置記憶體而定)。
- 根據裝置 RAM 和影片播放解析度,指定媒體緩衝區的可變大小。這應該會計入 1 分鐘的影片播放時間:
- 分配:
- 請參閱圖形記憶體指南,確保不會在匿名記憶體中重複圖片。
- 圖片通常會佔用最多記憶體,因此重複使用圖片可能會對裝置造成很大的負擔。尤其是在大量瀏覽圖片格狀檢視畫面時,更是如此。
- 在移動畫面時,請捨棄參照來釋放配置: 請確保沒有留下點陣圖和物件的參照。
- 請參閱圖形記憶體指南,確保不會在匿名記憶體中重複圖片。
- 程式庫:
- 新增程式庫時,請剖析程式庫的記憶體配置,因為程式庫也可能會載入其他程式庫,進而配置記憶體並建立 Bindings。
- 網路:
- 請勿在應用程式啟動期間執行封鎖網路呼叫,這會減緩應用程式啟動時間,並在啟動時產生額外的記憶體負擔,而應用程式載入時記憶體特別受限。先顯示載入或啟動畫面,等 UI 就位後再發出網路要求。
繫結
繫結會將其他應用程式帶入記憶體,或增加繫結應用程式的記憶體消耗量 (如果該應用程式已在記憶體中),以利 API 呼叫,因此會造成額外的記憶體負擔。因此,前景應用程式的可用記憶體會減少。繫結服務時,請留意繫結的使用時間和方式。請務必在不需要繫結時立即釋出繫結。
常見繫結和最佳做法:
- Play Integrity API:用於檢查裝置完整性
- 在載入畫面後和媒體播放前檢查裝置完整性
- 播放內容前,請先釋放 PlayIntegrity 的參照
StandardIntegrityManager。
- Play Billing Library:用於透過 Google Play 管理訂閱項目和購買交易
- 在載入畫面後初始化程式庫,並在播放任何媒體前處理所有帳單作業。
- 使用程式庫完畢後,請務必先使用
BillingClient.endConnection(),再播放影片或媒體。 - 使用
BillingClient.isReady()和BillingClient.getConnectionState()檢查服務是否已中斷,以便重新完成任何帳單作業,然後在完成後再次執行BillingClient.endConnection()。
- GMS FontsProvider
- 在 RAM 較低的裝置上,建議使用獨立字型,而非字型供應程式,因為下載字型的成本很高,且 FontsProvider 會繫結服務來執行這項操作。
- Google 助理程式庫:有時用於搜尋和應用程式內搜尋,請盡可能取代這個程式庫。
- 適用於 Leanback 應用程式:使用 Gboard 文字轉語音功能或 androidx.leanback 程式庫。
- 請按照搜尋指南導入搜尋功能。
- 注意:leanback 已淘汰,應用程式應改用 TV Compose。
- 適用於 Compose 應用程式:
- 使用 Gboard 文字轉語音功能導入語音搜尋。
- 導入「待播清單」,讓使用者能探索應用程式中的媒體內容。
- 適用於 Leanback 應用程式:使用 Gboard 文字轉語音功能或 androidx.leanback 程式庫。
前景服務
前景服務是與通知相關聯的特殊服務類型。這項通知會顯示在手機和平板電腦的通知匣中,但電視裝置的通知匣與這些裝置不同。即使前景服務很有用,因為應用程式在背景執行時可以保持運作,但電視應用程式仍須遵守下列指南:
在 Android TV 和 Google TV 中,前景服務只能在使用者離開應用程式後繼續執行:
- 音訊應用程式: 使用者離開應用程式後,前景服務只能繼續執行,以便繼續播放音軌。音訊播放完畢後,服務必須立即停止。
- 其他應用程式: 使用者離開應用程式後,所有前景服務都必須停止。因為系統不會發出通知,告知使用者應用程式仍在執行並消耗資源。
- 對於更新推薦內容或「接下來請看」等背景工作,請使用
WorkManager。
工作和鬧鐘
WorkManager 是最先進的 Android API,可排程背景週期性工作。WorkManager 會在適用時使用新的 JobScheduler (SDK 23 以上),否則會使用舊的 AlarmManager。如要在電視上執行排定時間的工作,請遵循下列最佳做法:
- 在 SDK 23 以上版本中,請避免使用
AlarmManagerAPI,尤其是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()
一般記憶體注意事項
下列指南提供 Android 應用程式開發的一般資訊:
- 盡量減少物件配置、最佳化物件重複使用情形,並及時取消配置任何未使用的物件。
- 請勿保留對物件的參照,尤其是點陣圖。
- 請避免使用
System.gc()和直接釋放記憶體呼叫,因為這會干擾系統的記憶體處理程序。舉例來說,在採用 zRAM 的裝置中,強制呼叫gc()會因記憶體壓縮和解壓縮而暫時增加記憶體用量。 - 使用
LazyList,例如在 Compose 的目錄瀏覽器中示範的RecyclerView,或在現已淘汰的 Leanback UI 工具包中,重複使用檢視區塊,而不重新建立清單元素。 - 在本機快取從外部內容供應商讀取的元素,這些元素不太可能變更,並定義更新間隔,避免分配額外的外部記憶體。
- 檢查是否有記憶體流失的問題。
- 請注意典型的記憶體流失案例,例如匿名執行緒內的參照、從未釋放的視訊緩衝區重新配置,以及其他類似情況。
- 使用記憶體快照資料偵錯記憶體流失問題。
- 產生基準設定檔,盡量減少在冷啟動時執行應用程式所需的即時編譯量。
瞭解直接記憶體回收
Android TV 應用程式要求記憶體時,如果系統壓力過大,Android 基礎的 Linux 核心可能就必須使用直接記憶體回收。
這個程序包括完全暫停所有分配執行緒,等待釋放記憶體頁面。如果背景回收作業無法主動維護足夠的記憶體集區,就會發生這種情況。
這可能會導致使用者體驗明顯暫停或卡頓,因為系統會暫停分配執行緒,直到有足夠的記憶體可用為止。就這個意義而言,分配執行緒不限於應用程式程式碼呼叫 (例如 malloc());例如,記憶體需要分配給程式碼頁面中的頁面。
工具摘要
- 使用 Android Studio 記憶體分析器工具,檢查使用期間的記憶體用量。
- 使用 Android GPU 檢查器檢查圖像分配情形。