プロダクト ニュース

R8 の保持ルールを構成してトラブルシューティングする

所要時間: 7 分

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

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

この記事を読んだら、YouTube のパフォーマンス スポットライト ウィークの動画で、R8 オプティマイザーの有効化、デバッグ、トラブルシューティングについて確認してください。

 

 

Keep ルールが必要な理由

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

R8 は、直接呼び出しを分析して、使用されているコードのグラフを構築します。コードが動的にアクセスされる場合、R8 の静的解析ではそれを予測できず、そのコードが未使用であると特定されて削除され、実行時のクラッシュにつながります。

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

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

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

アプリのカスタム Keep ルールはテキスト ファイルに記述します。慣例により、このファイルは 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 ファイルはサポートされなくなりました。最適化されたバージョンにアップグレードしてください。

保持ルールの作成方法

保持ルールは、次の 3 つの主要な部分で構成されています。

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

構文と例については、保持ルールを追加するをご覧ください。

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

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

グローバル オプション

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

-dontotptimize を使用すると、R8 のパフォーマンス最適化が事実上無効になり、アプリの動作が遅くなります。

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

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

保持ルールが広すぎる

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

  -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 つの間違いは、アプリの ActivitiesServices、または BroadcastReceivers の Keep ルールを手動で追加することです。これは不要です。デフォルトの proguard-android-optimize.txt ファイルには、これらの標準 Android コンポーネントがすぐに動作するための関連ルールがすでに含まれています。

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

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 など)を作成し、それを使用してフィールドを保持する必要があるクラスに「タグ付け」します。これは、クリーンで宣言的で、スケーラビリティの高い別のパターンです。使用しているフレームワークの既存のアノテーションに対して Keep ルールを作成することもできます。

  # Keep all fields of any class annotated with @Serialize

-keepclassmembers class * {

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

}

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

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

Keep Option機能
-keepクラスと宣言で言及されているメンバーが削除または名前変更されないようにします。 
-keepclassmembers指定されたメンバーの削除や名前の変更を禁止しますが、クラス自体は削除できます。ただし、削除できるのは、他の方法で削除されていないクラスのみです。
-keepclasseswithmembers組み合わせ: 指定されたメンバーがすべて存在する場合にのみ、クラスとそのメンバーを保持します。

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

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

allowshrinkingallowobfuscation などの修飾子は、広範な -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 を使用する場合は、必要なキープルールをライブラリの利用者に提供する責任があります。これらのルールは consumer-rules.pro ファイルに配置され、ライブラリの AAR ファイル内に自動的にバンドルされます。

  android {

    defaultConfig {

        consumerProguardFiles("consumer-rules.pro")

    }

    ...

}

ライブラリ コンシューマーの場合

問題のある保持ルールを除外する

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

  release {

    optimization.keepRules {

        // Ignore all consumer rules from this specific library

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

    }

}

最適な保持ルールは保持ルールがないことです

R8 の最終的な構成戦略は、Keep ルールを記述する必要性を完全に排除することです。多くのアプリでは、リフレクションよりもコード生成を優先する最新のライブラリを選択することで、この目標を達成できます。コード生成を使用すると、オプティマイザーは実行時に実際に使用されるコードと削除できるコードをより簡単に特定できます。また、動的リフレクションを使用しないということは、「隠し」エントリ ポイントがないため、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 アプリのパフォーマンスを向上させるための強力なツールです。その有効性は、静的分析エンジンとしての動作を正しく理解しているかどうかにかかっています。

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

パフォーマンス スポットライト ウィークの最新情報をチェックしながら、YouTube で今日のスポットライト ウィークの動画を視聴して、R8 チャレンジを続けましょう。R8 の有効化やトラブルシューティングに関する質問には、#optimizationEnabled を使用してください。Google がお手伝いいたします。

ご自身の目で特典をご確認ください。

今すぐアプリの R8 フルモードを有効にすることをおすすめします。

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

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

質問がある場合は、ソーシャル タグ #AskAndroid を付けてお寄せください。専門家が 1 週間を通して質問をモニタリングし、回答します。

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

作成者:

続きを読む