R8 でプラットフォームのコードとリソースを最適化する

Android プラットフォーム ビルドシステム(Soong)は、DEX バイトコードをコンパイルするターゲットで R8 コンパイラを実行します。これには、Android アプリ(android_app)、テスト(android_test)、compile_dex: true を含むインストール可能な Java ライブラリ(services.jar など)が含まれます。これにより、未使用のコードとリソースを圧縮、最適化、削除します。静的ライブラリ(android_library と静的 java_library)では、Soong は R8 を直接実行しませんが、optimize プロパティ ブロックを使用して、コンシューマーの keep ルール(export_proguard_flags_files: true)を、それらを静的にリンクするダウンストリーム DEX ターゲットにアタッチして伝播します。

システム イメージやベンダー パッケージを構築するプラットフォーム エンジニアにとって、過度に広範な keep ルールを排除することは、システムの健全性に直接的なメリットをもたらします。

  • システム パーティションのフットプリントの縮小: R8 は、APK と JAR を /system、/system_ext、/product、/vendor にパッケージ化する前に、デッドクラス、メソッド、リソースを削除します。
  • コンパイルされたアーティファクトのサイズが小さくなる: DEX メソッドが少ないほど、ビルド時またはデバイス上のコンパイル中に dex2oat によって生成される .odex ファイルと .vdex ファイルのサイズが小さくなります。
  • ランタイム メモリの負荷を軽減: Android は、デマンドページングされた mmap 呼び出しを使用して、.odex、.vdex、APK リソース テーブルをプロセス メモリにマッピングするため、バイナリを小さくすると、アプリの起動時のメジャー ページフォールトが減少し、プロセス全体で常駐コードとリソース メモリが減少します。コンパイルされたアプリコードがデバイスのメモリに与える影響については、アプリコードはメモリをご覧ください。
  • より効果的なプログラム全体の最適化: 保持制約を絞り込むことで、R8 はメソッドのインライン化、インターフェースと仮想呼び出しの仮想化解除、未使用フィールドの削除、クラス間での定数の伝播を行うことができます。

Soong で R8 の最適化を構成する

Android.bp ファイルで、android_app とインストール可能な java_library ターゲット(または android_library と静的 java_library モジュールで、コンシューマーのキープルールをエクスポートする)の optimize プロパティ ブロックを使用して、R8 の最適化を構成します。

android_app {
    name: "MySystemApp",
    srcs: ["src/**/*.java"],
    optimize: {
        obfuscate: true,
        shrink_resources: true,
    },
}

次の表に、Soong で最も一般的な optimize プロパティ(build/soong/java/dex.go で定義)をまとめます。

プロパティ 説明
enabled ターゲットで R8 を実行するかどうかを制御します。すべての android_app ターゲットのデフォルトは true です。
shrink ツリー シェイキングを制御して、到達不能なクラス、フィールド、メソッドを削除します。android_app ターゲットのデフォルトは true です(スタンドアロンの java_library モジュールとテスト モジュールの場合は false。明示的に設定しない限り -dontshrink が渡されます)。
optimize メソッドのインライン化、クラスの統合、定数の伝播、デッドブランチの削除など、バイトコードの最適化を制御します。android_app ターゲットのデフォルトは true です(RELEASE_R8_OPTIMIZE_BY_DEFAULT リリースビルド フラグで制御されます)。
obfuscate 識別子の縮小と名前変更を制御します。過去の互換性を確保するため、デフォルトは false に設定されていますが、DEX のサイズを削減し、より高度な最適化を可能にするため、可能な限り true に設定してください(可能な限り難読化を有効にするをご覧ください)。
shrink_resources コード圧縮後に、パッケージ化された APK から未使用のリソース(res/ エントリ)を削除します。デフォルトは false です。
optimized_shrink_resources 統合された R8 コードとリソース圧縮ツールのパイプラインを実行して、R8 が 1 回のパスでコードとリソースの参照を共同でトレースできるようにします。shrink_resources: true が設定されている場合、デフォルトは RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT(標準プラットフォーム ビルドでは true)です。
proguard_flags_files カスタム保持ルールを含むモジュール固有の .flags ファイルまたは .pro ファイルを一覧表示します。標準のアノテーションまたはデフォルトで十分な場合は、カスタム ファイルを追加しないでください。
export_proguard_flags_files このライブラリの proguard_flags_files を、このライブラリに静的に依存するダウンストリーム モジュールに伝播します。
trace_references_from このターゲット R8 が自動的にトレースして保持するバイトコード参照を持つ、直接 DEX コンパイル依存ターゲット(instrumentation_for または libs を介して接続)を一覧表示します(override_android_app バリアントと override_android_test バリアントにわたって伝播され、.impl 分割とペアになっている場合は、非依存のコンパニオン Java ライブラリでもサポートされます)。
allow_traced_references instrumentation_for または libs を介して接続された、直接 DEX コンパイルに依存するターゲット(trace_references_from にリストできないクロスリポジトリ テストやベンダーテストなど)が、trace_references: true を設定することで、このターゲットへのバイトコード参照のトレースを有効にできるようにします。
trace_references allow_traced_references: true を含むターゲットの直接 DEX コンパイル依存関係で true に設定すると、このモジュールで R8 tracereferences が実行され、生成されたキープルールがターゲット モジュールに渡されます。
protect_api_surface --protect-api-surface を R8 に渡します。java_sdk_library に設定すると、有効なすべての API スコープ(public、system、module-lib、system-server、test)にわたって Metalava API の keep ルールが自動的に生成され、実装ライブラリにアタッチされます。

