メモリ使用を最適化する

メモリの最適化は、スムーズなパフォーマンスの確保、アプリのクラッシュの防止、システムの安定性とプラットフォームの健全性の維持に不可欠です。すべてのアプリでメモリ使用量をモニタリングして最適化する必要がありますが、テレビ向けコンテンツ アプリには、ハンドヘルド デバイス向けの一般的な Android アプリとは異なる固有の課題があります。

メモリ使用量が多いと、次のようなアプリやシステムの動作に問題が生じる可能性があります。

  • アプリ自体が遅くなったり、遅延したり、最悪の場合は強制終了することがあります。
  • ユーザーに表示されるシステム サービス(音量調節、画像設定ダッシュボード、音声アシスタントなど)が非常に遅くなるか、まったく機能しなくなる。
  • システム コンポーネントが強制終了され、これらのコンポーネントが再起動すると、リソース競合が急増し、フォアグラウンド アプリに直接影響します。
  • ランチャーへの遷移が大幅に遅延し、遷移が完了するまでフォアグラウンド アプリが応答していないように見えることがあります。
  • システムが直接再利用の状態になると、メモリ割り当てを待機しながらスレッドの実行が一時的に一時停止します。これは、メインスレッドやコーデック関連のスレッドなど、任意のスレッドで発生する可能性があり、オーディオと動画のフレーム ドロップや UI のグリッチを引き起こす可能性があります。

TV デバイスのメモリに関する考慮事項

通常、テレビ デバイスのメモリ容量はスマートフォンやタブレットよりもかなり少なく、たとえば、テレビで確認できる構成は、1 GB の RAM と 1080p の動画解像度です。一方で、ほとんどのテレビアプリには類似した機能があり、実装も類似しており、共通の課題もあります。これらの 2 つの状況では、他のデバイスタイプやアプリでは発生しない問題が発生します。

  • メディア TV アプリは通常、グリッド状の画像ビュー全画面の背景画像の両方で構成されており、短時間に大量の画像をメモリに読み込む必要があります。
  • 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(1,280x720)の UI 解像度、1080p/FullHD(1,920x1,080)の動画解像度
  • 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 の UI、1080p または 2160p の動画解像度

ただし、特定のメモリ使用の誤用によって使用可能なメモリが使い果たされ、パフォーマンスが低下する可能性があるため、これらのデバイスのメモリ使用量を気にする必要がないわけではありません。

低 RAM TV デバイスのメモリ ターゲット

このようなデバイスでメモリを測定する場合は、Android Studio メモリ プロファイラを使用してメモリのすべてのセクションをモニタリングすることを強くおすすめします。TV アプリは、メモリ使用量をプロファイリングし、このセクションで定義するしきい値を下回るようにカテゴリを調整する必要があります。

メモリ プロファイラ

[メモリのカウント方法] セクションでは、報告されたメモリ数について詳しく説明しています。テレビアプリのしきい値の定義では、次の 3 つのメモリ カテゴリに焦点を当てます。

  • 匿名 + スワップ: Android Studio の Java + ネイティブ + スタック割り当てメモリで構成されます。
  • グラフィック: プロファイラ ツールで直接報告されます。通常はグラフィック テクスチャで構成されます。
  • ファイル: Android Studio では「コード」と「その他」のカテゴリに分類されます。

これらの定義に基づいて、次の表に、各タイプのメモリグループで使用できる最大値を示します。

