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

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

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

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

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

テレビ デバイスは通常、スマートフォンやタブレットよりもメモリが大幅に少ないです。たとえば、テレビでよく見られる構成は、1 GB の RAM と 1080p の動画解像度です 。同時に、ほとんどのテレビアプリには同様の機能があるため、同様の実装と共通の課題があります。 これらの 2 つの状況は、他のデバイスタイプやアプリでは見られない問題を引き起こします。

  • メディア テレビアプリは通常、グリッド表示の画像ビューフルスクリーンの背景画像 の両方で構成されており、短時間で大量の画像をメモリに読み込む必要があります。
  • テレビアプリはマルチメディア ストリームを再生 します。動画と音声を再生するには一定量のメモリを割り当てる必要があり、スムーズな再生を確保するにはかなりのメディア バッファが必要です。
  • 追加のメディア機能(シーク、エピソードの変更、音声トラックの変更など)が適切に実装されていない場合、メモリ プレッシャーがさらにかかる可能性があります。

テレビ デバイスについて

このガイドでは、主にアプリのメモリ使用量と低 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)の UI 解像度、 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 の UI、1080p または 2160p の動画解像度

特定のメモリの誤用により使用可能なメモリが不足 し、パフォーマンスが低下する可能性があるため、これらのデバイスでのメモリ使用量を考慮する必要があります。

低 RAM テレビ デバイスのメモリ ターゲット

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

Memory Profiler

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

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

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

メモリタイプ 目的 使用ターゲット(1 GB)
匿名 + スワップ(Java + ネイティブ + スタック) 割り当て、メディア バッファ、 変数、その他のメモリを大量に消費するタスクに使用されます。 160 MB 未満
グラフィック GPU がテクスチャとディスプレイ 関連のバッファ に使用します。 30 ~ 40 MB
ファイル メモリ内のコードページとファイルに使用されます。 60 ~ 80 MB

合計メモリの最大値 (匿名 + スワップ + グラフィック + ファイル)は、次の値を超えてはなりません

  • 1 GB の低 RAM デバイスの場合、合計メモリ使用量(匿名 + スワップ + グラフィック + ファイル )は 280 MB です。

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

  • 匿名 + スワップ + グラフィック )のメモリ使用量 200 MB

ファイル メモリ

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

  • 一般に、ファイル メモリは OS のメモリ管理によって適切に処理されます。
  • 現時点では、メモリ プレッシャーの主な原因ではないことがわかっています。

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

  • ビルドに**未使用のライブラリを含めない** でください。可能な場合は、完全なライブラリではなく、 ライブラリの小さなサブセットを使用してください。
  • サイズの大きなファイルを開いたままにしない でください。使用が終わったらすぐに 解放してください。
  • Java クラスと Kotlin クラスの コンパイル済みコードのサイズを最小限に抑えます。アプリを縮小、難読化、最適化するをご覧ください。

テレビに関する具体的な推奨事項

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

グラフィック メモリ

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

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

匿名 + スワップ メモリ

匿名 + スワップ は、Android Studio の Memory Profiler のネイティブ + 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 への参照を解放します。
  • Play Billing Library: Google Play を使用して サブスクリプションと購入を管理するために使用されます
  • GMS FontsProvider
    • フォントのダウンロードはコストがかかり、FontsProvider はサービスをバインドしてダウンロードを行うため、フォント プロバイダを使用するのではなく、低 RAM デバイスでスタンドアロン フォントを使用することをおすすめします。
  • 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 デバイスでは、厳密に必要な場合を除き、ジョブを実行しないでください。必要な場合は、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 の カタログ ブラウザや、 非推奨になった Leanback UI ツールキットの RecyclerView で示されているように、LazyList を使用してビューを再利用し、リスト要素 を再作成しないようにします。
    • 変更される可能性が低い外部コンテンツ プロバイダから読み取った要素をローカルにキャッシュし、追加の外部メモリの割り当てを防ぐ更新間隔を定義します。
  • メモリリークの可能性を確認します。
    • 匿名スレッド内の参照、解放されない動画バッファの再割り当てなど、一般的なメモリリーク のケースに注意 してください。
    • ヒープダンプを使用して メモリリークをデバッグします。
  • ベースライン プロファイル を生成して、コールド スタート時にアプリを実行する際に必要なジャストインタイム コンパイルの量を最小限に抑えます。

ダイレクト リクレイムについて

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

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

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

ツールの概要