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

ライブラリの作成者は、アプリ デベロッパーがエンドユーザーに質の高いエクスペリエンスを提供しながら、ライブラリをアプリに簡単に組み込めるようにする必要があります。ライブラリが追加の設定なしで Android の最適化に対応していることを確認するか、ライブラリが Android での使用に適していない可能性があることを文書化する必要があります。

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

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

リフレクションよりもコード生成を使用する

可能であれば、リフレクションよりもコード生成(codegenを使用します。コード生成とリフレクションはどちらもプログラミング時にボイラープレート コードを回避する一般的なアプローチですが、コード生成は R8 などのアプリ最適化ツールとの互換性が高くなっています。

  • コード生成では、ビルドプロセス中にコードが分析され、変更されます。コンパイル後に大きな変更がないため、オプティマイザーは最終的に必要なコードと安全に削除できるコードを把握しています。
  • リフレクションでは、コードは実行時に分析および操作されます。コードは実行されるまで実際には確定しないため、オプティマイザーは安全に削除できるコードを認識できません。実行時にリフレクションを通じて動的に使用されるコードが削除される可能性があり、ユーザーのアプリがクラッシュする原因になります。

最近のライブラリの多くは、リフレクションではなくコード生成を使用しています。RoomDagger2 などで使用される共通のエントリ ポイントについては、KSP をご覧ください。

リフレクションが許可されている場合

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

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

このようにリフレクションを使用すると、ランタイム コストを抑え、対象のコンシューマー保持ルールを記述できます。

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

ライブラリの保持ルールの種類

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

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

消費者保持ルールを記述する

一般的な保持ルールのガイダンスに加えて、ライブラリ作成者向けの推奨事項を以下に示します。

  • 不適切なグローバル ルールを使用しない - -dontobfuscate-allowaccessmodification などのグローバル設定をライブラリのコンシューマー キープルール ファイルに含めないでください。ライブラリを使用するすべてのアプリに影響します。
  • ライブラリのコンシューマー保持ルール ファイルで -repackageclasses を使用しないでください。ただし、ライブラリのビルドを最適化するために、ライブラリのビルド保持ルール ファイルで、<your.library.package>.internal などの内部パッケージ名とともに -repackageclasses を使用できます。これにより、ライブラリを使用するアプリが最適化されていなくても、ライブラリの効率を高めることができますが、一般的にアプリも最適化されるため、この処理は必要ありません。ライブラリの最適化について詳しくは、ライブラリ作成者向けの最適化をご覧ください。
  • proguard-android-optimize.txt で定義されている属性と重複する可能性がある場合でも、ライブラリが機能するために必要な属性は、ライブラリの保持ルールファイルで宣言します。
  • ライブラリの配布で次の属性が必要な場合は、ライブラリのビルド キープルール ファイルでそれらを保持し、ライブラリのコンシューマー キープルール ファイルでは保持しないでください。
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • ライブラリ作成者は、実行時にアノテーションが使用される場合は、コンシューマーの保持ルールに RuntimeVisibleAnnotations 属性を保持する必要があります。
  • ライブラリの作成者は、コンシューマーの保持ルールで次のグローバル オプションを使用しないでください。
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -overloadaggressively
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

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 ライブラリのビルドを最適化する(上級者向け)

通常、ライブラリ ビルド時に可能な最適化は非常に限られているため、ライブラリ ビルドを直接最適化する必要はありません。ライブラリがアプリケーションの一部として含まれるアプリケーション ビルド時にのみ、R8 はライブラリのすべてのメソッドがどのように使用され、どのパラメータが渡されるかを知ることができます。ライブラリ デベロッパーは、ライブラリを最適化する前に、最適化の複数の段階について検討し、ライブラリとアプリのビルド時の両方で動作を維持する必要があります。

ビルド時にライブラリを最適化したい場合は、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 の両方で保持ルールを定義する必要があるかもしれません。

ライブラリのビルドで -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.0r8-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>)からルールを選択します。