ライブラリ作成者向けの最適化

ライブラリ作成者は、アプリ デベロッパーがライブラリをアプリに簡単に組み込めるようにすると同時に、エンドユーザーに高品質なエクスペリエンスを提供できるようにする必要があります。つまり、ライブラリは、デベロッパーによる追加の設定を必要とせずに Android の最適化(R8)と互換性がある必要があります。または、ライブラリが Android での使用に適していない可能性があることをドキュメントに記載する必要があります。Android での使用を目的としたライブラリは、重要なアプリの最適化を妨げず、追加の最適化要件に準拠していることが重要です。

このドキュメントは、公開ライブラリのデベロッパーを対象としていますが、大規模なモジュール化されたアプリの内部ライブラリ モジュールのデベロッパーにも役立つ可能性があります。

アプリ デベロッパーで、Android アプリの最適化について学習したい場合は、アプリの最適化を有効にするをご覧ください。使用に適したライブラリについては、ライブラリを賢く選択するをご覧ください。

保持ルールの種類について

ライブラリには、次の 2 種類の保持ルールを設定できます。

  • コンシューマーの保持ルールでは、ライブラリが反映するものを保持するルールを指定する必要があります。ライブラリがリフレクションまたは JNI を使用して、そのコードまたはクライアント アプリで定義されたコードを呼び出す場合、これらのルールでは、保持する必要があるコードを記述する必要があります。ライブラリは、アプリの保持ルールと同じ形式を使用するコンシューマー保持ルールをパッケージ化する必要があります。これらのルールはライブラリ アーティファクト(AAR または JAR)にバンドルされ、ライブラリが使用されると、Android アプリの最適化中に自動的に使用されます。これらのルールは、build.gradle.kts(または build.gradle)ファイルの consumerProguardFiles プロパティで指定されたファイルで管理されます。詳細については、コンシューマー保持ルールを作成するをご覧ください。
  • ライブラリ ビルドの保持ルールは、ライブラリのビルド時に適用されます。これらは、ビルド時にライブラリを部分的に最適化する場合にのみ必要です。ライブラリの公開 API が削除されないようにする必要があります。削除されると、公開 API がライブラリ ディストリビューションに存在しなくなり、アプリ デベロッパーがライブラリを使用できなくなります。これらのルールは、build.gradle.kts(または build.gradle)ファイルの proguardFiles プロパティで指定されたファイルで管理されます。詳細については、AAR ライブラリ ビルドを最適化するをご覧ください。

最適化の要件とガイドライン

ライブラリの R8 構成は、それを使用するユーザーアプリの最終的なバイナリサイズとパフォーマンスにグローバルな影響を及ぼします。一般的な keep ルールのベスト プラクティスに加えて、ライブラリ作成者は特定の要件を遵守し、追加のガイドラインを考慮する必要があります。

最適化の要件に従う

