メモリの最適化は、Android で安定した高性能のゲーム エクスペリエンスを提供するために不可欠です。このガイドでは、メモリ効率が重要な理由、Android オペレーティング システムがプロセス メモリの上限を管理する方法、Google Play Console の新しいメモリ指標について説明します。これらの情報は、ゲームの技術的な品質をモニタリングして改善するのに役立ちます。
メモリの最適化の重要性
ゲームのメモリを最適化することは、プレーヤーの維持、デバイスの互換性の拡大、プラットフォームの品質基準の遵守に不可欠です。
- コールド スタートの防止(ユーザー エクスペリエンスと維持): プレーヤーがゲームから一時的に離れる(通知に返信したり、メッセージを確認したりするなど)と、オペレーティング システムはゲーム プロセスをバックグラウンドに配置します。ゲームのバックグラウンド メモリ使用量が大きすぎると、システムのローメモリ キラー(LMK)は、フォアグラウンド タスク用に RAM を再利用するために、ゲーム プロセスの強制終了を優先します。ユーザーが再開するときに、シームレスで瞬時のウォーム再開ではなく、ゲームは長いコールド スタートを行う必要があります。つまり、重いグラフィック アセット、音声、ゲームエンジンのバイナリをストレージから完全に再読み込みします。バックグラウンドでのメモリ使用量を抑えることで、このようなバックグラウンドでのサイレントな強制終了を防ぎ、ユーザーの状態を維持して、プレーヤーがセッションをすぐに再開できるようにします。システムの LMK の動作について詳しくは、Android Vitals - ローメモリ キラーのガイドをご覧ください。
- エコシステムとデバイスの安定性: メモリ使用量の非効率性やメモリ リークは、システム全体の健全性を低下させます。システムメモリが不足すると、システムに大きな負荷がかかり、フレームレートの低下、UI のカクつき、音声の不具合が発生します。メモリ プレッシャーが大きすぎると、システムのローメモリ キラー(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 はプロセスを強制終了します。この強制終了は、ApplicationExitInfoを使用して、メモリ制限機能の終了理由 (Android 17、26Q4 以降で利用可能)の下に記録されます。
Google Play Console Vitals の新しいメモリ上限
デベロッパーがメモリの問題を事前に特定できるように、Google Play は Google Play Console の Android Vitals に新しい指標を導入しています。Google Play Console は、ゲーム セッションの 90 パーセンタイル(P90)の匿名 RSS + スワップ メモリ使用量を追跡して、極端な外れ値を特定します。
警告と適用の上限は、デバイスの物理 RAM 容量とプロセスの状態に基づいてスケーリングされます。これらの上限は、2 つの異なるフェーズで適用されます。
詳細なガイドラインとメモリ上限については、Android Vitals - 不適切な動作の上限とは をご覧ください。
ユーザーが認識しているサービス
認識可能なサービスは、Android システムがユーザーに認識されると見なす重要なバックグラウンド プロセスです。この状態には、次のすべてのプロセスが含まれます。
- フォアグラウンド サービス(FGS)
- 優先ジョブ
- ユーザーが開始したデータ転送ジョブ
- システム バインド サービスまたは他のアプリケーションによってバインドされたサービス
認識可能なサービスは、バックグラウンドでの重要な長時間実行タスク用に設計されているため、累積的なライフサイクル リークの影響を受けやすくなります。 Android プラットフォームのメモリ上限では、この状態はフォアグラウンドではないと見なされます。つまり、ゲームがバックグラウンドに移行しても認識可能なサービスを実行し続けると、Google Play ヘルプセンターのページに示されている、より厳しいバックグラウンドまたはサービス メモリ上限が適用されます。ゲームは、フォアグラウンドからバックグラウンドの認識可能なサービスの状態に移行するときに、不要なアセットを積極的にトリミングする必要があります。
R8 の要件
バイトコード サイズを最小限に抑え、基本的な Java プロセスのオーバーヘッドを削減するため、Google Play Console はアプリの品質に関するガイドラインの一環としてコードの最適化を評価します。ビルド パイプラインの構成について詳しくは、R8 でアプリの最適化を有効にするガイドをご覧ください。
プロジェクトで R8 を構成するには、R8 でアプリの最適化を有効にする ガイドの手順に沿って操作します。高度な圧縮と最適化の設定を有効にするには、フルモードで R8 を使用するをご覧ください。R8 がクラスの難読化やデッドコードの削除を妨げているルールを特定するには、R8 構成アナライザを使用するをご覧ください。
ビットマップの要件
ビットマップは、最新の高忠実度ゲームのメモリ使用量の大部分を占めています。ビットマップ ピクセルデータは Android 8.0(API レベル 26)以降の管理されていないネイティブ ヒープに直接格納されるため、最適化されていない画像の読み込みにより、プロセスがプラットフォームのメモリ上限を超える可能性があります。画像のスケーリングとキャッシュに関するベスト プラクティスについては、画像の使用を最適化するをご覧ください。
メモリ使用量をモニタリングする
ゲームのメモリを効果的に最適化するには、まず Android プラットフォームがフットプリントを測定する方法を理解する必要があります。Android 17 では、メモリ指標が更新され、ファイルバックアップ メモリまたは GPU プライベート メモリを除き、匿名 RSS(RssAnon)と非圧縮スワップ(VmSwap)の合計を追跡します。このガイドでは、Perfetto や meminfo などのシステムレベルのツールを活用する方法、ProfilingManager や onTrimMemory などの診断 API を実装する方法、Unity と Unreal Engine 内で正確なメモリ割り当てを抽出する方法について詳しく説明します。ゲームを正確にプロファイリングし、従来のランタイム メモリ ポーリングに関連するパフォーマンスの低下を回避する方法を理解してください。
詳しくは、メモリ使用量をモニタリングするをご覧ください。
メモリ削減戦略
ゲームエンジンはクロスプラットフォーム開発を簡素化しますが、デフォルトのメモリ処理により OS レベルのメモリ上限がトリガーされる可能性があります。このページでは、Unity と Unreal Engine に合わせて調整された実践的な最適化手順について詳しく説明します。Java ベースの onTrimMemory に依存すると Unity でデッドロックが発生する可能性がある理由と、代わりにネイティブ ライフサイクル コールバックを使用する方法について説明します。また、ASTC 8x8 テクスチャ圧縮の使用やアセットのアンロードの構成など、すべてハードウェア層でゲームをスムーズに実行するためのアセットレベルの最適化についても説明します。
詳しくは、メモリ使用量を削減するをご覧ください。