サービスに関する最新情報

R8 Keep ルールの構成とトラブルシューティング

所要時間: 7 分

最新の Android 開発では、小さく、高速で、安全なアプリケーションをリリースすることが、ユーザーの基本的な期待となっています。これを実現するための Android ビルドシステムの主なツールは、R8  オプティマイザーです。これは、圧縮、コードの名前変更または圧縮、アプリの最適化のために、デッドコードとリソースの削除を処理するコンパイラです。

R8 を有効にすることは、アプリをリリースする準備において重要なステップですが、デベロッパーは「保持ルール」の形式でガイダンスを提供する必要があります。

この記事を読んだら、YouTube で R8 オプティマイザーを有効化、デバッグ、トラブルシューティングする方法に関する Performance Spotlight Week の動画をご覧ください。

 

 

Keep ルールが必要な理由

保持ルールを記述する必要性は、根本的な競合に起因しています。R8 は静的解析ツールですが、Android アプリは多くの場合、リフレクションや、JNI(Java Native Interface)を使用したネイティブ コードとの間の呼び出しなどの動的実行パターンに依存しています。

R8 は、直接呼び出しを分析して、使用されているコードのグラフを作成します。コードに動的にアクセスする場合、R8 の静的解析ではそれを予測できないため、そのコードは未使用と識別されて削除され、ランタイム クラッシュが発生します。

保持ルールは、R8 コンパイラに対する明示的な指示です。このルールは、「この特定のクラス、メソッド、フィールドは、実行時に動的にアクセスされるエントリ ポイントである。直接参照が見つからない場合でも、保持する必要があります。」

保持ルールの詳細については、公式ガイドをご覧ください。

保持ルールを記述する場所

アプリケーションのカスタム保持ルールはテキスト ファイルに記述されます。慣例により、このファイルの名前は proguard-rules.pro で、アプリまたはライブラリ モジュールのルートに配置されます。このファイルは、モジュールの build.gradle.kts ファイルの release ビルドタイプで指定されます。

release {

    isShrinkResources = true

    isMinifyEnabled = true

    proguardFiles(

        getDefaultProguardFile("proguard-android-optimize.txt"),

        "proguard-rules.pro",

    )

}

正しいデフォルト ファイルを使用する

getDefaultProguardFile メソッドは、Android SDK が提供するデフォルトのルールセットをインポートします。間違ったファイルを使用すると、アプリが最適化されない可能性があります。必ず proguard-android-optimize.txt を使用してください。このファイルは、標準の Android コンポーネントのデフォルトの Keep ルールを提供し、R8 のコード最適化を有効にします。古い proguard-android.txt は Keep ルールのみを提供し、R8 の最適化は有効にしません。

progaurd.png

これは重大なパフォーマンスの問題であるため、Android Studio Narwhal 3 Feature Drop 以降、間違ったファイルを使用しているデベロッパーに警告が表示されるようになります。また、Android Gradle プラグイン バージョン 9.0 以降では、古い proguard-android.txt ファイルはサポートされなくなります。最適化されたバージョンにアップグレードしてください。

保持ルールの作成方法

keep ルールは、主に次の 3 つの部分で構成されています。

  1. -keep や -keepclassmembers などのオプション 
  2. オプションの修飾子(allowshrinking など)
  3. 一致するコードを定義するクラス仕様

完全な構文と例については、保持ルールを追加するのガイダンスをご覧ください。

Keep ルールのアンチパターン

ベスト プラクティスだけでなく、アンチパターンについても知っておくことが重要です。これらのアンチパターンは、誤解やトラブルシューティングのショートカットから生じることが多く、本番環境ビルドのパフォーマンスに致命的な影響を及ぼす可能性があります。

グローバル オプション

これらのフラグはグローバルな切り替えであり、リリースビルドでは決して使用しないでください。これらは、問題を特定するための一時的なデバッグにのみ使用されます。

-dontotptimize を使用すると、R8 のパフォーマンス最適化が無効になり、アプリの速度が低下します。

-dontobfuscate を使用すると、すべての名前変更が無効になり、-dontshrink を使用すると、デッドコードの削除が無効になります。どちらのグローバル ルールもアプリのサイズを増加させます。

アプリのユーザー エクスペリエンスのパフォーマンスを向上させるため、本番環境ではこれらのグローバル フラグをできる限り使用しないでください。

広すぎる keep ルール

R8 のメリットを無効にする最も簡単な方法は、過度に広範な Keep ルールを記述することです。以下のような keep ルールは、このパッケージ内のすべてのクラスまたはそのサブパッケージのすべてを圧縮、難読化、最適化しないよう R8 オプティマイザーに指示します。これにより、そのパッケージ全体に対する R8 のメリットが完全に失われます。代わりに、範囲を絞った具体的な保持ルールを作成してください。

-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMS

反転演算子(!)

