ライブラリの作成者は、アプリ デベロッパーがライブラリをアプリに簡単に組み込み、質の高いエンドユーザー エクスペリエンスを維持できるようにする必要があります。つまり、ライブラリは、デベロッパーによる追加の設定を必要とせずに 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 構成は、使用するアプリの最終的なバイナリサイズとパフォーマンスにグローバルな影響を与えます。一般的な保持ルールのベスト プラクティスに加えて、ライブラリ作成者は特定の要件に準拠し、追加のガイドラインを考慮する必要があります。
最適化の要件を遵守する
ライブラリの非効率性は、アプリの肥大化、メモリの無駄遣い、起動の遅延、ANR(アプリケーション応答なしエラー)の主な原因となります。ライブラリは、アプリの品質とユーザー エクスペリエンスが大幅に低下しないように、以下の要件に違反しないようにする必要があります。
広範な keep ルールやパッケージ全体の keep ルールがない: ライブラリに、ライブラリまたは別のライブラリのコードの大部分を保持する広範な keep ルールを含めてはなりません。広範な保持ルールは、短期的にはクラッシュを解決するかもしれませんが、ライブラリを使用するすべてのアプリのアプリサイズを肥大化させます。
ライブラリやその他の参照ライブラリのパッケージに、パッケージ全体の keep ルール(
-keep class com.mylibrary.** {*; }など)を含めないでください。このようなルールは、ライブラリを使用するすべてのアプリで、これらのパッケージの最適化を制限します。不適切なグローバル ルールがない:
-dontobfuscateや-allowaccessmodificationなどのグローバル オプションは使用しないでください。可能な限りリフレクションよりもコード生成を使用する: 可能であれば、リフレクションよりもコード生成(codegen)を使用します。コード生成とリフレクションはどちらもプログラミング時にボイラープレート コードを回避する一般的なアプローチですが、コード生成は R8 などのアプリ オプティマイザーとの互換性が高くなっています。
コード生成では、ビルドプロセス中にコードが分析され、変更されます。コンパイル後に大きな変更がないため、オプティマイザーは最終的に必要なコードと安全に削除できるコードを把握しています。
リフレクションでは、コードは実行時に分析および操作されます。コードは実行されるまで最終決定されないため、オプティマイザーは安全に削除できるコードを認識できません。実行時にリフレクションを介して動的に使用されるコードが削除され、ユーザーのアプリがクラッシュする可能性があります。
最近のライブラリの多くは、リフレクションではなくコード生成を使用しています。Room、Dagger2 など多くのライブラリで使用される共通のエントリ ポイントについては、KSP をご覧ください。
R8 フルモードをサポートする: R8 フルモードが有効になっている場合、ライブラリがクラッシュしないようにします。R8 のフルモードは、R8 を使用するうえで推奨されるモードであり、2023 年に安定版となった AGP 8.0 以降ではデフォルトとなっています。ライブラリが R8 でクラッシュする場合は、パッケージ全体を保持するのではなく、特定のリフレクションまたは JNI エントリ ポイントを特定して、ターゲット ルールを追加します。
その他の推奨事項
最適化の要件以外に、次の推奨事項があります。
- ライブラリのコンシューマー保持ルール ファイルで
-repackageclassesを使用しないでください。ただし、ライブラリのビルドを最適化するために、ライブラリのビルド保持ルール ファイルで、<your.library.package>.internalなどの内部パッケージ名とともに-repackageclassesを使用できます。これにより、最適化されていないアプリでのライブラリの効率が向上します。ただし、アプリも最適化されるため、通常は必要ありません。 - ライブラリが機能するために必要な属性は、
proguard-android-optimize.txtで定義されている属性と重複する可能性がある場合でも、ライブラリの保持ルールファイルで宣言します。 - ライブラリの配布で次の属性が必要な場合は、ライブラリのビルド keep ルール ファイルで維持し、ライブラリのコンシューマー keep ルール ファイルでは維持しないでください。
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
RuntimeVisibleAnnotations 属性を保持する必要があります。-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 アーカイブ)または JAR(Java アーカイブ)にコンパイルされたときに最適化または難読化されたため、ライブラリを最適化から除外することです。ライブラリのビルド時の最適化は制限されています。アプリで、キープルールの対象に含めることでライブラリの最適化を無効にしないでください。詳細については、AAR ライブラリのビルドを最適化するをご覧ください。
-keepオプションの誤解:-keepルールは、R8 が最適化パスを実行するのを防ぎます。詳細については、適切な保持オプションを選択するをご覧ください。
ルールのパッケージ化を構成する
コンシューマー 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はライブラリにパッケージ化され、ライブラリを使用するアプリのビルド時に後で実行される最適化に影響します。
たとえば、ライブラリがリフレクションを使用して内部クラスを構築する場合、proguardFiles と consumerProguardFiles の両方で保持ルールを定義する必要があるかもしれません。
ライブラリのビルドで -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 バージョン向けに記述されているかを示す r8-from-<X>-upto-<Y> 形式の名前のサブディレクトリが複数存在することがあります。各サブディレクトリには、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>)からルールを選択します。