Android開発におけるProduct Flavorは、共有コードベースから同じアプリケーションの複数のバリアントを作成できるGradleのメカニズムです。各flavorは独自のapplicationId、リソース、依存関係、機能を持つことができます(例:無料版と有料版)。Google Android Developers、2025年によると、Product FlavorsはBuild Variantsシステムの一部であり、flavorDimensionsを介してBuild Typesと組み合わされます。これはGoogle Playで複数のアプリバージョンを公開するための標準的なアプローチです。
重要なポイント
Product Flavorはandroid.productFlavorsブロック内のGradle設定で、プロダクトのバリアントを記述します。各flavorはapplicationId、versionName、versionCode、minSdkVersion、targetSdkVersion、signingConfig、およびdefaultConfigのその他のパラメータを上書きできます。Product Flavorsに数量制限はありません。プロジェクトには2、5、10個のflavorを含めることができ、Gradleがすべての組み合わせを処理します。
Product Flavorはコードベースの再利用(codebase reuse)の問題を解決します。つまり、単一のリポジトリから複数の異なるアプリケーションをビルドする必要がある場合です。典型的なシナリオ:広告付きの無料版と広告なしの有料版、機能制限付きのデモ版、企業向けと消費者向けのバージョン、さまざまなクライアント向けのホワイトラベルアプリ。Product Flavorsがない場合、各バージョンを別々のプロジェクトで維持する必要があり、60〜70%のコード重複が発生します。
歴史的に、Product FlavorsはAndroid Gradle Plugin 0.9(2013年)でant設定の置き換えとして登場しました。それ以前は、開発者は異なるバージョンに別々のプロジェクトを使用するか、ビルド前に手動でリソースを置き換えていました。AGPへのflavorの導入により、アプローチが統一され、標準になりました。JetBrains、2024年の調査によると、複数バージョンを持つAndroidプロジェクトの78%がProduct Flavorsを使用しており、残りはBuildConfigやリフレクションを介した手動切り替えを使用しています。
Build Typeはビルドプロセス(デバッグ付きのdebug、最適化付きのrelease)を管理します。Product Flavorはビルドコンテンツ(有料機能なしのfree、ありのpaid)を管理します。Build Typeはインフラストラクチャ設定であり、Product Flavorはプロダクト設定です。両方の概念は直交しています。free flavorのdebugビルドは、free flavorのreleaseビルドとはコンパイルパラメータのみが異なり、機能は異なりません。Product Flavorを使用してデバッガを無効にすることはできません。それはBuild Typeの役割です。
Flavor DimensionsはProduct Flavorsを独立したカテゴリにグループ化するメカニズムです。アプリに無料版/有料版と、別にアメリカ/ヨーロッパ地域がある場合、flavorは2つの次元にグループ化されます:「tier」(free、paid)と「region」(us、eu)。Gradleは次元のデカルト積を作成します:freeUs、freeEu、paidUs、paidEu — 4つのバリアント。次元がない場合、Gradleは4つすべてのflavorを単一の平面として扱い、1つしか選択できません。
次元はflavorDimensionsブロックで文字列または文字列リストとして宣言されます。次元の順序はsource setの優先順位に影響します。最初の次元が最も高い優先順位を持ちます。次元A(tier)が最初に指定された場合、リソースの競合が発生したときにsrc/free/がsrc/us/を上書きします。順序はVariant名の形成方法にも影響します。最初に最初の次元のflavor、次に2番目の次元、次にBuild Typeが来ます:freeUsDebug。
次元の数に制限はありませんが、新しい次元ごとにBuild Variantsの数が乗算されます。4つの次元(それぞれ2つのflavor)と2つのbuild typesを持つプロジェクトの場合、2 × 2 × 2 × 2 × 2 = 32のバリアントになります。実用的な制限は3次元(最大8〜12バリアント)です。それを超えると、Gradle設定が遅くなり、Android StudioのBuild Variantsパネルが読みにくくなります。
android {
flavorDimensions "tier", "api"
productFlavors {
free {
dimension "tier"
applicationId "com.example.app.free"
versionNameSuffix "-free"
}
paid {
dimension "tier"
applicationId "com.example.app.paid"
}
minApi21 {
dimension "api"
minSdk 21
}
minApi26 {
dimension "api"
minSdk 26
}
}
}
// 結果:freeMinApi21、freeMinApi26、paidMinApi21、paidMinApi26
// 各×debug/release = 8つのBuild Variants
Product Flavorを作成するには、android内にproductFlavorsブロックを追加し、flavor名とそのパラメータを指定する必要があります。最小限のflavor宣言は名前と次元です。その他のすべてのパラメータはdefaultConfigから継承され、上書きできます。flavorはapplicationId、versionCode、testInstrumentationRunnerを含むdefaultConfigを完全に継承します。
各flavorはapplicationIdを上書きできます。これにより、同じデバイスに複数のアプリバージョンを同時にインストールできます。たとえば、無料版はcom.example.app.free、有料版はcom.example.app.paidになります。applicationIdが上書きされない場合、すべてのflavorは同じ識別子を持ち、並行してインストールできません。applicationIdはマニフェストのパッケージと一致する必要があります(applicationIdSuffixが使用されていない場合)。
AGP 8+はbuild.gradleにGroovyではなくKotlin DSLを使用することを推奨しています。Kotlin DSLは設定への型安全なアクセスを提供します。IDEがパラメータ名を提案し、コンパイル時に型をチェックし、エラーを強調表示します。Product FlavorsのためのGroovyからKotlin DSLへの移行は、通常、引用符を括弧に置き換え、型を追加することで構成されます。AGPは下位互換性があります。両方の構文が同じプロジェクト内で並行して機能します。
// build.gradle.kts — Kotlin DSL
android {
flavorDimensions += "tier"
productFlavors {
register("free") {
dimension = "tier"
applicationId = "com.example.app.free"
versionNameSuffix = "-free"
buildConfigField("boolean", "IS_PREMIUM", "false")
}
register("paid") {
dimension = "tier"
applicationId = "com.example.app.paid"
versionNameSuffix = "-paid"
buildConfigField("boolean", "IS_PREMIUM", "true")
}
}
}
各Product Flavorは独自のsource set — src/<flavorName>/ディレクトリを作成します。このディレクトリには、上書きされたリソース、ソースファイル、マニフェストを含めることができます。flavor source setはmainの上にオーバーレイとして機能します。src/free/res/のファイルは、同じ名前のsrc/main/res/のファイルを上書きします。これにより、メインコードを変更せずに、flavorごとに異なる文字列、アイコン、色、レイアウトを持つことができます。
Java/Kotlinクラスを上書きするには、flavor固有の実装(各flavorで抽象クラスを実装する)とBuildConfigフィールド(コードでの分岐)の2つのアプローチがあります。最初のアプローチの方がクリーンです。mainでインターフェースまたは抽象クラスを定義し、src/free/とsrc/paid/に具体的な実装を配置します。ビルド時には、現在のflavorの実装のみがコンパイルされます。これにより、APKサイズの削減(有料コードが無料版に入らない)とセキュリティ(誤って有料関数を呼び出すことが不可能)という同時の利点が得られます。
flavor source setのAndroidManifest.xmlは置き換えではなく、メインマニフェストとマージされます。マージはAndroidのルールに従います。同じ要素内の重複する属性は上書きされ、一意の属性は追加されます。たとえば、メインマニフェストがINTERNET権限を宣言し、freeが宣言しない場合、インターネット権限は残ります。ただし、tools:node="replace"を使用すると、特定のflavorのマニフェストブロック全体を置き換えることができます。これは、異なるflavorが異なる権限(有料版のSDカード書き込み、無料版のカメラ)を必要とする場合に役立ちます。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:label="Free App"
tools:replace="android:label">
</application>
</manifest>
典型的なシナリオを考えてみましょう。free — 広告と基本機能を備えたバージョン、paid — 広告なし、拡張機能を備えたバージョン。無料版の場合、applicationIdは「com.example.app.free」に設定され、有料版は「com.example.app.paid」に設定されます。applicationIdはAndroidシステムにおけるアプリケーションの一意識別子であるため、両方のバージョンを同じデバイスに同時にインストールできます。
アーキテクチャ的には、分離はインターフェース + flavor実装を通じて構築されます。メインのsource setで、PaymentServiceインターフェースが宣言されます。src/free/には、AdMobを介して支払い前に広告を表示する実装があります。src/paid/には、支払いゲートウェイに直接進む実装があります。PaymentServiceを使用するコードは、どの実装がロードされているかを知りません。これはコンパイル時に解決されます。このアプローチにより、開発者が誤って呼び出したとしても、サブスクリプション管理コードが無料版に入らないことが保証されます。
異なるflavorのAPKサイズは、依存関係の包含/除外により5〜15MB異なる場合があります。特定のflavorからライブラリを除外するには、build.gradleでflavor固有の依存関係を使用します:freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'。この依存関係はfreeバリアントにのみ追加され、有料版のサイズは増加しません。共有依存関係にはimplementationを使用します。すべてのflavorがそれらを含みます。
// src/main/kotlin/com/example/payment/PaymentService.kt
interface PaymentService {
fun processOrder(amount: Double, callback: (PaymentResult) -> Unit)
}
// src/free/kotlin/.../FreePaymentService.kt
class FreePaymentService : PaymentService {
override fun processOrder(amount: Double, callback: (PaymentResult) -> Unit) {
AdManager.showInterstitial {
PaymentGateway.charge(amount, callback)
}
}
}
// src/paid/kotlin/.../PaidPaymentService.kt
class PaidPaymentService : PaymentService {
override fun processOrder(amount: Double, callback: (PaymentResult) -> Unit) {
PaymentGateway.charge(amount, callback)
}
}
マルチモジュールプロジェクトでは、ライブラリモジュールに独自のProduct Flavorsがない場合があり、問題が発生します。ライブラリは1回(releaseとして)ビルドされますが、flavorを持つアプリモジュールは対応するバリアントのライブラリを期待します。AGP 8.1以降、ライブラリはpublishing.multipleVariantsブロックを通じて複数のバリアントを公開できます。これにより、ライブラリのすべてのflavorバリアントを単一のmavenリポジトリに公開でき、アプリモジュールが自動的に正しいものを選択します。
別のアプローチは、ライブラリにアプリモジュールと同じflavorDimensionsとproductFlavorsを宣言することです。AGPは1つの次元内で正確な名前の一致によってflavorを自動的に照合します。ライブラリのflavor名がアプリの名前と一致する場合、AGPは一貫したバリアントを作成します。保守を容易にするために、共通のflavor定義をConvention Pluginに抽出することをお勧めします。これはプロジェクトのすべてのモジュールに適用されるGradleプラグインです。
公開を目的としないライブラリ(内部モジュール)の場合、ルートプロジェクトのbuild.gradleを介してflavorを同期するだけで十分です。Gradleはsubprojectsメソッドを提供し、すべてのサブプロジェクトに設定を適用できます。ただし、subprojectsに設定を入れすぎると設定フェーズが遅くなることに注意してください。Convention Pluginsを使用することをお勧めします。これらは1回コンパイルされて再利用され、設定時間を15〜30%削減します。
よくある質問
数量に制限はありませんが、各次元がBuild Variantsの数を乗算します。1つの次元に4つのflavor + 2つのbuild types = 8つのバリアント。2つの次元に4 + 4 = 16のバリアント。3次元以下、合計10〜12バリアント以下にすることをお勧めします。
はい、source set src/<flavor>/AndroidManifest.xmlを介して可能です。マニフェストはメインのものとマージされます。ブロック全体を置き換えるには、tools:node="replace"を使用します。たとえば、特定のflavorのアプリラベルや権限を置き換えます。
<flavorName>Implementation設定を使用します。例:freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'。この依存関係はfreeバリアントをビルドするときのみ含まれます。有料版の場合:paidImplementation。共通の依存関係はimplementationを介して指定されます。
Product Flavorはプロダクトバージョン(free、paid、demo)を定義し、Build Typeはビルド方法(debug、release)を定義します。flavorはapplicationId、versionName、リソースを上書きできます。Build Typeはdebuggable、minification、signingを制御します。両方は直交しており、Build Variantに結合されます。
はい、Product Flavorsは制限なくComposeで動作します。異なるflavorは、source setsまたは抽象クラスの実装を通じて異なるCompose画面を持つことができます。また、flavor固有のCompose依存関係を追加することもできます:freeImplementation 'androidx.compose.ui:ui-tooling'。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。