反転演算子(!)は、ルールからパッケージを除外する強力な方法のように見えます。しかし、それほど単純ではありません。例を見てみましょう

-keep class !com.example.my_package.** { *; } // USE WITH CAUTION

このルールは、「com.example.package 内のクラスを保持しない」という意味だと思うかもしれません。しかし、実際には「com.example.package 内にないアプリ全体のすべてのクラス、メソッド、プロパティを保持する 」という意味です。このことに驚いた場合は、R8 構成に否定がないか確認することをおすすめします。

Android コンポーネントの冗長ルール

もう 1 つのよくある間違いは、アプリの Activities、Services、BroadcastReceivers の保持ルールを手動で追加することです。これは不要です。デフォルトの proguard-android-optimize.txt ファイルには、これらの標準 Android コンポーネントがすぐに動作するように、関連するルールがすでに含まれています。

また、多くのライブラリには独自の Keep ルールが用意されています。そのため、これらに対して独自のルールを作成する必要はありません。使用しているライブラリの Keep ルールに問題がある場合は、ライブラリの作成者に連絡して問題を確認することをおすすめします。

Keep ルールのベスト プラクティス

避けるべきことがわかったところで、ベスト プラクティスについて説明します。

保持ルールを限定的に記述する

適切な保持ルールは、できるだけ範囲を絞り、具体的にする必要があります。必要なものだけを保持し、それ以外はすべて R8 で最適化できるようにする必要があります。

ルール品質

 

-keep class com.example.** { ; }

 

低: パッケージ全体とそのサブパッケージを保持します。

 

-keep class com.example.MyClass { ; }

 

低: クラス全体を保持します。これは、まだ広すぎる可能性があります。
-keepclassmembers class com.example.MyClass {

    private java.lang.String secretMessage;

    public void onNativeEvent(java.lang.String);

}
高: 特定のクラスの関連するメソッドとプロパティのみが保持されます。

共通の祖先を使用する

複数の異なるデータモデルに対して個別の Keep ルールを記述するのではなく、共通の基本クラスまたはインターフェースをターゲットとするルールを 1 つ記述します。以下のルールは、このインターフェースを実装するクラスのメンバーを保持するように R8 に指示するもので、拡張性に優れています。

# Keep all fields of any class that implements SerializableModel

-keepclassmembers class * implements com.example.models.SerializableModel {

    <fields>;

}

アノテーションを使用して複数のクラスをターゲットにする

カスタム アノテーション(@Serialize など)を作成し、それを使用して、フィールドを保持する必要があるクラスに「タグ付け」します。これは、クリーンで宣言型、かつスケーラビリティに優れたもう 1 つのパターンです。使用しているフレームワークの既存のアノテーションに対して Keep ルールを作成することもできます。

# Keep all fields of any class annotated with @Serialize

-keepclassmembers class * {

    @com.example.annotations.Serialize <fields>;

}

適切な Keep オプションを選択する

保持オプションは、ルールの最も重要な部分です。間違ったものを選択すると、最適化が不必要に無効になる可能性があります。

Keep オプション機能
-keep宣言で指定されたクラスとメンバーが削除または名前変更されないようにします。 
-keepclassmembers指定されたメンバーの削除や名前変更を防止します。ただし、クラス自体の削除は許可されます(削除されていないクラスのみ)。
-keepclasseswithmembers組み合わせ: 指定されたすべてのメンバーが存在する場合にのみ、クラスとそのメンバーを保持します。

keep オプションの詳細については、keep オプションのドキュメントをご覧ください。

修飾子を使用した最適化を許可する

allowshrinking や allowobfuscation などの修飾子を使用すると、広範な -keep ルールが緩和され、R8 の最適化機能が回復します。たとえば、以前のライブラリでクラス全体に -keep を使用する必要がある場合、圧縮と難読化を許可することで、最適化をある程度取り戻すことができます。

# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it.

-keep,allowshrinking,allowobfuscation class com.example.LegacyClass

追加の最適化のためのグローバル オプションを追加する

Keep ルール以外にも、R8 構成ファイルにグローバル フラグを追加して、最適化をさらに促進できます。

-repackageclasses は、難読化されたすべてのクラスを 1 つのパッケージに移動するよう R8 に指示する強力なオプションです。これにより、冗長なパッケージ名文字列が削除され、DEX ファイルの容量が大幅に節約されます。

-allowaccessmodification を指定すると、R8 はアクセスを拡大(private から public など)して、より積極的なインライン化を可能にします。proguard-android-optimize.txt を使用する場合、この機能はデフォルトで有効になります。

警告: ライブラリ作成者は、これらのグローバル最適化フラグをコンシューマー ルールに追加してはなりません。追加すると、アプリ全体に強制的に適用されます。

さらに、Android Gradle プラグインのバージョン 9.0 では、ライブラリのグローバル最適化フラグが完全に無視されるようになります。