ライブラリの非効率性は、アプリの肥大化、メモリの浪費、起動の遅延、ANR(アプリケーション応答なしエラー)の主な原因です。ライブラリは、アプリの品質とユーザー エクスペリエンスを大幅に低下させないように、次の要件に違反しないようにする必要があります。

  • 広範な keep ルールやパッケージ全体の keep ルールは使用しない: ライブラリには、ライブラリ内のほとんどのコードを保持する広範な keep ルールや、別のライブラリ内のほとんどのコードを保持する広範な keep ルールを含めないでください。広範な keep ルールは、短期的にはクラッシュを解決するかもしれませんが、ライブラリを使用するすべてのアプリのアプリサイズを肥大化させます。

    ライブラリ内のパッケージや、参照されている他のライブラリのパッケージに、パッケージ全体の keep ルール(-keep class com.mylibrary.** {*; } など)を含めないでください。このようなルールは、ライブラリを使用するすべてのアプリで、これらのパッケージの最適化を制限します。

    ライブラリが使用するサードパーティの依存関係(-keep public class com.google.android.gms.** { *; } など)に、広範な keep ルールを含めないでください。多くの外部ライブラリは、独自のコンシューマー keep ルールをすでに提供しています。パッケージ全体のルールを導入すると、それらのルールがオーバーライドされ、R8 はアプリケーション全体で参照されていない依存関係コードを保持することになります。

  • 不適切なグローバル ルールを使用しない: -dontobfuscate や -allowaccessmodification などのグローバル オプションは使用しないでください。

  • 可能な限りリフレクションではなく codegen を使用する: 可能な限り、リフレクションではなくコード生成(codegen)を使用します。コード生成とリフレクションは、どちらもプログラミング時にボイラープレート コードを回避するための一般的なアプローチですが、コード生成の方が R8 などのアプリ オプティマイザーとの互換性が高くなります。

    codegen を使用すると、ビルドプロセス中にコードが分析され、変更されます。コンパイル後に大きな変更はないため、オプティマイザーは最終的に必要なコードと、安全に削除できるコードを把握できます。

    リフレクションを使用すると、コードは実行時に分析および操作されます。コードは実行されるまで最終的に確定されないため、オプティマイザーはどのコードを安全に削除できるかを判断できません。実行時にリフレクションを介して動的に使用されるコードが削除される可能性が高く、ユーザーのアプリがクラッシュする原因になります。

    最新のライブラリの多くは、リフレクションではなく codegen を使用しています。Room、Hilt などで使用される共通のエントリ ポイントについては、KSP をご覧ください。

  • R8 フルモードをサポートする: R8 フルモードが有効になっているときにライブラリがクラッシュしないようにする必要があります。R8 のフルモードは、R8 を使用する際におすすめのモードです。2023 年に安定版となった AGP 8.0 以降では、デフォルトのモードとなっています。R8 でライブラリがクラッシュする場合の解決策は、特定のリフレクションまたは JNI エントリ ポイントを特定し、パッケージ全体を保持するのではなく、ターゲットを絞ったルールを追加することです。これらのエントリ ポイントのアノテーションからターゲット ルールを生成するには、Jetpack Keep を使用します。

その他の推奨事項

最適化の要件とは別に、次の推奨事項があります。

  • ライブラリのコンシューマー保持ルールファイルで -repackageclasses を使用しないでください。コンシューマー keep ルールは、ライブラリを使用するすべてのアプリの R8 構成に統合されるため、このグローバル オプションはアプリ全体に適用され、アプリ デベロッパー独自の再パッケージ化設定をオーバーライドします。デフォルトでは、AGP 9.0 以降、コンシューマーの保持ルールにグローバル オプションが含まれていると、ライブラリの公開は失敗します。詳細については、AGP 9.0 のリリースノートをご覧ください。

    ライブラリのビルドを最適化するには、ライブラリのビルド保持ルールファイルで、-repackageclasses を内部パッケージ名(<your.library.package>.internal など)とともに使用します。これにより、最適化されていないアプリでのライブラリの効率が向上しますが、アプリも最適化する必要があるため、通常は必要ありません。

  • proguard-android-optimize.txt で定義されている属性と重複する可能性がある場合でも、ライブラリの keep ルールファイルでライブラリが機能するために必要な属性を宣言します。

  • ライブラリの配布で次の属性が必要な場合は、ライブラリのコンシューマー keep ルール ファイルではなく、ライブラリのビルド keep ルール ファイルでそれらの属性を維持します。

    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • ライブラリの作成者は、実行時にアノテーションが使用される場合、コンシューマーの保持ルールに RuntimeVisibleAnnotations 属性を保持する必要があります。

  • ライブラリ作成者は、コンシューマーの keep ルールで次のグローバル オプションを使用しないでください。-repackageclasses と同様に、これらのオプションはライブラリのコードだけでなく、アプリの R8 ビルド全体を構成するため、アプリ デベロッパーに任せます。

    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