静的ライブラリ(android_library、java_library、AAR インポート)でも、Android.bp で required_downstream_optimization: { enabled: true, optimize: true } を設定して、それらを(直接または推移的に)静的にリンクするダウンストリーム DEX コンパイル ターゲットに、R8 圧縮とバイトコード最適化を有効にするよう要求できます。Soong は androidx.compose.runtime_runtime でこれを自動的に有効にするため、Jetpack Compose をリンクするターゲットは R8 で最適化されます。

可能な限り難読化を有効にする

Soong では、過去の互換性のために obfuscate のデフォルトは false になっていますが、可能な限り、android_app とスタンドアロン DEX ターゲットで obfuscate: true を明示的に設定する必要があります。

  • DEX と .vdex のフットプリントの縮小: パッケージ、クラス、フィールド、メソッドの名前を短い識別子(a、b)に変更すると、DEX 文字列プール(string_ids、string_data_item)と型記述子が縮小されます。Android は .vdex ファイルと .odex ファイルをプロセス メモリにマッピングするため、シンボル テーブルを小さくすると、システム パーティションのサイズとランタイム メモリ使用量の両方が直接削減されます。
  • プログラム全体の最適化の強化: R8 で識別子の名前を変更できるようにすると、クラスのマージ、パッケージのフラット化、パッケージ間の合成クラスの重複除去が可能になります。これらは、名前の競合を回避するために R8 でスキップする必要があるものです。
  • フルスタック トレースのシンボル化: Soong で難読化を有効にしても、デバッグのしやすさは低下しません。R8 でコンパイルされたターゲットごとに、Soong はモジュールの中間ディレクトリに proguard_dictionary マッピング ファイルを出力し、すべてのモジュール ディクショナリをビルドの proguard-dict.zip アーティファクトにバンドルし、マッピング ハッシュ(--map-id-template)を DEX ヘッダーに埋め込み、クラス SourceFile 属性(--source-file-template)を書き換えて、retrace がスタック トレースを自動的にシンボル化できるようにします。

