メモリ使用量を最適化する

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

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

  • アプリ自体が遅くなったり、ラグが発生したり、最悪の場合は強制終了されたりする可能性があります。
  • ユーザーに表示されるシステム サービス(音量調節、画像設定ダッシュボード、音声アシスタントなど)の動作が非常に遅くなるか、まったく動作しなくなることがあります。
  • ローメモリ キラー(LMK)デーモン プロセスは、メモリ負荷が高い状態に対応するため、必要最小限のプロセスを強制終了することがあります。その後、これらのコンポーネントはすぐに再起動し、リソース競合のスパイクを発生させ、フォアグラウンド アプリに直接影響を与える可能性があります。
  • ランチャーへの切り替えが大幅に遅延し、切り替えが完了するまでフォアグラウンド アプリが応答していないように見えることがあります。
  • システムは 直接再利用の使用を開始し、メモリ割り当てを待機している間、スレッドの実行を一時的に一時停止することがあります。これは、メインスレッドやコーデック関連のスレッドなど、任意のスレッドで発生する可能性があり、音声や動画のフレーム落ち、UI の不具合を引き起こす可能性があります。

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

通常、テレビ デバイスのメモリはスマートフォンやタブレットよりも大幅に少なくなっています。たとえば、テレビで確認できる構成は、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 TV デバイスで実行されるアプリは、追加のメモリ制御対策を実装する必要があります。

次の特徴を持つデバイスは、このカテゴリに該当すると見なされます。

  • 1 GB デバイス: 1 GB の RAM、720p/HD(1280x720)の UI 解像度、1080p/FullHD(1920x1080)の動画解像度
  • 1.5 GB デバイス: 1.5 GB の RAM、1080p/フル HD(1920x1080)UI 解像度、2160p/UltraHD/4K(3840x2160)動画解像度
  • OEM が追加のメモリ制約により ActivityManager.isLowRAMDevice() フラグを定義したその他の状況。

通常のテレビ デバイス

これらのデバイスでは、このようなメモリ負荷の高い状況は発生しません。これらのデバイスには次の特性があると考えられます。

  • RAM 1.5 GB 以上、720p または 1080p の UI、1080p の動画解像度
  • RAM 2 GB 以上、1080p の UI、1080p または 2160p の動画解像度

ただし、これらのデバイスでアプリがメモリ使用量を気にしなくてもよいという意味ではありません。特定のメモリの誤用により、使用可能なメモリが枯渇し、パフォーマンスが低下する可能性があります。

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

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

メモリ プロファイラ

メモリのカウント方法のセクションでは、報告されたメモリの数値について詳しく説明しています。テレビアプリのしきい値の定義では、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

以下の値を超えないことを強くおすすめします

  • Anon+Swap + Graphics)のメモリ使用量が 200 MB

ファイル メモリ

ファイル バックアップ メモリに関する一般的なガイダンスとして、次の点に注意してください。

  • 一般に、ファイルメモリは OS のメモリ管理によって適切に処理されます。
  • 現時点では、メモリ負荷の主な原因とはなっていません。

ただし、ファイル メモリ全般を扱う場合は、次の点に注意してください。

  • ビルドに未使用のライブラリを含めないようにし、可能な場合は完全なライブラリではなく、ライブラリの小さなサブセットを使用します。
  • サイズの大きなファイルをメモリに開いたままにせず、処理が完了したらすぐに解放します。
  • Java クラスと Kotlin クラスのコンパイル済みコードのサイズを最小化するには、アプリの圧縮、難読化、最適化ガイドをご覧ください。

具体的なテレビのおすすめ

このセクションでは、テレビ デバイスでのメモリ使用量を最適化するための具体的な推奨事項について説明します。

グラフィック メモリ