ライブラリのベスト プラクティス

すべての Android アプリは、何らかの形でライブラリに依存しています。では、ライブラリのベスト プラクティスについて説明します。

ライブラリ デベロッパー向け

ライブラリでリフレクションまたは JNI を使用している場合は、必要な Keep ルールをコンシューマーに提供する必要があります。これらのルールは consumer-rules.pro ファイルに配置され、ライブラリの AAR ファイル内に自動的にバンドルされます。

android {

    defaultConfig {

        consumerProguardFiles("consumer-rules.pro")

    }

    ...

}

ライブラリのユーザー向け

問題のある Keep ルールを除外する

問題のある Keep ルールを含むライブラリを使用する必要がある場合は、AGP 9.0 以降の build.gradle.kts ファイルでフィルタできます。これにより、特定の依存関係から取得されるルールを無視するよう R8 に指示できます。

release {

    optimization.keepRules {

        // Ignore all consumer rules from this specific library

        it.ignoreFrom("com.somelibrary:somelibrary")

    }

}

最適な Keep ルールは Keep ルールなし

R8 構成の究極の戦略は、保持ルールを記述する必要性を完全になくすことです。多くのアプリでは、リフレクションよりもコード生成を優先する最新のライブラリを選択することで、これを実現できます。コード生成により、オプティマイザーは、実行時に実際に使用されるコードと削除できるコードをより簡単に判断できます。また、動的リフレクションを使用しないということは、「隠れた」エントリ ポイントがないということなので、Keep ルールは必要ありません。 新しいライブラリを選択する際は、リフレクションではなくコード生成を使用するソリューションを常に優先してください。

ライブラリの選択方法については、ライブラリを賢く選択するをご覧ください。

R8 構成のデバッグとトラブルシューティング

R8 が保持すべきコードを削除した場合や、APK が想定よりも大きい場合は、これらのツールを使用して問題を診断します。

重複する Keep ルールとグローバル Keep ルールを見つける

R8 は数十のソースからルールをマージするため、「最終的な」ルールセットを把握するのは困難です。このフラグを proguard-rules.pro ファイルに追加すると、完全なレポートが生成されます。

# Outputs the final, merged set of rules to the specified file

-printconfiguration build/outputs/logs/configuration.txt

このファイルを検索して、冗長なルールを見つけたり、問題のあるルール(-dontoptimize など)を、そのルールを含む特定のライブラリまでさかのぼって追跡したりできます。

R8 に尋ねる: なぜこれを保持しているのか?

削除されるはずのクラスがアプリに残っている場合、R8 でその理由を確認できます。次のルールを追加します。

# Asks R8 to explain why it's keeping a specific class

class com.example.MyUnusedClass

-whyareyoukeeping 

ビルド中に、R8 はそのクラスを保持する原因となった参照の正確なチェーンを出力します。これにより、参照をトレースしてルールを調整できます。

詳細なガイドについては、R8 のトラブルシューティングのセクションをご覧ください。

次のステップ

R8 は、Android アプリのパフォーマンスを向上させる強力なツールです。その有効性は、静的分析エンジンとしての動作を正しく理解しているかどうかにかかっています。

メンバーレベルの具体的なルールを記述し、祖先とアノテーションを活用し、適切な keep オプションを慎重に選択することで、必要なものを正確に保持できます。最も高度な方法は、リフレクション ベースのライブラリではなく、codegen ベースの最新のライブラリを選択することで、ルールを完全に不要にすることです。

パフォーマンス スポットライト ウィークの期間中は、YouTube で今日のスポットライト ウィーク動画をチェックして、R8 チャレンジを続けてください。R8 の有効化やトラブルシューティングに関する質問には、#optimizationEnabled を使用します。お知らせください。

ぜひご自身でメリットを実感してください。

今すぐアプリで R8 フルモードを有効にしてみてください。

  1. デベロッパー ガイドに沿って、アプリの最適化を有効にしてください。
  2. proguard-android.txt をまだ使用しているかどうかを確認し、proguard-android-optimize.txt に置き換えます。
  3. 次に、効果を測定します。違いを感じるだけでなく、確認しましょう。 GitHub の Macrobenchmark サンプルアプリのコードを適応させて、起動時間の前後を測定することで、パフォーマンスの向上を測定します。

アプリのパフォーマンスが大幅に向上すると確信しています。

また、ソーシャルタグ #AskAndroid を使用して質問することもできます。1 週間を通して、エキスパートが質問をモニタリングし、回答します。

明日は、ベースライン プロファイルと起動プロファイルを使用したプロファイルに基づく最適化について説明し、過去のリリースで Compose のレンダリング パフォーマンスがどのように改善されたか、バックグラウンド処理のパフォーマンスに関する考慮事項について説明します。

作成者:
続きを読む