obfuscate: false を保持する(または公開 API 名を明示的に保持する)のは、以下の場合のみです。

  • bootclasspath または system_server クラスパス上の共有ライブラリ: 実行時に他のモジュール(framework.jar、services.jar、<uses-library> ターゲットなど)によって動的にリンクされる外部 API サーフェスを公開するモジュール(java_library または java_sdk_library)は、obfuscate: false を保持するか、protect_api_surface: true、@KeepForApi、-keep ルールを使用して API サーフェスを明示的に保持する必要があります。これにより、スタブに対してコンパイルされた呼び出し元が実行時にクラス名とメンバー名を解決できるようになります。java_sdk_library モジュールで protect_api_surface: true を設定すると、有効なすべての API スコープ(public、system、module-lib、system-server、test)にわたって、Metalava ProGuard の保持ルールが自動的に生成され、実装ライブラリに適用されます。更新不可能な bootclasspath ターゲット(framework-minus-apex、ext、framework-location)で一般化された R8 最適化が有効になっている場合、アクティブな boot/hiddenapi/*.txt ファイルにリストされているメンバーを保持するために、framework-hiddenapi-proguard-rules(hiddenapi_to_proguard によって生成)も含まれます(hiddenapi-max-target-o.txt は省略)。

アプリケーションで obfuscate: true を有効にする前に、次の移行の前提条件を確認してください。

  • 外部テスト APK の依存関係: 外部の android_test APK が instrumentation_for: "MyApp" を設定し、MyApp の内部クラスまたはメソッドを直接呼び出す場合、それらのシンボルの名前を変更すると、テスト実行時に NoSuchMethodError または NoClassDefFoundError が発生します。trace_references_from(または trace_references: true を含む allow_traced_references)を構成し、MyApp.impl を静的にリンクする自己インストルメンテーション APK としてテストを構造化するか、テストフックに @VisibleForTesting アノテーションを付けます(ステップ 2: テスト駆動型の保持ルールを移行するを参照)。これにより、テストに必要な内部シンボルが保持され、アプリの残りの部分は難読化されます。
  • 文字列ベースのリフレクションと JNI: モジュールが、保持ルールやアノテーションなしで、リテラル文字列名(Class.forName、getDeclaredMethod、または JNI FindClass と GetMethodID)によってクラス、メソッド、フィールドを検索する場合、難読化によってそれらのターゲットの名前が変更され、ランタイム検索が中断されます。(アノテーションのないリフレクションも shrink: true と optimize: true で失敗します)。これらのエントリ ポイントにアノテーションを保持するためのガイド(@UsesReflection、@UsedByReflection、@UsedByNative)でアノテーションを付けると、R8 はそれらの名前を保持し、モジュールの残りの部分を難読化します。

カスタムフラグをゼロにする原則に従う

理想的な Android プラットフォーム モジュールには、カスタムの proguard.flags ファイルや keep.xml ファイルがありません。Android プラットフォームのビルドでは、カスタムの保持ルールの大部分が冗長です。

  • プラットフォーム ベースラインと AAPT2: ほとんどのエントリ ポイントは、グローバル プラットフォーム ベースライン(@Keep、JNI native メソッド、@VisibleForTesting など)によって自動的に保持されるか、AndroidManifest.xml とレイアウト リソースから AAPT2 によって生成されます(デフォルトのプラットフォーム保持ルールを理解するをご覧ください)。
  • ターゲットを絞った代替手段: エントリ ポイントを保持する必要がある場合は、分離された .flags ファイルよりも、宣言サイトのアノテーション(keepanno、@VisibleForTesting)、エクスポートされたライブラリ ルール(export_proguard_flags_files: true)、または静的テストリンクを優先します(既存の keep ルールを監査して移行するをご覧ください)。

カスタムの proguard.flags ファイルまたは keep.xml ファイルを追加または保持する前に、ルールがデフォルトのベースラインですでに処理されているかどうか、またはコード アノテーションに移行できるかどうかを確認します。

デフォルトのプラットフォーム保持ルールについて

Soong は、build/soong/java/dex.go で構成された DEX コンパイル ターゲット(android_app、android_test、インストール可能な java_library モジュール)のすべての R8 呼び出しに、次のグローバル ベースライン ルールを自動的に渡します。

  • build/make/core/proguard.flags: すべてのパッケージで @com.android.internal.annotations.VisibleForTesting アノテーションが付加されたクラスとメンバーを保持し、android.**、com.android.**、com.google.android.** パッケージ内で @VisibleForTesting(androidx.annotation.VisibleForTesting または com.google.common.annotations.VisibleForTesting)アノテーションが付加されたクラスとメンバーを保持します。また、@TestApi、@Keep(androidx.annotation、android.support.annotation、com.android.internal.annotations)、@KeepForWeakReference、@WeaklyReferencedCallback、@dalvik.annotation.optimization.** も保持されます。
  • build/make/core/proguard_basic_keeps.flags: スタック トレース、ランタイム可視性アノテーション(RuntimeVisible*Annotations)、Exceptions 属性と AnnotationDefault 属性、native メソッド、Serializable メンバー、@JavascriptInterface メソッド、Throwable(String) コンストラクタ、Parcelable$CREATOR フィールド、protobuf MessageLite フィールドの SourceFile 属性を保持します。
  • build/make/core/proguard/kotlin.flags: 特定の Kotlin メタアノテーション(kotlin.Metadata と kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target})に関する無害な警告を抑制し、リリースビルドで Kotlin DebugMetadata アノテーションを削除します。
  • build/make/core/proguard/checknotnull.flags: 一般的な null チェック ヘルパー呼び出し(com.google.common.base.Preconditions.checkNotNull と dagger.internal.Preconditions.checkNotNull*)を、簡潔なバイトコード null チェックに置き換えます。このファイルでは、フレームワーク API の境界を越えて明示的な例外メッセージを保持するために、意図的に Objects.requireNonNull を省略しています。
  • build/make/core/proguard/enumvalues.flags: ビルド構成で無効にされていない限り、enum タイプの values メソッドと valueOf メソッドを保持します。
  • AAPT2 の自動生成ルール: AAPT2 は、マージされた AndroidManifest.xml、レイアウト XML、設定 XML ファイルを検査して、登録済みのすべての Activity、Service、BroadcastReceiver、ContentProvider、BackupAgent、Application、カスタム View サブクラス、Preference サブクラス、XML android:onClick メソッドに対して、正確な保持ルールを生成します。

既存の保持ルールを監査して移行する

プラットフォーム リポジトリ内の既存の proguard.flags ファイルまたは keep.xml ファイルを監査する場合は、次の 4 段階の階層に沿って各ルールを評価します。

ステップ 1: 重複するルールや古いルールを削除する

グローバル ベースライン、AAPT2 マニフェストとレイアウト ルール、R8 のデフォルトですでにカバーされているルールと、存在しないクラスやパッケージを参照しているルールを削除します。

.flags ファイル内のすべてのルールが冗長な場合は、ファイルを削除し、Android.bp から proguard_flags_files を削除します。残りの optimize ブロックがデフォルト設定を再記述しているだけの場合は、冗長なブロックを削除し、bpfmt -w Android.bp を使用してビルドファイルをフォーマットします。パッケージをクリーンアップするときは、サブディレクトリ内のコンパニオン モジュール(Kotlin ターゲット バリアントなど)が同じフラグファイルを参照しているかどうかを確認し、一緒に更新します。

ステップ 2: テスト駆動型の保持ルールを移行する

次のいずれかのパターンを使用して、テスト駆動型の保持ルールをカスタムの本番環境用 .flags ファイルから移動します。

  • テストまたはコンパニオン ターゲット(trace_references_from または allow_traced_references)からの参照をトレースする: ターゲットと直接依存モジュールの両方が独自の DEX(android_app、android_test、または dexed java_library)をコンパイルし、instrumentation_for または libs を介して接続されている場合、Soong は依存モジュールで R8 tracereferences を実行し、生成された保持ルールをターゲットの R8 コンパイルにフィードできます。依存関係サイクルを作成したり、手動で .impl ライブラリを分割したりする必要はありません。

    • 同じリポジトリのテスト(trace_references_from): ターゲット モジュールに直接依存する instrumentation_for または libs をリストします(これは override_android_app バリアントと override_android_test バリアントに自動的に伝播され、.impl 分割とペアになっている場合は、依存関係のないコンパニオン Java ライブラリを引き続きサポートします)。

      android_app {
          name: "MyApp",
          srcs: ["src/**/*.java"],
          optimize: {
              obfuscate: true,
              shrink_resources: true,
              trace_references_from: ["MyAppTests"],
          },
      }
      
      android_test {
          name: "MyAppTests",
          instrumentation_for: "MyApp",
          srcs: ["tests/**/*.java"],
      }
      
    • リポジトリ間テストまたはベンダーテスト(allow_traced_references と trace_references): ターゲット モジュールが trace_references_from で依存関係を直接リストできない場合(別のリポジトリまたはベンダーツリーのテストなど)、ターゲット モジュールに allow_traced_references: true を設定し、trace_references: true を使用して各依存関係からオプトインします。

      android_app {
          name: "MyApp",
          srcs: ["src/**/*.java"],
          optimize: {
              allow_traced_references: true,
          },
      }
      
      android_test {
          name: "MyAppVendorTests",
          instrumentation_for: "MyApp",
          srcs: ["tests/**/*.java"],
          optimize: {
              trace_references: true,
          },
      }
      
    • テストでの文字列ベースのリフレクション: tracereferences は、バイトコードの定数プール参照を検査し、テストでの文字列ベースのリフレクション(Class.forName、getDeclaredField、getDeclaredMethod、リフレクション フィールド インジェクション ヘルパー)を検出できません。リフレクションでアクセスされる本番環境のフィールドとメソッドに @VisibleForTesting(または @UsedByReflection)アノテーションを付けます。

  • 実装ライブラリをテストにリンクする(static_libs): テスト専用のエントリ ポイントを本番環境 APK に保持せず、テストの変更によって本番環境ターゲットで R8 が再トリガーされないようにするには、アプリのソースファイルを android_library(MyApp.impl)に配置し、MyApp.impl を MyApp と MyAppTests の両方に静的にリンクします。

    android_library {
        name: "MyApp.impl",
        srcs: ["src/**/*.java"],
        manifest: "AndroidManifest.xml",
    }
    
    android_app {
        name: "MyApp",
        static_libs: ["MyApp.impl"],
        optimize: {
            obfuscate: true,
            shrink_resources: true,
        },
    }
    
    android_test {
        name: "MyAppTests",
        srcs: ["tests/**/*.java"],
        static_libs: ["MyApp.impl"],
    }
    

    この 3 モジュール パターンにより、MyAppTests は内部クラスに完全にアクセスできる自己インストルメンテーション テストとして実行され、MyApp は製品版のエントリ ポイントに対してのみ縮小と難読化を行うことができます。

  • テストフックに @VisibleForTesting アノテーションを付ける: 外部テスト APK が android_app ターゲットの内部メソッドまたはコンストラクタを数個しか呼び出さない場合は、それらの宣言に @VisibleForTesting アノテーションを付けることができます。グローバル build/make/core/proguard.flags ベースラインは、カスタム .flags ファイルなしで、android.**、com.android.**、com.google.android.** パッケージ内の @VisibleForTesting アイテムを自動的に保持します。(これらの名前空間外のベンダー モジュールには、trace_references_from、@UsedByReflection、またはエクスポートされたライブラリ ルールを使用します)。trace_references_from または trace_references を使用せずに @VisibleForTesting で手動アノテーションを付ける場合は、Mockito mock(...) または spy(...) に渡されるクラスにもアノテーションを付けます(スタブメソッドだけでなく)。これは、オンデバイスの mockito-target(dexmaker-mockito)がランタイム サブクラスを生成し、R8 がサブクラス化されていないクラスを final(MockitoException: Can't mock/spy class ... - final class)としてマークできるためです。

  • 重複する静的ライブラリ(jarjar_rules)を回避または再パッケージ化する: android_app とその instrumentation_for テスト APK の両方が同じライブラリを静的にリンクしている場合(guava などの直接リンク、または推移的リンク)、tracereferences はテストで定義されたクラスを認識し、アプリの keep ルールを出力しません。実行時に、アプリの親クラスローダーは、R8 で縮小されたクラスを使用してテストのコピーをシャドウイングし、IllegalAccessError または NoSuchMethodError を引き起こします。テストに独自のバンドル コピーが必要ない場合は、テストの static_libs から重複を削除(または libs を介してリンク)して、競合を回避します。それ以外の場合は、jarjar_rules を使用して、アプリの重複する静的ライブラリ パッケージをモジュール固有の接頭辞で再パッケージ化します。

ステップ 3: 所有ライブラリからルールをエクスポートする

共有 java_library または android_library が独自の内部リフレクションまたは JNI コールバックの保持ルールを必要とする場合は、ライブラリ ターゲットでルールを定義し、export_proguard_flags_files: true を設定します。

java_library {
    name: "my-shared-library",
    srcs: ["src/**/*.java"],
    optimize: {
        proguard_flags_files: ["proguard.flags"],
        export_proguard_flags_files: true,
    },
}

my-shared-library を静的にリンクするすべてのダウンストリーム android_app とライブラリ ターゲットは、これらのルールを自動的に継承するため、ダウンストリーム アプリでルールを複製する必要はありません。異なるディレクトリにまたがる複数のモジュールが、単一のコード ライブラリに関連付けられていないルールセットを共有する必要がある場合は、Android.bp の明示的な filegroup を介して .flags ファイルを公開します。これにより、モジュールはパッケージの境界を越えて :my-shared-flags をクリーンに参照できます。ダウンストリーム アプリの最適化を制限せずにライブラリ コンシューマーの keep ルールを作成するための一般的なガイダンスについては、ライブラリ作成者向けの最適化をご覧ください。

ステップ 4: ソースコード内の宣言にアノテーションを付ける

クラス、メソッド、コンストラクタ、フィールドがリフレクションまたは JNI を介してアクセスされ、AAPT2 またはグローバル ベースラインでカバーされていない場合は、.flags ファイル内の分離された -keep ルールを、Keep アノテーション ガイドのソース アノテーションに置き換えます(keepanno Javadoc リファレンスを参照)。

  • @UsesReflection: リフレクションを実行するコードを制御する場合は、このアノテーションを使用することをおすすめします。リフレクション呼び出しサイトに配置して、動的にアクセスされるターゲット クラス、メソッド、フィールドを宣言します。@UsesReflection は、アノテーション付きの呼び出しサイト自体が到達可能であるという前提条件を自動的にエンコードするため、呼び出しサイトが使用されていない場合、R8 は呼び出し元とリフレクション ターゲットの両方を削除します。
  • @UsedByReflection: 外部コードまたはライブラリによってリフレクションでインスタンス化または呼び出されるクラス、メソッド、フィールド、コンストラクタに配置します。たとえば、Bundle キーまたは Settings キーから Class.forName で読み込まれるクラスや、動的クラスローダーを介して読み込まれるプラグイン クラスなどです。kind(KeepItemKind.CLASS_AND_METHODS など)、preconditions(@KeepCondition)、パラメータ制約を指定して、R8 が正確なリフレクション コントラクトのみを保持し、クラスの未使用のメンバーを最適化または削除できるようにします。JobService や BroadcastReceiver などのマニフェスト登録コンポーネントに @UsedByReflection を追加しないでください。AAPT2 はすでにそれらを保持しています。
  • @UsedByNative: GetMethodID、GetStaticMethodID、GetFieldID を使用して C または C++ JNI コードからアクセスされるメソッドまたはフィールドに配置します。
  • @KeepForApi: ライブラリ自体が配布前に圧縮されるときに、そのまま残す必要があるライブラリ API クラスまたはメンバーに配置します。
  • @Keep(androidx.annotation.Keep): keepanno が適用されない場合の代替として使用します。クラスに @Keep を適用すると、クラスとそのすべてのメンバーが無条件に保持されます。メソッドまたはフィールドに @Keep を適用すると、クラスがインスタンス化されない場合でも、メンバーとそのメンバーを含むクラスを保持する無条件のエントリ ポイントとして機能します。一方、keepanno は条件付きの到達可能性を表します。

Soong モジュールで R8 keepanno アノテーションを使用するには:

  1. Android.bp の libs に "keepanno-annotations" を追加します。

    libs: [
        "keepanno-annotations",
    ],
    
  2. com.android.tools.r8.keepanno.annotations.* をインポートし、Java または Kotlin のソースコード内の宣言または呼び出しサイトにアノテーションを付けます。

    import com.android.tools.r8.keepanno.annotations.KeepItemKind;
    import com.android.tools.r8.keepanno.annotations.UsedByReflection;
    
    public final class CustomPluginController {
        @UsedByReflection(
            description = "Instantiated via Class.forName from plugin config",
            kind = KeepItemKind.CLASS_AND_METHODS)
        public CustomPluginController(Context context) {
            // ...
        }
    }
    

    R8 は、keepanno アノテーションを内部の keep ルールモデルに直接変換し、最終的な DEX 出力からアノテーションを削除します。これにより、実行時のバイトコード オーバーヘッドがゼロになります。

よくある問題の回避

以降のセクションでは、プラットフォーム コードでよくある proguard.flags と keep.xml の落とし穴とその修正方法について説明します。

広範なコンポーネントの keep ルール

# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
  • パフォーマンスが低下する理由: このルールは AAPT2 と完全に重複しています。このルールは、コンポーネントが最終的にマージされた AndroidManifest.xml に登録されているかどうかを確認せずにワイルドカードを使用するため、R8 は、クラスパス上の任意の場所で見つかったすべての Activity、Service、BroadcastReceiver サブクラス(未使用のライブラリ コンポーネントや無効なデバッグ アクティビティを含む)を保持せざるを得なくなります。
  • 推奨の修正方法: ルールを削除します。AAPT2 は、マージ マニフェストを検査し、登録されたコンポーネントの正確な -keep ルールを生成します。

Broad XML click handler keep rules

# Don't do this:
-keepclassmembers class * {
    public void *(android.view.View);
}
  • パフォーマンスが低下する理由: モジュール内のすべてのクラスですべての public void *(View) メソッドを保持すると、R8 は、たまたま 1 つの View パラメータを受け入れるメソッドを削除またはインライン化できなくなります。
  • 推奨の修正方法: ルールを削除します。AAPT2 は、レイアウト XML ファイルをスキャンし、android:onClick 属性によって参照されるメソッドを対象とする keep ルールを生成します。

広範な View のゲッターとセッターの keep ルール

# Don't do this:
-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
    public *** get*();
}
  • パフォーマンスに悪影響を及ぼす理由: このルールにより、R8 はアプリ内のすべての View サブクラスと、リンクされたすべてのライブラリ(AndroidX ライブラリやマテリアル ライブラリなど)にわたって、すべてのゲッターとセッターを保持することを強制されます。これにより、UI コード全体で、デッドメソッドの削除とインライン化がブロックされます。
  • 推奨の修正方法: ルールを削除します。AAPT2 は、レイアウト XML ファイルからインフレートされたカスタム View クラスのコンストラクタをすでに保持しています。コードが ObjectAnimator.ofFloat(view, "translationZ", ...) などのリフレクション文字列名を使用してビュー プロパティをアニメーション化する場合は、文字列名を型付きプロパティ参照(View.TRANSLATION_Z、またはカスタムの FloatProperty または IntProperty 実装)に置き換えて、リフレクションを完全に回避します。リフレクションによるプロパティ アクセスを回避できない場合は、特定のゲッターまたはセッターに @UsedByReflection アノテーションを付けます。