適切な画像形式と解像度を使用します。

  • デバイスの UI 解像度よりも高い解像度の画像を読み込まないでください。たとえば、1080p の画像は 720p UI のデバイスで 720p に縮小される必要があります。
  • 可能であれば、ハードウェア格納型ビットマップを使用します。
    • Glide などのライブラリで、デフォルトで無効になっている Downsampler.ALLOW_HARDWARE_CONFIG 機能を有効にします。これを有効にすると、グラフィック メモリと匿名メモリの両方に存在することになるビットマップの重複を回避できます。
  • 中間レンダリングと再レンダリングを避ける
    • これらは Android GPU Inspector で特定できます。
    • [テクスチャ] セクションで、最終レンダリングの要素のみではなく、最終レンダリングに向けたステップとなる画像を探します。これは一般的に「中間レンダリング」と呼ばれます。
    • Android SDK アプリケーションの場合、レイアウト フラグ forceHasOverlappedRendering:false を使用してこのレイアウトの中間レンダリングを無効にすることで、これらを削除できることがよくあります。
    • 重複するレンダリングについては、重複するレンダリングを回避するをご覧ください。
  • 可能な場合は、プレースホルダ画像の読み込みを避けて、プレースホルダ テクスチャに @android:color/ または @color を使用します。
  • 合成をオフラインで実行できる場合は、デバイスで複数の画像を合成しないようにします。ダウンロードした画像から画像合成を行うのではなく、スタンドアロン画像を読み込むことを優先する
  • ビットマップの処理ガイドに沿って、ビットマップを適切に処理します。

Anon+Swap メモリ

Android Studio の Memory Profiler の [Anon+Swap] は、[Native] + [Java] + [Stack] の割り当てで構成されています。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(デバイスのメモリによって異なります)以下にしてください。
  • 割り当て:
    • グラフィック メモリのガイダンスを使用して、匿名メモリで画像を重複させないようにします。
      • 画像はメモリを最も多く使用するものの 1 つであるため、画像の重複はデバイスに大きな負荷をかける可能性があります。これは、特に画像グリッドビューのナビゲーションが頻繁に行われる場合に当てはまります。
    • 画面を移動する際に参照を削除して割り当てを解放する: ビットマップやオブジェクトへの参照が残っていないことを確認します。
  • ライブラリ:
    • 新しいライブラリを追加する際に、ライブラリからのメモリ割り当てをプロファイリングします。ライブラリを追加すると、追加のライブラリも読み込まれ、割り当てが行われ、バインディングが作成される可能性があります。
  • ネットワーキング:
    • アプリの起動時にブロッキング ネットワーク呼び出しを実行しないでください。アプリの起動時間が長くなり、起動時にメモリが特にアプリの読み込みによって制約されるため、メモリのオーバーヘッドが追加で発生します。最初に読み込み画面またはスプラッシュ画面を表示し、UI が配置されたらネットワーク リクエストを 1 回実行します。

バインディング

バインディングは、他のアプリをメモリに読み込むか、バインドされたアプリのメモリ消費量を増やす(すでにメモリにある場合)ため、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 のテキスト読み上げを使用して音声検索を実装します。
    • Watch Next を実装して、アプリ内のメディア コンテンツを見つけやすくします。

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

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

Android TV と Google TV では、ユーザーがアプリを離れた後もフォアグラウンド サービスが実行し続けることが許可されるのは、以下の条件を満たす場合のみです。

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

ジョブとアラーム

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

  • SDK 23 以降では、特に AlarmManager.set()AlarmManager.setExact() などのメソッドを含む AlarmManager 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 を使用して、ビューを再利用し、リスト要素を再作成しないようにします。
    • 変更される可能性の低い外部コンテンツ プロバイダから読み取った要素をローカルにキャッシュに保存し、追加の外部メモリの割り当てを防ぐ更新間隔を定義します。
  • メモリリークの可能性がないか確認します。
    • 匿名スレッド内の参照、解放されない動画バッファの再割り当てなど、一般的なメモリリークのケースに注意してください。
    • ヒープダンプを使用して、メモリリークをデバッグします。
  • ベースライン プロファイルを生成して、コールド スタート時にアプリを実行する際に必要なジャストインタイム コンパイルの量を最小限に抑えます。

ダイレクト メモリの再利用について

Android TV アプリケーションがメモリをリクエストし、システムに負荷がかかっている場合、Android の基盤となる Linux カーネルは ダイレクト メモリ再利用を使用しなければならないことがあります。

このプロセスでは、割り当てスレッドを完全に一時停止して、解放されたメモリページを待ちます。これは、バックグラウンド再利用が十分なメモリプールを事前に維持できない場合に発生します。

このため、十分なメモリが利用可能になるまでシステムがスレッドの割り当てを一時停止し、ユーザー エクスペリエンスで目に見える一時停止やジャンクが発生する可能性があります。この意味で、スレッドの割り当ては malloc() などのアプリケーション コード呼び出しに限定されません。たとえば、コードページをページインするためにメモリを割り当てる必要があります。

ツールの概要