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

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

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

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

keep ルールの種類について

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

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

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

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

最適化の要件を遵守する

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

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

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

  • 不適切なグローバル ルールを使用しない: グローバル オプション は絶対に使用しないでください。たとえば、 -dontobfuscate-allowaccessmodification などです。

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

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

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

    最近の多くのライブラリでは、リフレクションではなく codegen が使用されています。Room、Dagger2 などで使用される一般的なエントリ ポイントについては、KSP をご覧ください。

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

その他の推奨事項

最適化の要件に加えて、次の推奨事項があります。

  • ライブラリのコンシューマー keep ルールファイルで -repackageclasses を使用しないでください。 ただし、ライブラリのビルドを最適化するには、-repackageclasses 内部パッケージ名(<your.library.package>.internalなど)を指定して ライブラリのビルド keep ルールファイルでを使用できます。これにより、最適化されていないアプリでのライブラリの効率が向上します。ただし、アプリも最適化する必要があるため、通常は必要ありません。
  • ライブラリが機能するために必要な属性は、proguard-android-optimize.txt で定義されている属性と重複する可能性がある場合でも、ライブラリの keep ルールファイルで宣言します。
  • ライブラリ ディストリビューションで次の属性が必要な場合は、ライブラリのコンシューマー keep ルールファイルではなく、ライブラリのビルド keep ルールファイルで保持します。
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • ライブラリ作成者は、実行時にアノテーションが使用される場合は、コンシューマー keep ルールで RuntimeVisibleAnnotations 属性を保持する必要があります。
  • ライブラリ作成者は、コンシューマー keep ルールで次のグローバル オプションを使用しないでください。
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

リフレクションが適切な場合

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

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

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

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

よくある誤解

いくつかのよくある誤解により、R8 の構成が誤っている可能性があります。次に例を示します。

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

  • 難読化されたライブラリの最適化をバイパスする: ライブラリが AAR(Android アーカイブ)または JAR(Java アーカイブ)にコンパイルされたときに最適化または 難読化されたため、ライブラリを最適化から除外するという誤りがよくあります。ライブラリのビルド時間の最適化は制限されており、keep ルールに含めることでライブラリの最適化を無効にしないでください。詳しくは、AAR ライブラリのビルドを最適化するをご覧ください。

  • -keep オプションに関する誤解 -keep ルールを使用すると、 R8 は最適化パスを実行できなくなります。詳しくは、 適切な keep オプションを選択するをご覧ください。

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

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

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 の正しい場所にルールがバンドルされます。

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

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 はライブラリにパッケージ化され、ライブラリを使用するアプリのビルド時に行われる最適化に影響します。

たとえば、ライブラリがリフレクションを使用して内部クラスを構築する場合は、proguardFilesconsumerProguardFiles の両方で 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> バージョンは排他的です。バージョン範囲は通常は連続していますが、重複することもあります。

たとえば、r8r8-upto-8.0.0r8-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 プラグインはレガシーの場所 (proguard.txt(AAR の場合)またはMETA-INF/proguard/<ProGuard-rule-files>( JAR の場合))からルールを選択します。