メモリの最適化は、Android で安定した高パフォーマンスのゲーム エクスペリエンスを提供するために不可欠です。このガイドでは、メモリ効率が重要である理由、Android オペレーティング システムがプロセス メモリの上限を管理する方法、Google Play Console の新しいメモリ指標について説明します。これらの指標は、ゲームの技術的な品質をモニタリングして改善するのに役立ちます。
メモリ最適化の重要性
ゲームのメモリを最適化することは、プレーヤーの維持、デバイスの互換性の拡大、プラットフォームの品質基準の遵守に不可欠です。
- コールド スタートの防止(ユーザー エクスペリエンスと維持): プレーヤーが一時的にゲームから離れた場合(通知に応答したり、メッセージを確認したりする場合など)、オペレーティング システムはゲームプロセスをバックグラウンドに配置します。ゲームのバックグラウンド メモリ使用量が多すぎると、システムのローメモリ キラー(LMK)は、フォアグラウンド タスクの RAM を再利用するために、ゲームプロセスの終了を優先します。ユーザーが次に再開するとき、シームレスで瞬時のウォーム レジュームではなく、ゲームは長いコールド スタートを経験する必要があります。つまり、重い画像および映像、オーディオ、ゲームエンジンのバイナリをストレージから完全に再読み込みする必要があります。バックグラウンドでのメモリ使用量を低く抑えることで、こうしたサイレント バックグラウンド終了を防ぎ、ユーザーの状態を保持して、プレーヤーがセッションをすぐに再開できるようにします。システム LMK の動作の詳細については、Android Vitals - ローメモリ キラーのガイドをご覧ください。
- エコシステムとデバイスの安定性: メモリ使用量の非効率性とメモリリークは、システム全体の健全性を低下させます。システムメモリが不足すると、システムに大きな負荷がかかり、フレームレートの低下、UI の遅延、音声の不具合が発生します。メモリ不足が深刻な場合、システムの Low Memory Killer(LMK)がバックグラウンド プロセスを積極的に終了するため、プレーヤーがタスクを切り替えるときに、他のアプリケーションのコールド スタートが遅くなり、ユーザーの状態が失われます。
- プラットフォーム レベルの終了: Android 17(API レベル 37)以降、システムはメモリを過剰に使用するプロセスをより積極的に終了するようになります。ゲームのフットプリントが高すぎると、OS は標準のスタック トレースを生成せずにプロセスを突然終了することがあります。
- デバイスの互換性: フラッグシップ デバイスには 12 GB ~ 16 GB の RAM が搭載されていますが、世界中のゲーム ユーザーの大部分は 4 GB または 6 GB の RAM を搭載したデバイスを使用しています。適切なメモリ管理により、複雑な個別のアセット パッケージを必要とせずに、すべてのハードウェア層でゲームのアクセス性と応答性を維持できます。
Android のメモリについて
効果的なメモリ予算戦略を設計するには、Android プラットフォームが物理メモリを管理する方法と、ゲームのアクティブなフットプリントを測定する方法を理解する必要があります。
Android のメモリに関する基本コンセプト
プラットフォーム レベルのメモリ管理に関する基本コンセプトについては、公式のメモリ管理の概要のドキュメントをご覧ください。このリソースでは、次の 4 つのアーキテクチャ領域について説明します。
- メモリの概要: Android は、ページングとメモリ マッピング(mmap)を使用して RAM を管理します。ディスク上の従来のスワップ ファイルはサポートされていません。代わりに、ページ圧縮(zRAM を使用)とページ再利用を使用して物理メモリを解放します。
- プロセス間のメモリ割り当て: Android はシステム全体で RAM を共有します。Dalvik または ART 仮想マシンの実行に特定のヒープを割り当て、ネイティブ開発環境(C++ ゲームエンジンなど)がネイティブ システムヒープからメモリをリクエストできるようにします。
- アプリのメモリ管理: マルチプロセス モデルで動作する Android では、アプリがライフサイクル状態を動的にモニタリングし、不要なリソース(キャッシュされていないグラフィックやビットマップなど)を自発的に解放して、システムの健全性をサポートすることが期待されます。
- プロセスとスレッドの概要: システムは、ユーザーが認識する現在の公開設定と重要度に基づいてプロセスを階層に分類し、メモリ不足の状況でどのプロセスを維持し、どのプロセスを最初に終了するかを決定します。
合計メモリ使用量の指標
プラットフォーム レベルの Android 17 メモリリミッターは、合計常駐サイズ(RSS)や仮想メモリサイズではなく、合計メモリ使用量を使用してプロセスの消費量を評価します。
合計メモリ使用量 = 匿名 RSS(RssAnon)+ 非圧縮スワップ(VmSwap)
ゲームがプラットフォームの制限を超えないようにするには、これらの指標がシステムレベルで何を表しているかを正確に理解する必要があります。これらの指標、物理 RAM の割り当て、ファイルバック ページの処理方法の詳細については、メモリ使用量のモニタリング ガイドの RSS 指標とスワップ指標についてをご覧ください。
メモリの制約
システムの安定性を維持し、アプリケーションが過剰なリソースを消費しないようにするため、Android プラットフォームは実行中のプロセスのメモリ上限を管理します。
Android 17 以降のメモリリミッター
Android 17(API レベル 37)以降では、Linux cgroup v2 を使用して、アプリごとに厳格なメモリ上限を管理し、個々のアプリがシステム全体の不安定化を引き起こさないようにしています。技術的な実装の詳細については、AOSP メモリリミッター ガイドとメモリ効率の優先: Android 17 の重要なステップのブログをご覧ください。
- メカニズム: メモリリミッターは、すべてのアプリケーション プロセスをモニタリングし、プロセスのライフサイクル状態に基づいて制限を動的に割り当てます。
- 表示可能なプロセス(フォアグラウンド): 現在 UI を表示しているアプリプロセスは、より大きなリソース ワーキング セットを実行することが想定されているため、より寛大な制限が適用されます。
- 非表示のプロセス(バックグラウンドまたはサービス): UI を表示せずにアクティブな処理を行うアプリプロセスは、より厳しく制限された予算に制約されます。
- カーネル属性: このサービスは、次の 2 つの主な属性に依存しています。
memory.high: ソフトリミット。この値を超えると、カーネルはプロセスをスロットリングし、メモリを積極的に再利用しようとします。この再利用により、ゲームのパフォーマンスが低下する可能性があります。memory.swap.max: プロセスが使用できるスワップ領域または zRAM 領域の上限を管理します。
- 終了動作: プロセスが
memory.highを超えて匿名メモリを割り当て続け、スワップ容量を使い果たすと、割り当てが失敗し、OS はプロセスをサイレントに強制終了します。この終了は、メモリリミッターの終了理由(Android 17、26Q4 以降で使用可能)でApplicationExitInfoを使用してログに記録されます。
Google Play Console の Vitals の新しいメモリ上限
デベロッパーがメモリの問題を事前に特定できるように、Google Play は Google Play Console の Android Vitals に新しい指標を導入します。Google Play Console は、ゲーム セッションの 90 パーセンタイル(P90)の匿名 RSS + スワップ メモリ使用量を追跡して、極端な外れ値を特定します。
警告と違反措置のしきい値は、デバイスの物理 RAM 容量とプロセス状態に基づいてスケーリングされます。これらの上限は、2 つの異なるフェーズで適用されます。
詳細なガイドラインとメモリ上限については、Android Vitals - 「不正な動作のしきい値とは」をご覧ください。
ユーザーが認識しているサービス
知覚可能なサービスは、Android システムがユーザーに認識可能と見なす重要なバックグラウンド プロセスです。この状態には、実行中のすべてのプロセスが含まれます。
- フォアグラウンド サービス(FGS)
- 優先ジョブ
- ユーザーが開始するデータ転送ジョブ
- システムにバインドされたサービス、または他のアプリケーションによってバインドされたサービス
知覚可能なサービスは、バックグラウンドで実行される重要な長時間実行タスク向けに設計されているため、累積的なライフサイクル リークの影響を非常に受けやすくなっています。Android プラットフォームのメモリ上限では、この状態はフォアグラウンドではないと見なされます。つまり、ゲームがバックグラウンドに移行しても、Perceptible Service の実行を継続している場合は、Google Play ヘルプセンターのページに記載されている、より厳しいバックグラウンドまたはサービスメモリの上限が適用されます。ゲームは、フォアグラウンドからバックグラウンドの Perceptible Service 状態に移行する際に、不要なアセットを積極的にトリミングする必要があります。
R8 の要件
バイトコードのサイズを最小限に抑え、基本的な Java プロセスのオーバーヘッドを削減するため、Google Play Console では、アプリの品質に関するガイドラインの一環としてコードの最適化を評価しています。ゲームに適用される最適化の要件と制限の詳細については、Android Vitals のドキュメントの DEX コードの最適化をご覧ください。
標準の Android プロジェクトで R8 を構成するには、アプリの最適化を有効にするガイドに沿って操作します。高度な圧縮と最適化の設定を有効にするには、フルモードで R8 を使用するをご覧ください。R8 がクラスを難読化したり、デッドコードを削除したりするのを妨げているルールを特定するには、R8 Configuration Analyzer を使用します。
ゲームエンジンを使用してビルドする場合、デフォルトの R8 の動作はエンジンとバージョンによって異なります。
- Unity: コードの圧縮はデフォルトで無効になっているため、プロジェクト設定で有効にする必要があります。バージョン サポートと構成手順の詳細については、R8 を使用して Unity コードを圧縮、最適化するをご覧ください。
- Unreal Engine: UE 4.25 以降では、リリースビルドで R8 がデフォルトで有効になっているため、JNI ブリッジとプラグインの保持ルールを管理する必要があります。バージョンの詳細と UPL 構成の詳細については、R8 を使用して Unreal Engine コードを圧縮、最適化するをご覧ください。
ビットマップの要件
ビットマップは、最新のハイファイ ゲームのメモリ使用量の大部分を占めています。Android 8.0(API レベル 26)以降では、ビットマップ ピクセルデータが管理対象外のネイティブ ヒープに直接保存されるため、最適化されていない画像読み込みによって、プロセスがプラットフォームのメモリしきい値を超える可能性があります。画像のスケーリングとキャッシュに関するベスト プラクティスについては、画像の使用を最適化するをご覧ください。
メモリ使用量をモニタリングする
ゲームのメモリを効果的に最適化するには、まず Android プラットフォームがメモリ使用量を測定する方法を理解する必要があります。Android 17 では、メモリ指標が更新され、匿名 RSS(RssAnon)と非圧縮スワップ(VmSwap)の合計が追跡されるようになりました。ファイル バックアップ メモリと GPU プライベート メモリは除外されます。このガイドでは、Perfetto や meminfo などのシステムレベルのツールを使用する方法、ProfilingManager や onTrimMemory などの診断 API を実装する方法、Unity と Unreal Engine 内で正確なメモリ割り当てを抽出する方法について詳しく説明します。ゲームを正確にプロファイリングし、従来のランタイム メモリ ポーリングに関連するパフォーマンスの途切れを回避する方法を理解します。
詳細については、メモリ使用量をモニタリングするをご覧ください。
メモリ削減戦略
ゲームエンジンはクロスプラットフォーム開発を簡素化しますが、デフォルトのメモリ処理が OS レベルのメモリ制限をトリガーする可能性があります。このページでは、Unity と Unreal Engine に特化した実用的な最適化手順について詳しく説明します。Java ベースの onTrimMemory に依存すると Unity でデッドロックが発生する理由と、代わりにネイティブ ライフサイクル コールバックを使用する方法について説明します。また、ASTC 8x8 テクスチャ圧縮の使用やアセットのアンロードの構成など、アセットレベルの重要な最適化についても説明します。これにより、すべてのハードウェア ティアでゲームをスムーズに実行できます。
詳細については、メモリ使用量を削減するをご覧ください。