記憶體最佳化功能對於確保順暢效能、防止應用程式當機,以及維持系統和平台的穩定性至關重要。雖然應監控每個應用程式的記憶體用量並加以最佳化,但電視裝置的內容應用程式會面臨特定挑戰,與手持裝置的一般 Android 應用程式不同。
記憶體用量過高可能會導致應用程式和系統行為發生問題,包括:
- 應用程式本身可能會變慢或延遲,在最糟的情況下,甚至會遭到終止。
- 使用者可見的系統服務 (音量控制、畫面設定資訊主頁、語音助理等) 會變得非常延遲,甚至可能完全無法運作。
- 系統元件可能會遭到終止,然後重新啟動,觸發極端資源競爭的尖峰,並直接影響前景應用程式。
- 轉換至啟動器可能會延遲,導致前景應用程式在轉換完成前無法回應。
- 系統可能會進入直接回收情況,在等待記憶體分配時暫時暫停執行緒。任何執行緒都可能發生這種情況,例如主執行緒或編解碼相關執行緒,可能會導致音訊和影像影格遺失,以及 UI 異常。
電視裝置的記憶體注意事項
電視裝置的記憶體通常比手機或平板電腦少很多。舉例來說,我們在電視上看到的設定是 1 GB RAM 和 1080p 影片解析度。同時,大多數電視應用程式都具有類似的功能,因此實作方式和常見的挑戰也相似。這兩種情況會導致其他裝置類型和應用程式未見的問題:
- 媒體電視應用程式通常會同時包含格線圖片檢視畫面和全螢幕背景圖片,因此需要在短時間內將大量圖片載入記憶體
- 電視應用程式會播放多媒體串流,因此需要分配一定量的記憶體來播放影片和音訊,並需要大量的媒體緩衝區來確保順暢播放。
- 如果未正確實作,其他媒體功能 (尋找、變更劇集、變更音軌等) 可能會造成額外的記憶體壓力。
瞭解電視裝置
本指南主要說明低 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 TV 裝置上執行的應用程式需要實作額外的記憶體控管措施。
我們將具有下列特徵的裝置歸類為這一類別:
- 1 GB 裝置:1 GB RAM、720p/HD (1280x720) 使用者介面解析度、1080p/FullHD (1920x1080) 影片解析度
- 1.5 GB 裝置:1.5 GB RAM、1080p/Full HD (1920 x 1080) 使用者介面解析度、2160p/Ultra HD/4K (3840 x 2160) 影片解析度
- 其他情況下,OEM 會因其他記憶體限制而定義
ActivityManager.isLowRAMDevice()
標記。
一般電視裝置
這些裝置不會遇到記憶體壓力過大的情況。我們認為這些裝置具有下列特性:
- 至少 1.5 GB 的 RAM、720p 或 1080p 的 UI 和 1080p 的影片解析度
- 至少 2 GB RAM、1080p 使用者介面和 1080p 或 2160p 影片解析度
這並不表示應用程式不應在這些裝置上注意記憶體用量,因為某些特定的記憶體濫用行為仍可能耗盡可用記憶體,並導致效能不佳。
低 RAM TV 裝置的記憶體目標
在這些裝置上測量記憶體時,我們強烈建議使用 Android Studio 記憶體分析器監控記憶體的每個部分。TV 應用程式應分析記憶體用量,並將各類別的記憶體用量控制在本節所定義的門檻以下。
在「記憶體計算方式」一節中,您可以找到已回報記憶體數字的詳細說明。針對 TV 應用程式的門檻定義,我們會著重於三個記憶體類別:
- 匿名 + 交換:由 Android Studio 中的 Java + 原生 + 堆疊配置記憶體組成。
- 圖形:直接在分析工具中回報。通常由圖形紋理組成。
- 檔案:在 Android Studio 中,會以「Code」+「Others」類別回報。
根據這些定義,下表列出各記憶體群組類型應使用的最大值:
記憶體類型 | 目的 | 使用量目標 (1 GB) |
---|---|---|
匿名 + 交換 (Java + 原生 + 堆疊) | 用於配置、媒體緩衝區、變數和其他需要大量記憶體的工作。 | < 160 MB |
圖形 | 由 GPU 用於紋理和顯示相關緩衝區 | 30 到 40 MB |
檔案 | 用於記憶體中的程式碼頁面和檔案。 | 60 至 80 MB |
總記憶體上限 (Anon+Swap + Graphics + File) 不得超過下列值:
- 對於 1 GB 低 RAM 裝置,總記憶體用量 (Anon+Swap + Graphics + File) 為 280 MB。
強烈建議不要超過:
- 記憶體用量為 200 MB (Anon+Swap + Graphics)。
檔案記憶體
請注意,以下是檔案備份記憶體的一般指南:
- 一般來說,檔案記憶體會由 OS 記憶體管理機制妥善處理。
- 目前我們尚未發現這項功能是記憶體壓力的主要原因。
不過,一般來說,處理檔案記憶體時:
- 請勿將未使用的程式庫納入建構,並盡可能使用較小的程式庫子集,而非完整的程式庫。
- 不要將大型檔案保持在記憶體中,並在使用完畢後立即釋放。
- 如要縮減 Java 和 Kotlin 類別的編譯程式碼大小,請參閱「縮減、模糊處理及最佳化應用程式」指南。
特定電視推薦
本節提供如何在電視裝置上最佳化記憶體用量的具體建議。
圖形記憶體
使用適當的圖片格式和解析度。
- 請勿載入解析度高於裝置 UI 解析度的圖片。舉例來說,在 720p UI 裝置上,1080p 圖片應縮小至 720p。
- 盡可能使用硬體支援的點陣圖。
- 在 Glide 等程式庫中,啟用預設為停用的
Downsampler.ALLOW_HARDWARE_CONFIG
功能。啟用這項功能可避免重複的位圖,否則位圖會同時出現在圖形記憶體和匿名記憶體中。
- 在 Glide 等程式庫中,啟用預設為停用的
- 避免中間轉換和重新轉換
- 您可以使用 Android GPU 檢查器來找出這些問題:
- 請在「Textures」部分查看圖片,這些圖片是最終算繪的步驟,而非僅是形成算繪的元素,這通常是所謂的「中間算繪」。
- 對於 Android SDK 應用程式,您通常可以使用版面配置標記
forceHasOverlappedRendering:false
停用此版面配置的中間轉換作業,藉此移除這些轉換作業。 - 如要瞭解如何避免重疊的轉譯作業,請參閱「避免重疊的轉譯作業」一文。
- 盡可能避免載入預留位置圖片,請使用
@android:color/
或@color
做為預留位置紋理。 - 在裝置上避免合成多張圖片,除非合成作業可在離線狀態下執行。請盡量載入獨立圖片,而非從下載的圖片進行圖片合成
- 請參閱「處理位圖」指南,進一步瞭解如何處理位圖。
匿名+Swap 記憶體
匿名+Swap 是由 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 流程。
- 您可以使用 生命週期感知元件 (例如
- 在尋找影片時留意緩衝區的記憶體:開發人員在尋找影片時,通常會額外分配 15 到 60 秒的未來內容,以便為使用者準備影片,但這會產生額外的記憶體開銷。一般來說,在使用者選取新的影片位置之前,不要使用超過 5 秒的未來緩衝區。如果您確實需要在搜尋時預先緩衝額外時間,請務必:
- 提前配置尋向緩衝區,並重複使用。
- 緩衝區大小不得超過 15 到 25 MB (視裝置記憶體而定)。
- 根據裝置 RAM 和影片播放解析度,指定媒體緩衝區的變數大小。這應該會計算 1 分鐘的影片播放時間:
- 分配:
- 請參考圖形記憶體指南,確保您不會在匿名記憶體中重複圖片
- 圖片通常是記憶體的最大使用者,因此複製圖片可能會對裝置造成很大的壓力。在圖片 GridLayoutView 的大量導覽期間,這一點尤其重要。
- 在移動畫面時釋放配置,方法是捨棄其參照:請確認沒有任何對位圖和物件的參照。
- 請參考圖形記憶體指南,確保您不會在匿名記憶體中重複圖片
- 程式庫:
- 新增程式庫時,請剖析程式庫的記憶體配置,因為這些程式庫可能會載入其他程式庫,而這些程式庫也可能會進行配置並建立繫結。
- 建立人脈:
- 請勿在應用程式啟動期間執行封鎖網路呼叫,因為這會減慢應用程式啟動時間,並在啟動時產生額外的記憶體開銷,而記憶體會特別受到應用程式載入的限制。請先顯示載入或啟動畫面,並在 UI 就緒後執行網路要求。
繫結
繫結會引入額外的記憶體開銷,因為繫結會將其他應用程式帶入記憶體,或是增加繫結應用程式的記憶體用量 (如果該應用程式已在記憶體中),以便進行 API 呼叫。這會減少前景應用程式的可用記憶體。繫結服務時,請注意使用繫結的時間和時間長度。請務必在不再需要時立即釋放繫結。
常見繫結和最佳做法:
- Play Integrity API:用於檢查裝置完整性
- 在載入畫面後和媒體播放前檢查裝置完整性
- 播放內容前,請釋放對 PlayIntegrity
StandardIntegrityManager
的參照。
- Play 帳款服務程式庫:用於透過 Google Play 管理訂閱項目和購買交易
- 在載入畫面後初始化程式庫,並在播放任何媒體之前處理所有結帳作業。
- 使用程式庫後,請使用
BillingClient.endConnection()
關閉程式庫,並在播放影片或媒體前,一律使用BillingClient.endConnection()
關閉程式庫。 - 使用
BillingClient.isReady()
和BillingClient.getConnectionState()
檢查是否已中斷服務,以便再次執行任何需要的結帳作業,然後在完成後再次執行BillingClient.endConnection()
。
- GMS FontsProvider
- 在 RAM 較低的裝置上,建議使用獨立字型,而非字型供應程式,因為下載字型需要耗費大量資源,而 FontsProvider 會將服務繫結至下載字型。
- Google 助理程式庫:有時用於搜尋和應用程式內搜尋,請盡可能取代這個程式庫。
- 適用於 Leanback 應用程式:使用 Gboard 文字轉語音或 androidx.leanback 程式庫。
- 請按照搜尋指南實作搜尋功能。
- 注意:leanback 已淘汰,應用程式應改用 TV Compose。
- 適用於 Compose 應用程式:
- 使用 Gboard 文字轉語音功能實作語音搜尋。
- 實作 Watch Next,讓應用程式中的媒體內容可供探索。
- 適用於 Leanback 應用程式:使用 Gboard 文字轉語音或 androidx.leanback 程式庫。
前景服務
前景服務是與通知相關聯的特殊服務類型。這則通知會顯示在手機和平板電腦的通知匣中,但電視裝置沒有與這些裝置相同的通知匣。雖然前景服務可在應用程式處於背景執行時繼續運作,但電視應用程式仍須遵守下列規範:
在 Android TV 和 Google TV 中,使用者離開應用程式後,僅允許前景服務繼續執行:
- 針對音訊應用程式:使用者離開應用程式後,僅允許前景服務繼續執行,以便繼續播放音訊。音訊播放結束後,服務必須立即停止。
- 其他應用程式: 所有前景服務都必須在使用者離開應用程式後停止,因為系統不會通知使用者應用程式仍在執行並消耗資源。
- 針對背景工作,例如更新推薦內容或「接下來請看」,請使用
WorkManager
。
工作和鬧鐘
WorkManager
是用於排定背景週期性工作的最新 Android API。在可用情況下 (SDK 23 以上版本),WorkManager 會使用新的 JobScheduler
,否則會使用舊版 AlarmManager
。如要瞭解在電視上執行排定工作時的最佳做法,請參考下列建議:
- 在 SDK 23 以上版本中,避免使用
AlarmManager
API,尤其是AlarmManager.set()
、AlarmManager.setExact()
和類似方法,因為這些方法不允許系統決定執行工作時機 (例如裝置閒置時)。 - 在 RAM 較少的裝置上,請避免執行工作,除非有必要。如有需要,請僅在播放後更新推薦內容時使用 WorkManager
WorkRequest
,並盡量在應用程式仍處於開啟狀態時執行。 - 定義 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 中的目錄瀏覽器或現已淘汰的 Leanback UI 工具包中的RecyclerView
,以便重複使用檢視畫面,而非重新建立清單元素。 - 快取從外部內容供應器讀取的本機元素,這些元素不太可能變更,並定義更新間隔,以免分配額外的外部記憶體。
- 檢查是否有記憶體流失的可能情形。
- 留意典型的記憶體耗損情況,例如匿名執行緒中的參照、從未釋放的影片緩衝區重新配置,以及其他類似情況。
- 使用堆積記憶體快照偵錯記憶體流失問題。
- 產生基準設定檔,盡量減少在冷啟動時執行應用程式所需的即時編譯作業量。
工具摘要
- 使用 Android Studio 記憶體分析器工具,檢查使用期間的記憶體用量。
- 使用 Android GPU 檢查器檢查圖形分配情形。