包括的な最適化または難読化により、

# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
  • パフォーマンスが低下する理由: -dontoptimize または -dontshrink を .flags ファイル内に配置すると、モジュールの Android.bp 設定が暗黙的にオーバーライドされ、ターゲット全体で最適化パスが無効になります。さらに、ライブラリが -dontoptimize を含む .flags ファイルをエクスポートすると、そのライブラリをリンクするすべてのダウンストリーム android_app で R8 の最適化が無効になります。
  • 推奨される修正: .flags ファイルから -dontoptimize、-dontshrink、-dontobfuscate を削除します。リーフ ターゲットで optimize ブロック(shrink、optimize、obfuscate)を使用して、Android.bp で最適化動作を明示的に制御します。

コミットされたルールの診断フラグ

# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
  • ビルドに悪影響を及ぼす理由: 診断フラグがビルドログをあふれさせるか、サンドボックス化された Soong ビルド中にローカルパスに出力ファイルを書き込もうとします。
  • 推奨の修正方法: commit された .flags ファイルからこれらのフラグを削除します。Soong は、R8 のマッピングと使用状況の出力(proguard_dictionary や proguard_usage.zip など)を、out/soong/.intermediates/ の下にあるモジュールの中間ディレクトリに自動的に書き込みます。

テストコードのプロダクション保持ルール

  • パフォーマンスが低下する理由: テストアクセス専用のカスタム -keep ルールを追加すると、そのコードは難読化されずに製品版ビルドに保持されます。trace_references_from(または trace_references: true を使用した allow_traced_references)を構成するか、@VisibleForTesting を使用してメソッドにアノテーションを付けると、手動の -keep ルールを維持する必要がなくなり、テストで参照されるもののみが保持されますが、これらのシンボルは本番環境バイナリに引き続き含まれます。
  • 推奨される修正: trace_references_from(または allow_traced_references と trace_references: true)を有効にしてカスタムテストの -keep ルールを排除するか、static_libs を使用して自己インストルメント化テスト APK にリンクされた .impl ライブラリにアプリを構造化し、テスト専用のエントリ ポイントを製品版 APK から完全に排除します。詳しくは、ステップ 2: テスト駆動型の keep ルールを移行するをご覧ください。

