Android プラットフォームのビルドシステム(Soong)は、Android アプリ(android_app)、テスト(android_test)、compile_dex: true(services.jar など)を含むインストール可能な Java ライブラリなど、DEX バイトコードをコンパイルするターゲットで R8 コンパイラを実行し、未使用のコードとリソースを縮小、最適化、削除します。静的ライブラリ(android_library と静的 java_library)では、Soong は R8 を直接実行しませんが、optimize プロパティ ブロックを使用して、それらを静的にリンクするダウンストリーム DEX ターゲットにコンシューマー keep ルール(export_proguard_flags_files: true)をアタッチして伝播します。
システム イメージやベンダー パッケージを構築するプラットフォーム エンジニアにとって、広すぎる保持ルールを排除することは、システムの健全性に直接的なメリットをもたらします。
- システム パーティションのフットプリントの縮小: 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 とテスト モジュールの場合、明示的に設定しない限り -dontshrink を渡す false です)。 |
optimize |
メソッドのインライン化、クラスの統合、定数の伝播、デッドブランチの削除などのバイトコードの最適化を制御します。デフォルトは、android_app ターゲットの場合は true(RELEASE_R8_OPTIMIZE_BY_DEFAULT リリースビルドフラグで制御)です。 |
obfuscate |
識別子の最小化と名前の変更を制御します。過去の互換性のためにデフォルトは false ですが、可能な限り true に設定して DEX のサイズを削減し、より深い最適化を可能にします(可能な限り難読化を有効にするを参照)。 |
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 が自動的にトレースして保持する Java ライブラリ コンパニオン ターゲットをリストします。 |
可能な限り難読化を有効にする
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を保持するか、@KeepForApiまたは-keepルール(bootclasspath ターゲットの場合はprotect_api_surface: trueも)を使用して API サーフェスを明示的に保持する必要があります。これにより、スタブに対してコンパイルされた呼び出し元は、実行時にクラス名とメンバー名を解決できます。
アプリケーションで obfuscate: true を有効にする前に、次の移行の前提条件を確認します。
- 外部テスト APK の依存関係: 外部
android_testAPK がinstrumentation_for: "MyApp"を設定し、MyAppの内部クラスまたはメソッドを直接呼び出す場合、それらのシンボルをリネームすると、テスト実行時にNoSuchMethodErrorまたはNoClassDefFoundErrorが発生します。テストを自己計測 APK として構造化し、MyApp.implを静的にリンクするか、テストフックに@VisibleForTestingのアノテーションを付けるか、trace_references_fromを構成(ステップ 2: テスト駆動型キープルールの移行を参照)して、テストに必要な内部シンボルを保持しながら、アプリの残りの部分を難読化することを推奨します。 - 文字列ベースのリフレクションと JNI: モジュールがリテラル文字列名(
Class.forName、getDeclaredMethod、または JNIFindClassとGetMethodID)でクラス、メソッド、フィールドを検索する場合、キープルールやアノテーションがないと、難読化によってこれらのターゲットの名前が変更され、実行時の検索が失敗します。(注: アノテーションのないリフレクションもshrink: trueとoptimize: trueで失敗します)。これらのエントリ ポイントに Keep アノテーションのガイド(@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)、静的テストのリンク設定を優先します(既存の保持ルールの監査と移行を参照)。
カスタム 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 ファイルから移動します。
実装ライブラリをテストにリンクする(
static_libs):android_testがアプリのパッケージ プライベートまたは内部実装の詳細を実行する場合、推奨されるプラットフォーム アーキテクチャは、アプリのソースファイルを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はテストを中断することなくobfuscate: trueを自由に有効にでき、テスト専用のエントリ ポイントが本番環境の APK に含まれるのを防ぎ、テストコードが変更されたときにMyAppを再ビルドする必要がなくなります。@VisibleForTestingでテストフックにアノテーションを付ける: 外部テスト APK がandroid_appターゲットの少数の内部メソッドまたはコンストラクタを呼び出し、.implライブラリへの再構築が現実的でない場合は、それらの宣言に@VisibleForTestingでアノテーションを付けます。グローバルbuild/make/core/proguard.flagsベースラインは、カスタム.flagsファイルなしで、android.**、com.android.**、com.google.android.**パッケージの@VisibleForTestingアイテムを自動的に保持します。(これらの名前空間外のベンダー モジュールについては、@UsedByReflectionまたはエクスポートされたライブラリ ルールを使用してください)。プラットフォーム ライブラリの境界を越えて
trace_references_fromを使用する: テストでターゲット実装を静的にリンクできない場合(システム サーバー サービスやframework-connectivityなどのプラットフォーム JAR を実行するテストなど)、テストソースを含む Java ライブラリ コンパニオン モジュールを指すターゲット モジュールでtrace_references_fromを構成します。R8 は、コンパニオン バイトコードで参照されるすべてのクラスとメンバーをトレースして保持します。
ステップ 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 が正確なリフレクション契約のみを保持し、クラスの未使用メンバーを最適化または削除できるようにします。AAPT2 はマニフェスト登録済みコンポーネント(JobServiceやBroadcastReceiverなど)をすでに保持しているため、それらに@UsedByReflectionを追加しないでください。@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アノテーションを内部の保持ルールモデルに直接変換し、最終的な DEX 出力からアノテーションを削除します。これにより、実行時のバイトコードのオーバーヘッドがゼロになります。
よくある問題の回避
以降のセクションでは、プラットフォーム コードでよくある proguard.flags と keep.xml の落とし穴と、その修正方法について説明します。
Broad コンポーネントの 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 クリック ハンドラの保持ルール
# Don't do this:
-keepclassmembers class * {
public void *(android.view.View);
}
- パフォーマンスが低下する理由: モジュール内のすべてのクラスで
public void *(View)メソッドを保持すると、R8 は 1 つのViewパラメータを受け入れるメソッドを削除したりインライン化したりできなくなります。 - 推奨される修正: ルールを削除します。AAPT2 はレイアウト XML ファイルをスキャンし、
android:onClick属性で参照されるメソッドのターゲット保持ルールを生成します。
Broad View のゲッターとセッターの保持ルール
# 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 ライブラリや Material ライブラリなど)のすべてのゲッターとセッターを保持する必要があり、UI コード全体でデッド メソッドの削除とインライン化がブロックされます。 - 推奨される修正: ルールを削除します。AAPT2 は、レイアウト XML ファイルから拡張されたカスタム
Viewクラスのコンストラクタをすでに保持しています。コードがObjectAnimator.ofFloat(view, "translationZ", ...)などのリフレクション文字列名を使用してビュー プロパティをアニメーション化する場合は、リフレクションを完全に回避するために、文字列名を型付きプロパティ参照(View.TRANSLATION_ZまたはカスタムのFloatPropertyまたはIntProperty実装)に置き換えます。リフレクション プロパティ アクセスを回避できない場合は、特定のゲッターまたはセッターに@UsedByReflectionのアノテーションを付けます。
包括的な最適化または難読化が無効になっている
# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
- パフォーマンスが低下する理由:
.flagsファイル内に-dontoptimizeまたは-dontshrinkを配置すると、モジュールの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 ビルド中にローカルパスに出力ファイルを書き込もうとする。
- 推奨の修正方法: コミットされた
.flagsファイルからこれらのフラグを削除します。Soong は、proguard_dictionaryやproguard_usage.zipなどの R8 マッピングと使用状況の出力を、out/soong/.intermediates/の下のモジュールの中間ディレクトリに自動的に書き込みます。
テストコードの保持ルール
- パフォーマンスが低下する理由: テストアクセス専用のカスタム
-keepルールを追加すると、そのコードは難読化されず、本番環境のビルドに保持されます。@VisibleForTestingでメソッドにアノテーションを付けるか、trace_references_fromを構成すると、手動の-keepルールを維持する必要がなくなりますが、これらのシンボルは引き続き本番環境バイナリで出荷されます。 - 推奨される修正: テスト専用のエントリ ポイントを本番環境の APK から完全に除外するには、ステップ 2: テスト駆動型の保持ルールの移行で説明されているように、アプリケーションを
.implライブラリに構造化し、static_libsを使用して自己インストルメント化テスト APK にリンクします。
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 で Keep 半径とルールの包含を分析する
オープンソースの R8 コンパイラには、コンパイル中にすべての Keep ルールの正確な影響を測定する Keep Radius アナライザ(Android Studio と Gradle では R8 構成アナライザとしても公開されています)が含まれています。Soong はこのアナライザを Android プラットフォーム ビルドに直接統合します(build/soong/java/dex.go で構成)。
プラットフォーム モジュールまたはビルド全体で保持ルールを分析するには:
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ファイルにキープルールの指標を記録するよう指示します。R8 は、各保持ルールとkeepannoアノテーションについて、次の情報を記録します。- 即時保持半径: ルールによって保持される正確なクラス、フィールド、メソッドと、シュリンク、最適化、難読化に対して適用される特定の制約。
- ルールの包含: どの他の保持ルールまたはアノテーションが同じアイテムをすでに保持しているか。カスタムルールが 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 で次の 2 つの R8 診断環境変数もサポートしています。
R8_DUMP_INPUT=true: スタンドアロン R8 再現用のすべての入力 JAR、ライブラリ JAR、マージされた ProGuard 構成を含むモジュールの中間ディレクトリにr8inputs.zipを書き込みます。R8_DUMP_PERFETTO_TRACE=true: Perfetto で R8 コンパイル パスを検査するために、r8trace.ptraceをモジュールの中間ディレクトリに書き込みます。
ライブラリ コンシューマー ルールを検証する
java_library または android_library のエクスポートでルールがダウンストリーム コンシューマー(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 を組み合わせてビルド内のすべてのルールの正確なクラス、フィールド、メソッドの保持半径を測定できます。