反射が問題ない場合

リフレクションを使用する必要がある場合は、次のいずれかにのみリフレクションする必要があります。

  • 特定のターゲティング タイプ(特定のインターフェース実装者またはサブクラス)
  • 特定のランタイム アノテーションを使用するコード

このようにリフレクションを使用すると、ランタイム コストが制限され、ターゲットを絞ったコンシューマー保持ルールを記述できます。

この特定のターゲットを絞った形式のリフレクションは、Android フレームワーク(アクティビティ、ビュー、ドローアブルをインフレートする場合など)と AndroidX ライブラリ(WorkManager ListenableWorkers や RoomDatabases を構築する場合など)の両方で見られるパターンです。これに対し、Gson のオープンエンド リフレクションは、Android アプリでの使用には適していません。

よくある誤解

よくある誤解が原因で、R8 の構成が不適切になることがあります。次に例を示します。

  • R8 の最適化に関する誤解: 一般的な理解とは異なり、R8 の最適化は難読化だけでなく、コード圧縮や、メソッドのインライン化とクラスの統合手法による論理的な最適化も含まれます。詳細については、R8 の最適化の概要をご覧ください。

  • 難読化されたライブラリの最適化をバイパスする: よくあるエラーは、ライブラリが AAR(Android Archive)または JAR(Java Archive)にコンパイルされたときに最適化または難読化されたため、最適化からライブラリを省略することです。ライブラリのビルド時の最適化は制限されており、アプリは、ライブラリを keep ルールに含めることでライブラリの最適化を無効にすべきではありません。詳細については、AAR ライブラリのビルドを最適化するをご覧ください。

  • -keep オプションの誤った理解: -keep ルールは、R8 が最適化パスを実行しないようにします。詳細については、適切な保持オプションを選択するをご覧ください。

コンシューマーの保持ルールの品質を検証する

ライブラリをアプリに埋め込んだときに、コードの最適化が過度に妨げられないことを検証するには、ライブラリ サンプルまたは統合テストアプリを使用します。R8 を有効にして、R8 Configuration Analyzer でアプリの R8 構成の全体的な品質を検査します。

コンシューマー ルールがライブラリのコードの大部分で最適化を制限していることが示されている場合、コンシューマー ルールを改良する必要があることを示しています。コンシューマー ルールを絞り込み、R8 が有効になっているアプリで、影響を受けるライブラリの要素をテストします。

ルール パッケージを構成する

コンシューマー保持ルールが正しく適用されるようにするには、ライブラリの形式に応じて適切にパッケージ化する必要があります。

Jetpack Keep を使用してライブラリのリフレクションと JNI の使用を宣言すると、その Gradle プラグインは、生成されたルールをコンシューマーの保持ルールとしてパッケージ化できます。

AAR ライブラリ

AAR ライブラリのコンシューマー ルールを追加するには、Android ライブラリ モジュールのビルド スクリプトで consumerProguardFiles オプションを使用します。詳細については、ライブラリ モジュールの作成に関するガイダンスをご覧ください。

Kotlin

android {
    defaultConfig {
        consumerProguardFiles("consumer-proguard-rules.pro")
    }
    ...
}

Groovy

android {
    defaultConfig {
        consumerProguardFiles 'consumer-proguard-rules.pro'
    }
    ...
}

JAR ライブラリ

JAR として提供される Kotlin または Java ライブラリにルールをバンドルするには、任意のファイル名で、最終的な JAR の META-INF/proguard/ ディレクトリにルールファイルを配置します。たとえば、コードが <libraryroot>/src/main/kotlin にある場合は、コンシューマー ルール ファイルを <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro に配置すると、ルールは出力 JAR の正しい場所にバンドルされます。

ルールが META-INF/proguard ディレクトリにあることを確認して、最終的な JAR がルールを正しくバンドルしていることを確認します。

AAR ライブラリのビルドを最適化する(上級者向け)