keep.xml での包括的なリソース ワイルドカード

<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@raw/*,@drawable/*,@string/*" />
  • パフォーマンスに悪影響を及ぼす理由: 包括的なワイルドカードは、モジュールとその依存関係全体で一致するタイプのリソースをすべて保持するため、リソースの圧縮(shrink_resources: true)が無効になり、APK とマッピングされた resources.arsc テーブルが肥大化します。
  • 推奨の解決方法:
    • keep.xml よりも静的リソース参照を優先する: Resources.getIdentifier メソッドを使用して、番号付きのテスト文字列やテーマ付きドローアブルなど、制限されたリソースセットを動的に検索することは避けてください。代わりに、コンパイル時の switch ステートメントを使用するか、静的な R.id、R.string、R.drawable 定数にマッピングします。静的参照を使用すると、R8 と AAPT2 が正確なライブリソースをトレースし、ランタイム文字列検索のオーバーヘッドを排除して、keep.xml の必要性を完全に排除できます。
    • 特定のリソース名を一覧表示する: サードパーティのライセンス リーダーなど、動的なルックアップが必要な場合は、tools:keep に正確なリソース識別子を一覧表示します(例: tools:keep="@raw/third_party_licenses")。
    • 不要な keep.xml ファイルを削除する: リストされたリソースがすでにコード(R.raw.foo)または XML(@raw/foo)で静的に参照されている場合、圧縮ツールはそれらを自動的に保持するため、res/raw/keep.xml を削除できます。

ルールの変更を確認して監査する

proguard.flags または keep.xml で保持ルールを削除または絞り込む場合は、モジュールが正常にビルドされ、単体テストとインストルメンテーション テストに合格し、必要なエントリ ポイントがすべて保持されていることを確認してください。

モジュールのビルドとテスト

  1. モジュールをクリーンにビルドして、R8 と AAPT2 が参照不足の警告なしで完了することを確認します。

    m <MODULE_NAME>
    
  2. atest を使用して、モジュールの単体テストとインストルメンテーション テストを実行します。

    atest <TEST_MODULE_NAME>
    
  3. システムアプリ、特権サービス、ハードウェア依存モジュールの場合、ターゲットの実機またはデバイス テストラボでインストルメンテーション テストと UI テストを実行して、ランタイム リフレクション パス、IPC バインディング、リソース インフレーションをテストします。

DEX とリソースの差分を検査する

ルール変更の前後にコンパイルされた APK または JAR を比較して、R8 が想定されるエントリ ポイントを削除せずに、デッドコードとリソースを削除していることを確認します。

  • apkanalyzer または dexdump を使用して、出力 APK または DEX ファイル内の保持されているクラス、メソッド、フィールドを検査します。

    apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • keep.xml を変更する場合や shrink_resources を有効にする場合は、aapt2 dump resources を使用して、ビルドで未使用のリソースが削除され、必要なリソースが保持されていることを確認します。

    aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • モジュールの R8 以前の classes.jar で prebuilts/r8/r8.jar を直接実行して、プラットフォームのフルビルドなしでフラグの変更をオフラインで検証する場合は、モジュールの --lib クラスパス JAR(android.jar、framework.jar、共有 libs)と、AndroidManifest.xml およびレイアウト XML から AAPT2 が生成した proguard.options ルールの両方を渡します。これらの入力がないと、R8 はフレームワークのスーパータイプ(Activity、Service、Phone、Handler)を解決できず、階層メソッドを十分に保持できません。

R8 で保持半径とルールの包含を分析する

オープンソースの R8 コンパイラには、コンパイル中にすべての Keep ルールの正確な影響を測定する Keep Radius アナライザ(Android Studio と Gradle では R8 Configuration Analyzer として公開)が含まれています。Soong は、このアナライザを Android プラットフォーム ビルド(build/soong/java/dex.go で構成)に直接統合します。

プラットフォーム モジュールまたはビルド全体の keep ルールを分析するには:

  1. R8_DUMP_KEEP_RADIUS=true 環境変数を使用してビルドを実行します。

    R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>
    

    R8_DUMP_KEEP_RADIUS=true が設定されている場合、Soong は R8 に、out/soong/.intermediates/ のコンパイル済みモジュールごとに中間 r8keepradius.pb ファイルに keep ルール指標を記録するよう指示します。各 keep ルールと keepanno アノテーションについて、R8 は以下を記録します。

    • 直接保持半径: ルールによって保持される正確なクラス、フィールド、メソッドと、圧縮、最適化、難読化に対して適用される特定の制約。
    • ルールの包含: 他のどの保持ルールまたはアノテーションが同じアイテムをすでに保持しているか。カスタムルールが AAPT2 ルールまたはグローバル ベースライン ルールに完全に包含されている場合は、安全に削除できます。
    • パッケージ全体およびグローバル ルール: パッケージ全体の幅広いワイルドカードを使用するルール、またはグローバル構成ディレクティブを適用するルール。
  2. KeepRadiusHtmlReportGenerator(prebuilts/r8/r8.jar にバンドル)を使用して、r8keepradius.pb 出力をインタラクティブな HTML レポートに変換します。

    # Generate an HTML report for a single module
    INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME>
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        $INTERMEDIATES/android_common/r8keepradius.pb \
        keep_radius_report.html
    
    # Scan all built modules under out/soong/.intermediates and generate
    # per-module HTML reports plus an aggregate keepradius.html summary
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        out/soong/.intermediates \
        out/keep_radius_reports
    

    ディレクトリを指定すると、KeepRadiusHtmlReportGenerator はすべての *keepradius*.pb ファイルを走査し、モジュールごとに HTML レポートを生成して、ビルド全体でライブアイテム数、保持アイテム数、最大半径の保持ルールを要約した out/keep_radius_reports/keepradius.html を作成します。生成されたレポートで圧縮、最適化、難読化のスコアを解釈する方法については、R8 構成アナライザを使用するをご覧ください。

Soong は、build/soong/java/dex.go で追加の R8 診断環境変数とインナーループ環境変数もサポートしています。

  • R8_DUMP_INPUT=true: スタンドアロン R8 再現用のすべての入力 JAR、ライブラリ JAR、統合された ProGuard 構成を含むモジュールの中間ディレクトリに r8inputs.zip を書き込みます。
  • R8_DUMP_PERFETTO_TRACE=true: Perfetto で R8 コンパイルパスを検査するために、r8trace.ptrace をモジュールの中間ディレクトリに書き込みます。
  • SOONG_SKIP_TRACE_REFERENCES=true: trace_references_from または trace_references で構成された tracereferences ルールの実行をスキップして、テストコードの反復処理時にローカルのインナーループ ビルドを高速化します。

ライブラリ コンシューマー ルールを検証する

java_library または android_library が keep ルールを下流のコンシューマー(export_proguard_flags_files: true)にエクスポートする場合、それらのルールには、ユーザーアプリの最適化を変更または無効にするグローバル フラグを含めないでください。

R8 には、オープンソースの keep ルール アナライザ(ProcessKeepRules)が用意されています。これは、Android プラットフォーム ビルドで process-keep-rules ホストツール(prebuilts/r8/Android.bp で定義)として公開されています。

# Build the R8 keep rules validator host binary
m process-keep-rules

# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>

process-keep-rules ツールは各構成ファイルを解析し、ライブラリ コンシューマー ルールで許可されていないディレクティブを検出すると、ファイルと行の診断で失敗します。許可されていないディレクティブには、グローバルな最適化、圧縮、難読化の無効化(-dontoptimize や -dontshrink など)、パッケージの再パッケージ化とアクセス変更フラグ、診断フラグまたはマッピング フラグ、アプリレベルの -keepattributes が含まれます。

pgaudit.py を使用してソースツリーを監査する

R8 のビルド時アナライザとともにビルドされていないソース ディレクトリを監査するため、Android プラットフォーム ツリーには build/make/core/proguard/tools/ の下に pgaudit.py スクリプトが含まれています。この静的分析ツールは、リポジトリ全体の Android.bp、proguard.flags、keep.xml ファイルをスキャンして、AAPT2 またはグローバル プラットフォーム ベースラインですでにカバーされているルール、広範なワイルドカード、-dontoptimize のオーバーライド、テスト専用の keep ルールにフラグを付けます。

# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/

# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json

プラットフォーム チームと OEM チームは、pgaudit.py を組み合わせてソースツリーのトリアージを迅速に行い、process-keep-rules を組み合わせてエクスポートされたライブラリ ルールを検証し、R8_DUMP_KEEP_RADIUS=true と KeepRadiusHtmlReportGenerator を組み合わせてビルド内のすべてのルールの正確なクラス、フィールド、メソッドの保持半径を測定できます。