メモリタイプ 目的 使用目標(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 機能を有効にします。これを有効にすると、グラフィック メモリと匿名メモリの両方に存在するビットマップの重複を回避できます。
  • 中間レンダリングと再レンダリングを避ける
    • これらは Android GPU Inspector で特定できます。
    • [テクスチャ] セクションで、最終的なレンダリングに向けたステップの画像を探します。これは、通常、いわゆる「中間レンダリング」です。
    • Android SDK アプリケーションの場合、レイアウト フラグ forceHasOverlappedRendering:false を使用してこのレイアウトの中間レンダリングを無効にすることで、多くの場合、これらのレンダリングを削除できます。
    • 重複するレンダリングに関する優れたリソースとして、重複するレンダリングを回避するをご覧ください。
  • 可能であればプレースホルダ画像の読み込みを避け、プレースホルダ テクスチャには @android:color/ または @color を使用してください。
  • コンポジションをオフラインで実行できる場合は、デバイスで複数の画像を合成しないでください。ダウンロードした画像から画像を合成するのではなく、スタンドアロンの画像を読み込むことをおすすめします。
  • ビットマップの処理ガイドに沿って、ビットマップを適切に処理します。

匿名+スワップメモリ

Anon+Swap は、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 以下にする必要があります。
  • 割り当て:
    • グラフィック メモリのガイダンスを使用して、匿名メモリに画像を重複させないようにします。
      • 画像は多くの場合、メモリを最も多く使用するため、画像が重複するとデバイスに大きな負荷がかかります。これは、画像グリッドビューの頻繁なナビゲーション中に特に顕著です。
    • 画面を移動するときに参照を破棄して割り当てを解放する: ビットマップとオブジェクトへの参照が残っていないことを確認します。
  • ライブラリ:
    • 新しいライブラリを追加するときに、ライブラリからのメモリ割り当てをプロファイリングします。追加のライブラリも読み込まれ、割り当てが行われ、バインディングが作成される可能性があるためです。
  • ネットワーキング:
    • アプリの起動中にブロックするネットワーク呼び出しを実行しない。これにより、アプリケーションの起動時間が遅くなり、起動時にメモリ オーバーヘッドが増加します。メモリは特にアプリの読み込みによって制限されます。最初に読み込み画面またはスプラッシュ画面を表示し、UI が配置されたらネットワーク リクエストを実行します。

バインディング

バインディングは、API 呼び出しを容易にするために、他のアプリケーションをメモリに持ってくるか、バインディングされたアプリのメモリ消費量を増やす(すでにメモリ内にある場合)ため、追加のメモリ オーバーヘッドが発生します。その結果、フォアグラウンド アプリの使用可能なメモリが減少します。サービスをバインドする際は、バインドを使用するタイミングと時間に注意してください。不要になったらすぐにバインディングを解放してください。

一般的なバインディングとベスト プラクティス:

  • Play Integrity API: デバイスの完全性を確認するために使用します。
    • 読み込み画面の後、メディアの再生前にデバイスの完全性を確認する
    • コンテンツを再生する前に、PlayIntegrity StandardIntegrityManager への参照を解放します。
  • Google Play Billing Library: Google Play を使用して定期購入と購入を管理するために使用します。
    • 読み込み画面の後にライブラリを初期化し、メディアを再生する前にすべての課金処理を処理します。
    • ライブラリの使用が終わったとき、および動画やメディアを再生する前に、必ず BillingClient.endConnection() を使用してください。
    • BillingClient.isReady()BillingClient.getConnectionState() を使用して、お支払い関連の作業を再度行う必要がある場合に備えて、サービスが切断されているかどうかを確認します。完了したら、BillingClient.endConnection() を再度実行します。
  • GMS FontsProvider
    • フォントのダウンロードはコストがかかり、FontsProvider はダウンロードを行うためにサービスをバインドするため、低 RAM デバイスではフォント プロバイダを使用するのではなく、スタンドアロン フォントを使用することをおすすめします。
  • Google アシスタント ライブラリ: 検索やアプリ内検索に使用されることがあります。可能であれば、このライブラリを置き換えてください。
    • Leanback アプリの場合: Gboard のテキスト読み上げ機能または androidx.leanback ライブラリを使用します。
      • 検索を実装する際は、検索に関するガイドラインに準拠してください。
      • 注: leanback は非推奨であり、アプリは TV Compose に移行する必要があります。
    • Compose アプリの場合:
      • Gboard のテキスト読み上げを使用して音声検索を実装する。
    • Watch Next を実装して、アプリ内のメディア コンテンツを検出可能にします。

フォアグラウンド サービス

フォアグラウンド サービスは、通知に関連付けられた特別なタイプのサービスです。この通知はスマートフォンやタブレットの通知トレイに表示されますが、テレビ デバイスにはスマートフォンやタブレットと同じ意味での通知トレイがありません。フォアグラウンド サービスは、アプリケーションがバックグラウンドにある間も実行し続けることができるため便利ですが、テレビアプリでは次のガイドラインに従う必要があります。

Android TV と Google TV では、ユーザーがアプリを終了した後にフォアグラウンド サービスを実行し続けることが許可されるのは次の場合に限られます。

  • オーディオ アプリの場合: フォアグラウンド サービスは、ユーザーがアプリを離れた後、オーディオ トラックの再生を継続するために実行し続けることを許可されるのみです。音声の再生が終了したら、サービスをすぐに停止する必要があります。
  • その他のアプリの場合: アプリがまだ実行されていてリソースを消費していることをユーザーに通知する通知がないため、ユーザーがアプリを離れたら、すべてのフォアグラウンド サービスを停止する必要があります
  • おすすめの更新や次の動画などのバックグラウンド ジョブの場合は、WorkManager を使用します。

ジョブとアラーム

WorkManager は、バックグラウンドの繰り返しジョブのスケジュール設定に使用できる最新の Android API です。WorkManager は、利用可能な場合(SDK 23 以降)は新しい JobScheduler を使用し、利用できない場合は古い AlarmManager を使用します。テレビでスケジュール設定されたジョブを実行する際のベスト プラクティスとして、次の推奨事項に従ってください。

  • SDK 23 以降では、AlarmManager API(特に AlarmManager.set()AlarmManager.setExact() などのメソッド)の使用を避けてください。これらの API を使用すると、システムがジョブを実行する適切なタイミング(デバイスがアイドル状態の場合など)を決定できなくなります。
  • 低 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()
  • ジョブを定期的に実行する必要がある場合(別のデバイスのアプリでユーザーのコンテンツ視聴アクティビティに基づいて [次に視聴する] を更新する場合など)は、ジョブのメモリ消費量を 30 MB 未満に抑えてメモリ使用量を抑えます。

その他の一般的なガイドライン

以下のガイドラインでは、Android アプリ開発に関する一般的な情報を提供しています。

  • オブジェクトの割り当てを最小限に抑え、オブジェクトの再利用を最適化し、使用されていないオブジェクトを速やかに割り当て解除します。
    • オブジェクト(特にビットマップ)への参照を保持しない
    • System.gc() と直接メモリ解放呼び出しは、システムのメモリ処理プロセスを妨げるため、使用しないでください。たとえば、zRAM を使用するデバイスで gc() を強制的に呼び出すと、メモリの圧縮と解凍によりメモリ使用量が一時的に増加する可能性があります。
    • リスト要素を再作成せずにビューを再利用するには、Compose のカタログ ブラウザで示されているような LazyList を使用するか、非推奨の Leanback UI ツールキットの RecyclerView を使用します。
    • 変更される可能性の低い外部コンテンツ プロバイダから読み取った要素をローカルにキャッシュに保存し、追加の外部メモリの割り当てを防ぐ更新間隔を定義します。
  • メモリリークの可能性を確認します。
    • 匿名スレッド内の参照、解放されない動画バッファの再割り当てなど、一般的なメモリリークのケースに注意してください
    • ヒープダンプを使用してメモリリークをデバッグします。
  • ベースライン プロファイルを生成して、コールド スタートでアプリを実行する際に必要なジャストインタイム コンパイルの量を最小限に抑えます。

ツールの概要