Build Variant w tworzeniu aplikacji na Androida to kombinacja build type i product flavor, która określa, jak zostanie zbudowany APK lub AAB: z jakimi parametrami, zasobami i kodem. Każdy wariant kompilacji stanowi oddzielną konfigurację Gradle z własnym applicationId, kluczami podpisywania i dołączonymi zależnościami. Według danych Google Android Developers, 2025, prawidłowa konfiguracja Build Variants skraca czas kompilacji o 40% dzięki wykluczeniu niepotrzebnych zasobów dla każdego wariantu. System wariantów kompilacji stanowi podstawę zarządzania konfiguracją we współczesnych projektach Android.
Najważniejsze
Build Variant — to wynik kombinacji jednego Build Type i jednego Product Flavor. Jeśli w projekcie nie zdefiniowano Product Flavor, Build Variant pokrywa się z Build Type. Gradle automatycznie generuje pełny zestaw wariantów jako iloczyn kartezjański wszystkich FlavorDimensions, Product Flavors i Build Types. Na przykład dla flavor free/paid i typów debug/release zostanie utworzone 8 wariantów: freeDebug, freeRelease, paidDebug, paidRelease.
Każdy Build Variant otrzymuje własną nazwę w formacie <Flavor><Type> z wielkiej litery flavor. Dla tego wariantu Gradle generuje osobne zadania: assembleFreeDebug, installFreeDebug, bundleFreeRelease. W Android Studio przełączanie między wariantami jest dostępne przez panel Build Variants (View → Tool Windows → Build Variants). Wybór wariantu wpływa na to, który kod jest kompilowany, które zasoby są dołączane i który APK/AAB jest tworzony.
System Build Variants rozwiązuje trzy kluczowe zadania: rozdzielenie konfiguracji dla różnych środowisk (dev/staging/production), tworzenie wielu wersji aplikacji (free/paid) i A/B testowanie kompilacji. Bez Build Variants programiści musieliby ręcznie przełączać flagi i konfiguracje, co prowadzi do błędów czynnika ludzkiego. Według badań Gradle Inc., 2024, wdrożenie Build Variants zmniejsza liczbę błędów kompilacji o 60% w projektach z trzema lub więcej środowiskami wdrożeniowymi.
AGP (Android Gradle Plugin) oblicza wszystkie kombinacje na etapie konfiguracji. Jeśli w projekcie są dwa wymiary z odpowiednio dwoma i trzema flavor, Gradle utworzy 2 × 2 × 3 = 12 kombinacji, pomnożonych przez liczbę Build Types (zwykle 2). Każda kombinacja otrzymuje unikalną nazwę i zestaw zadań. AGP automatycznie dodaje source set dla każdego wariantu: src/freeDebug/, src/paidRelease/, a także uogólnione src/free/ i src/debug/. Priorytet odczytu zasobów: variant → flavor → type → main.
// Przykład: 4 Build Varianty
// flavorDimensions "version", "server"
// version: demo, prod
// server: mock, live
// buildTypes: debug, release
// Razem: 2 × 2 × 2 = 8 wariantów
android {
flavorDimensions "version", "server"
productFlavors {
demo { dimension "version" }
prod { dimension "version" }
mock { dimension "server" }
live { dimension "server" }
}
}
Build Type określa jak kompilować aplikację — z informacjami debugowania czy bez, z optymalizacją czy bez, z jakim podpisywaniem. Product Flavor określa co kompilować — którą wersję produktu. Build Type — to mechanizm kompilacji (debug, release, staging). Product Flavor — to wariant produktu (free, paid, enterprise, demo). Oba pojęcia są ortogonalne: każdy Build Type można zastosować do każdego Product Flavor.
Build Type domyślnie obejmuje debug (debuggable=true, minification=false, signing=debug.keystore) i release (debuggable=false, minification=true, signing=production.keystore). Product Flavor domyślnie jest jeden, bez nazwy (faktycznie main source set). Deweloper może dodawać własne Build Types (np. „staging“ z debuggable=true i minification=true) i Product Flavors w dowolnej liczbie. Różnica polega również na tym, że Build Type nie można grupować w wymiary, a Product Flavor — można.
Kluczowa praktyczna różnica: defaultConfig w build.gradle stosuje się do wszystkich Variants, ale może być nadpisany w productFlavors i buildTypes. BuildConfigField dodany w buildType jest widoczny we wszystkich flavor tego typu, a dodany w productFlavor — we wszystkich typach tego flavor. Jeśli pole jest zdefiniowane zarówno tam, jak i tam — priorytet ma buildType (jest stosowany jako ostatni w łańcuchu).
| Cecha | Build Type | Product Flavor |
|---|---|---|
| Przeznaczenie | Jak kompilować | Co kompilować |
| Przykłady | debug, release, staging | free, paid, demo, enterprise |
| Domyślnie | debug + release | jeden (main) |
| Source Set | src/debug/, src/release/ | src/free/, src/paid/ |
| Wymiary | nie | flavorDimensions |
| Zastosowanie | po flavor, nadpisuje | po defaultConfig |
| BuildConfigField | nadpisuje flavor | nadpisuje defaultConfig |
Konfiguracja Build Variants jest wykonywana w bloku android pliku build.gradle na poziomie modułu. Najpierw deklaruje się buildTypes z ich parametrami, następnie flavorDimensions i productFlavors. Gradle automatycznie tworzy warianty na podstawie tych deklaracji. Każdy wariant dziedziczy defaultConfig modułu, nadpisując określone pola. Kolejność deklaracji wpływa na priorytet: buildTypes są stosowane po productFlavors.
Aby uzyskać dostęp do konkretnego Build Variant w skryptach Gradle, używa się android.applicationVariants (dla modułu app) lub android.libraryVariants (dla modułu bibliotecznego). Jest to kolekcja, po której można iterować i zmieniać konfigurację każdego wariantu w czasie konfiguracji. Na przykład można programowo dodać buildConfigField dla wszystkich wariantów zawierających słowo „demo“.
Android Gradle Plugin 8.x dodał obsługę onVariants — czystszego API do konfiguracji wariantów przez lambdy. Stare API (variantOutput, variantFilter) jest oznaczone jako przestarzałe. Zaleca się używanie onVariants razem z onEach dla modułów bibliotecznych. Migracja z variantOutput na onVariants — zalecany krok przy aktualizacji AGP z 7.x na 8.x.
android {
buildTypes {
debug {
debuggable true
minification false
signingConfig signingConfigs.debug
}
release {
debuggable false
minification true
proguardFiles "proguard-rules.pro"
signingConfig signingConfigs.release
}
staging {
debuggable true
minification true
versionNameSuffix "-staging"
}
}
flavorDimensions "tier", "region"
productFlavors {
free { dimension "tier" }
paid { dimension "tier" }
us { dimension "region" }
eu { dimension "region" }
}
}
android.onVariants { variant ->
if (variant.name.contains("Demo")) {
variant.setEnabled(false)
}
}
Każdy Build Variant otrzymuje własną hierarchię source sets — katalogów z kodem źródłowym, zasobami i manifestem. Source set lokalizuje się w src/<variantName>/ (np. src/freeDebug/) i może zawierać java/, res/, AndroidManifest.xml, assets/. Jeśli plik istnieje w source set wariantu, nadpisuje plik o tej samej nazwie z głównego source set (src/main/). Dla zasobów działa scalanie, a nie zastępowanie — system łączy zasoby ze wszystkich aktywnych source sets, dając priorytet specyficznym dla wariantu.
Source sets dla Build Variant są budowane w łańcuchu: src/main/ → src/flavor/ → src/type/ → src/flavorType/. Na przykład dla paidRelease najpierw stosuje się main, następnie paid, następnie release, następnie paidRelease. Każdy kolejny source set nadpisuje poprzedni. Oznacza to, że src/release/res/values/strings.xml nadpisze te same ciągi z src/paid/, ale src/paid/release/res/ jest jeszcze bardziej priorytetowy.
Używanie source sets dla wariantów — zalecany sposób dostosowywania zasobów. Zamiast sprawdzać BuildConfig.FLAVOR w kodzie i rozgałęziać logikę, można po prostu umieścić różne pliki w różnych source sets. Na przykład ikony dla wersji free i paid umieszcza się odpowiednio w src/free/res/ i src/paid/res/, a AndroidManifest z różnymi uprawnieniami — w src/free/AndroidManifest.xml i src/paid/AndroidManifest.xml. Jest to czystsze, szybsze (zasoby są kompilowane, a nie sprawdzane w czasie wykonania) i bezpieczniejsze (nie można przypadkowo włączyć płatnej funkcjonalności w darmowej wersji z powodu błędu w kodzie).
W projektach wielomodułowych każdy moduł (biblioteka) może mieć własne Build Variants. AGP automatycznie synchronizuje warianty: jeśli moduł app kompiluje paidRelease, wszystkie zależne biblioteki również są kompilowane w ich wariantach odpowiadających paidRelease. Problem pojawia się, gdy biblioteka nie ma product flavors, ale moduł app ma — wtedy biblioteka jest kompilowana raz (release lub debug w zależności od typu).
Dla modułów bibliotecznych Build Variant domyślnie pokrywa się z Build Type modułu app, ponieważ biblioteki nie mają product flavors. Jeśli biblioteka ma dostosowywać się do flavor modułu app, należy zadeklarować te same flavorDimensions i productFlavors w bibliotece. AGP dopasowuje flavor po pełnej zgodności nazwy. Gradle zaleca synchronizowanie flavor przez konfigurację kompilacji w projekcie głównym za pomocą subprojects lub Convention Plugins.
Począwszy od AGP 8.1, biblioteki mogą publikować multiple variants — opublikować w repozytorium maven wszystkie warianty biblioteki jednocześnie. Rozwiązuje to problem, gdy moduł app używa paid flavor, a biblioteka jest opublikowana tylko dla free. Multiple variants publishing (MVP) umożliwia zależnemu projektowi automatyczne wybranie potrzebnego wariantu. Aby włączyć MVP, należy dodać publishing { multipleVariants { ... } } w build.gradle biblioteki.
Czasami konieczne jest wyłączenie części Build Variants — na przykład jeśli kombinacja mockRelease nie ma sensu (serwer mock nie powinien trafić do produkcji). Gradle udostępnia variantFilter — blok DSL, w którym można sprawdzać właściwości każdego wariantu i wyłączać go przez setIgnore(true). VariantFilter jest stosowany na etapie konfiguracji, przed utworzeniem zadań, więc wyłączony wariant nie generuje zadań assemble i install.
Filtrowanie jest również przydatne do przyspieszenia kompilacji. Jeśli w projekcie jest 8 wariantów, a programista pracuje tylko nad jednym, pozostałe 7 wariantów i tak przechodzą konfigurację (configuration phase). Przy użyciu variantFilter wyłączone warianty nie tworzą zadań, co skraca czas konfiguracji o 30-50% dla projektów z 6+ wymiarami flavor. W CI/CD można dynamicznie filtrować warianty przez parametry wiersza poleceń -PbuildOnly=paidRelease.
android {
variantFilter { variant ->
// Wyłącz mock dla release i demo dla produkcji
def names = variant.flavors*.name
def isMock = names.contains("mock")
def isDemo = names.contains("demo")
def isRelease = variant.buildType.name == "release"
if ((isMock && isRelease) || (isDemo && !isMock)) {
variant.setIgnore(true)
}
}
}
// Dynamiczne filtrowanie przez parametry
if (project.hasProperty("buildOnly")) {
def target = project.property("buildOnly")
android.variantFilter { variant ->
variant.setIgnore(variant.name != target)
}
}
Często zadawane pytania
Nie ma ograniczeń, ale Gradle tworzy iloczyn kartezjański wszystkich flavor i typów. Jeśli masz 3 wymiary po 3 flavor i 3 build type — otrzymasz 27 wariantów. Zbyt wiele wariantów spowalnia konfigurację. Zaleca się nie więcej niż 10-12 wariantów w jednym module.
flavorDimensions grupują Product Flavors w niezależne osie. Na przykład wymiar „tier“ (free, paid) i „region“ (us, eu). Bez wymiarów wszystkie flavor należą do jednej osi i Gradle będzie wybierał tylko jeden flavor ze wszystkich (nie można mieć free+us i paid+eu jako oddzielnych wariantów).
W bloku productFlavor lub buildType wskaż applicationId. Na przykład dla wersji free: free { applicationId "com.example.app.free" }. W manifeście użyj ${applicationId} — Gradle automatycznie podstawi wartość. Pozwala to zainstalować oba warianty na jednym urządzeniu.
W iOS odpowiednikiem Build Variants jest kombinacja Scheme + Configuration. Xcode Schemes są konfigurowane przez konfiguracje Debug/Release z różnymi parametrami. Dla wielu wersji (free/paid) używa się Build Configurations i Preprocessor Macros. W Androidzie koncepcja jest sformalizowana bardziej rygorystycznie i wbudowana w Gradle.
Tak, każdy wariant może mieć inny rozmiar APK. Kompilacje debug zawierają informacje debugowania, SDK i nieobsługiwane zasoby. Kompilacje release z minifikacją i zmniejszaniem zasobów dają minimalny rozmiar. Product Flavor również wpływa: wersja free bez płatnych bibliotek będzie mniejsza od wersji paid o rozmiar tych bibliotek.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również