一般に、ライブラリのビルド時に可能な最適化は非常に限られているため、ライブラリのビルドを直接最適化する必要はありません。ライブラリ デベロッパーは、最適化の複数の段階について推論し、ライブラリを最適化する前に、ライブラリとアプリのビルド時の両方で動作を維持する必要があります。

ビルド時にライブラリを最適化する場合は、Android Gradle プラグインでサポートされています。

Kotlin

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        configureEach {
            consumerProguardFiles("consumer-rules.pro")
        }
    }
}

Groovy

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                'proguard-rules.pro'
        }
        configureEach {
            consumerProguardFiles "consumer-rules.pro"
        }
    }
}

proguardFiles の動作は consumerProguardFiles とは大きく異なります。

  • proguardFiles は、ビルド時に getDefaultProguardFile("proguard-android-optimize.txt") と組み合わせて使用されることが多く、ライブラリのビルド中に保持するライブラリの部分を定義します。少なくとも、これは公開 API です。
  • 一方、consumerProguardFiles はライブラリにパッケージ化され、ライブラリを使用するアプリのビルド時に、後で行われる最適化に影響します。

たとえば、ライブラリがリフレクションを使用して内部クラスを構築する場合、proguardFiles と consumerProguardFiles の両方で keep ルールを定義する必要があります。

ライブラリのビルドで -repackageclasses を使用する場合は、ライブラリのパッケージ内のサブパッケージにクラスを再パッケージ化します。たとえば、-repackageclasses 'internal' ではなく -repackageclasses 'com.example.mylibrary.internal' を使用します。

さまざまな R8 バージョンをサポートする(上級者向け)

ルールをカスタマイズして、特定のバージョンの R8 をターゲットに設定できます。これにより、新しい R8 バージョンを使用するプロジェクトでライブラリが最適に動作するようになり、古い R8 バージョンを使用するプロジェクトでは既存のルールを引き続き使用できます。

ターゲット R8 ルールを指定するには、AAR の classes.jar 内の META-INF/com.android.tools ディレクトリ、または JAR の META-INF/com.android.tools ディレクトリにルールを含める必要があります。

In an AAR library:
    proguard.txt (legacy location, the file name must be "proguard.txt")
    classes.jar
    └── META-INF
        └── com.android.tools (location of targeted R8 rules)
            ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
            └── ... (more directories with the same name format)

In a JAR library:
    META-INF
    ├── proguard/<ProGuard-rule-files> (legacy location)
    └── com.android.tools (location of targeted R8 rules)
        ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
        └── ... (more directories with the same name format)

META-INF/com.android.tools ディレクトリには、r8-from-<X>-upto-<Y> 形式の名前を持つ複数のサブディレクトリを配置して、ルールがどの R8 バージョン用に記述されているかを示すことができます。各サブディレクトリには、R8 ルールを含む 1 つ以上のファイルを、任意のファイル名と拡張子で配置できます。

-from-<X> 部分と -upto-<Y> 部分は省略可能です。<Y> バージョンは排他的です。バージョン範囲は通常は連続していますが、重複することもあります。

たとえば、r8、r8-upto-8.0.0、r8-from-8.0.0-upto-8.2.0、r8-from-8.2.0 は、ターゲット R8 ルールのセットを表すディレクトリ名です。r8 ディレクトリのルールは、どの R8 バージョンでも使用できます。r8-from-8.0.0-upto-8.2.0 ディレクトリのルールは、バージョン 8.0.0 から 8.2.0 より前までの R8 で使用できます。

Android Gradle プラグインは、その情報を使用して、現在の R8 バージョンで使用できるすべてのルールを選択します。ライブラリでターゲット R8 ルールが指定されていない場合、Android Gradle プラグインは以前の場所(AAR の場合は proguard.txt、JAR の場合は META-INF/proguard/<ProGuard-rule-files>)からルールを選択します。