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_testAPK がinstrumentation_for: "MyApp"を設定し、MyAppの内部クラスまたはメソッドを直接呼び出す場合、それらのシンボルの名前を変更すると、テスト実行時にNoSuchMethodErrorまたはNoClassDefFoundErrorが発生します。trace_references_from(またはtrace_references: trueを含むallow_traced_references)を構成し、MyApp.implを静的にリンクする自己インストルメンテーション APK としてテストを構造化するか、テストフックに@VisibleForTestingアノテーションを付けます(ステップ 2: テスト駆動型の保持ルールを移行するを参照)。これにより、テストに必要な内部シンボルが保持され、アプリの残りの部分は難読化されます。 - 文字列ベースのリフレクションと JNI: モジュールが、保持ルールやアノテーションなしで、リテラル文字列名(
Class.forName、getDeclaredMethod、または JNIFindClassとGetMethodID)によってクラス、メソッド、フィールドを検索する場合、難読化によってそれらのターゲットの名前が変更され、ランタイム検索が中断されます。(アノテーションのないリフレクションもshrink: trueとoptimize: trueで失敗します)。これらのエントリ ポイントにアノテーションを保持するためのガイド(@UsesReflection、@UsedByReflection、@UsedByNative)でアノテーションを付けると、R8 はそれらの名前を保持し、モジュールの残りの部分を難読化します。
カスタムフラグをゼロにする原則に従う
理想的な Android プラットフォーム モジュールには、カスタムの proguard.flags ファイルや keep.xml ファイルがありません。Android プラットフォームのビルドでは、カスタムの保持ルールの大部分が冗長です。
- プラットフォーム ベースラインと AAPT2: ほとんどのエントリ ポイントは、グローバル プラットフォーム ベースライン(
@Keep、JNInativeメソッド、@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フィールド、protobufMessageLiteフィールドのSourceFile属性を保持します。build/make/core/proguard/kotlin.flags: 特定の Kotlin メタアノテーション(kotlin.Metadataとkotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target})に関する無害な警告を抑制し、リリースビルドで KotlinDebugMetadataアノテーションを削除します。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サブクラス、XMLandroid: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、または dexedjava_library)をコンパイルし、instrumentation_forまたはlibsを介して接続されている場合、Soong は依存モジュールで R8tracereferencesを実行し、生成された保持ルールをターゲットの 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で手動アノテーションを付ける場合は、Mockitomock(...)または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 アノテーションを使用するには:
Android.bpのlibsに"keepanno-annotations"を追加します。libs: [ "keepanno-annotations", ],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 で保持ルールを削除または絞り込む場合は、モジュールが正常にビルドされ、単体テストとインストルメンテーション テストに合格し、必要なエントリ ポイントがすべて保持されていることを確認してください。
モジュールのビルドとテスト
モジュールをクリーンにビルドして、R8 と AAPT2 が参照不足の警告なしで完了することを確認します。
m <MODULE_NAME>atestを使用して、モジュールの単体テストとインストルメンテーション テストを実行します。atest <TEST_MODULE_NAME>システムアプリ、特権サービス、ハードウェア依存モジュールの場合、ターゲットの実機またはデバイス テストラボでインストルメンテーション テストと UI テストを実行して、ランタイム リフレクション パス、IPC バインディング、リソース インフレーションをテストします。
DEX とリソースの差分を検査する
ルール変更の前後にコンパイルされた APK または JAR を比較して、R8 が想定されるエントリ ポイントを削除せずに、デッドコードとリソースを削除していることを確認します。
apkanalyzerまたはdexdumpを使用して、出力 APK または DEX ファイル内の保持されているクラス、メソッド、フィールドを検査します。apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkkeep.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 ルールを分析するには:
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 ルールまたはグローバル ベースライン ルールに完全に包含されている場合は、安全に削除できます。
- パッケージ全体およびグローバル ルール: パッケージ全体の幅広いワイルドカードを使用するルール、またはグローバル構成ディレクティブを適用するルール。
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 を組み合わせてビルド内のすべてのルールの正確なクラス、フィールド、メソッドの保持半径を測定できます。