Un Build Variant nello sviluppo Android è una combinazione di un build type e un product flavor che determina come verrà compilato un APK o AAB: con quali parametri, risorse e codice. Ogni variante di build rappresenta una configurazione Gradle separata con il proprio applicationId, chiavi di firma e dipendenze incluse. Secondo Google Android Developers, 2025, una corretta configurazione dei Build Variants riduce il tempo di compilazione fino al 40% escludendo risorse non necessarie per ogni variante. Il sistema di varianti di build è il fondamento della gestione della configurazione nei progetti Android moderni.
Punti Chiave
Build Variant è il risultato della combinazione di un Build Type e un Product Flavor. Se nessun Product Flavor è definito nel progetto, il Build Variant corrisponde al Build Type. Gradle genera automaticamente l’insieme completo di varianti come prodotto cartesiano di tutti i FlavorDimensions, Product Flavors e Build Types. Ad esempio, per i flavor free/paid e i tipi debug/release, verranno create 4 varianti: freeDebug, freeRelease, paidDebug, paidRelease.
Ogni Build Variant riceve il proprio nome nel formato <Flavor><Type> con il flavor in maiuscolo. Gradle genera attività separate per questa variante: assembleFreeDebug, installFreeDebug, bundleFreeRelease. In Android Studio, il passaggio tra le varianti è disponibile tramite il pannello Build Variants (View → Tool Windows → Build Variants). La selezione di una variante influenza quale codice viene compilato, quali risorse vengono incluse e quale APK/AAB viene prodotto.
Il sistema Build Variants risolve tre compiti principali: separare le configurazioni per diversi ambienti (dev/staging/production), creare più versioni di un’app (free/paid) e testare A/B le compilazioni. Senza Build Variants, gli sviluppatori dovrebbero cambiare manualmente flag e configurazioni, portando a errori umani. Secondo uno studio di Gradle Inc., 2024, l’implementazione di Build Variants riduce gli errori di compilazione del 60% in progetti con tre o più ambienti di distribuzione.
AGP (Android Gradle Plugin) calcola tutte le combinazioni nella fase di configurazione. Se un progetto ha due dimensioni rispettivamente con due e tre flavor, Gradle creerà 2 × 2 × 3 = 12 combinazioni, moltiplicate per il numero di Build Types (di solito 2). Ogni combinazione riceve un nome univoco e un insieme di attività. AGP aggiunge automaticamente un source set per ogni variante: src/freeDebug/, src/paidRelease/, nonché quelli generalizzati src/free/ e src/debug/. Priorità di lettura delle risorse: variant → flavor → type → main.
// Esempio: 4 Build Variants
// flavorDimensions "version", "server"
// version: demo, prod
// server: mock, live
// buildTypes: debug, release
// Totale: 2 × 2 × 2 = 8 varianti
android {
flavorDimensions "version", "server"
productFlavors {
demo { dimension "version" }
prod { dimension "version" }
mock { dimension "server" }
live { dimension "server" }
}
}
Build Type definisce come compilare l’applicazione — con o senza informazioni di debug, con o senza ottimizzazione, con quale firma. Product Flavor definisce cosa compilare — quale versione del prodotto. Build Type è un meccanismo di compilazione (debug, release, staging). Product Flavor è una variante di prodotto (free, paid, enterprise, demo). Entrambi i concetti sono ortogonali: qualsiasi Build Type può essere applicato a qualsiasi Product Flavor.
I Build Type predefiniti includono debug (debuggable=true, minification=false, signing=debug.keystore) e release (debuggable=false, minification=true, signing=production.keystore). Il Product Flavor predefinito è uno, senza nome (effettivamente il source set main). Gli sviluppatori possono aggiungere i propri Build Type (ad esempio, “staging” con debuggable=true e minification=true) e qualsiasi numero di Product Flavor. Un’altra differenza è che i Build Type non possono essere raggruppati in dimensioni, mentre i Product Flavor sì.
La differenza pratica principale: defaultConfig in build.gradle si applica a tutti i Variants ma può essere sovrascritto in productFlavors e buildTypes. Un BuildConfigField aggiunto a un buildType è visibile in tutti i flavor di quel tipo, mentre uno aggiunto a un productFlavor è visibile in tutti i tipi di quel flavor. Se un campo è definito in entrambi, buildType ha priorità (viene applicato per ultimo nella catena).
| Caratteristica | Build Type | Product Flavor |
|---|---|---|
| Scopo | Come compilare | Cosa compilare |
| Esempi | debug, release, staging | free, paid, demo, enterprise |
| Predefinito | debug + release | uno (main) |
| Source Set | src/debug/, src/release/ | src/free/, src/paid/ |
| Dimensioni | no | flavorDimensions |
| Ordine di applicazione | dopo flavor, sovrascrive | dopo defaultConfig |
| BuildConfigField | sovrascrive flavor | sovrascrive defaultConfig |
La configurazione dei Build Variants viene effettuata nel blocco android del file build.gradle a livello di modulo. Prima vengono dichiarati i buildTypes con i loro parametri, poi flavorDimensions e productFlavors. Gradle crea automaticamente le varianti basandosi su queste dichiarazioni. Ogni variante eredita il defaultConfig del modulo, sovrascrivendo i campi specificati. L’ordine di dichiarazione influisce sulla priorità: i buildTypes vengono applicati dopo i productFlavors.
Per accedere a un Build Variant specifico negli script Gradle, utilizzare android.applicationVariants (per i moduli app) o android.libraryVariants (per i moduli libreria). Si tratta di una raccolta che può essere iterata per modificare la configurazione di ogni variante in fase di esecuzione della configurazione. Ad esempio, è possibile aggiungere programmaticamente buildConfigField per tutte le varianti contenenti la parola “demo”.
Android Gradle Plugin 8.x ha aggiunto il supporto per onVariants — un’API più pulita per configurare le varianti tramite lambda. La vecchia API (variantOutput, variantFilter) è stata contrassegnata come deprecata. Si consiglia di utilizzare onVariants insieme a onEach per i moduli libreria. Migrare da variantOutput a onVariants è un passo consigliato quando si aggiorna AGP da 7.x a 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)
}
}
Ogni Build Variant riceve la propria gerarchia di source set — directory con codice sorgente, risorse e manifesto. Un source set si trova in src/<variantName>/ (ad esempio, src/freeDebug/) e può contenere java/, res/, AndroidManifest.xml, assets/. Se un file esiste nel source set della variante, sovrascrive il file con lo stesso nome dal source set principale (src/main/). Per le risorse, avviene un’unione anziché una sostituzione — il sistema unisce le risorse da tutti i source set attivi, dando priorità a quelli specifici della variante.
I source set per un Build Variant vengono costruiti in catena: src/main/ → src/flavor/ → src/type/ → src/flavorType/. Ad esempio, per paidRelease, viene applicato prima main, poi paid, poi release, poi paidRelease. Ogni source set successivo sovrascrive il precedente. Ciò significa che src/release/res/values/strings.xml sovrascriverà le stesse stringhe da src/paid/, ma src/paidRelease/res/ ha priorità ancora maggiore.
Utilizzare i source set per le varianti è il modo consigliato per personalizzare le risorse. Invece di controllare BuildConfig.FLAVOR nel codice e ramificare la logica, puoi semplicemente inserire file diversi in source set diversi. Ad esempio, le icone per le versioni free e paid vanno rispettivamente in src/free/res/ e src/paid/res/, e l’AndroidManifest con permessi diversi va in src/free/AndroidManifest.xml e src/paid/AndroidManifest.xml. Questo è più pulito, più veloce (le risorse vengono compilate, non controllate a runtime) e più sicuro (non puoi includere accidentalmente funzionalità a pagamento nella versione gratuita a causa di un bug nel codice).
Nei progetti multimodulo, ogni modulo (libreria) può avere i propri Build Variants. AGP sincronizza automaticamente le varianti: se il modulo app compila paidRelease, tutte le librerie dipendenti vengono anch’esse compilate nelle loro varianti corrispondenti a paidRelease. Sorge un problema quando una libreria non ha product flavors ma il modulo app sì — allora la libreria viene compilata una volta (release o debug a seconda del tipo).
Per i moduli libreria, il Build Variant corrisponde per impostazione predefinita al Build Type del modulo app, poiché le librerie non hanno product flavors. Se una libreria deve adattarsi al flavor del modulo app, gli stessi flavorDimensions e productFlavors devono essere dichiarati nella libreria. AGP abbina i flavor per corrispondenza esatta del nome. Gradle consiglia di sincronizzare i flavor tramite la configurazione di build nel progetto root utilizzando subprojects o Convention Plugins.
A partire da AGP 8.1, le librerie possono pubblicare varianti multiple — pubblicare tutte le varianti della libreria in un repository maven contemporaneamente. Ciò risolve il problema quando il modulo app utilizza un flavor a pagamento ma la libreria è pubblicata solo per free. La pubblicazione di varianti multiple (MVP) consente al progetto dipendente di selezionare automaticamente la variante richiesta. Per abilitare MVP, aggiungi publishing { multipleVariants { ... } } al build.gradle della libreria.
A volte è necessario disabilitare alcuni Build Variants — ad esempio, se la combinazione mockRelease non ha senso (il server mock non dovrebbe andare in produzione). Gradle fornisce variantFilter — un blocco DSL dove puoi controllare le proprietà di ogni variante e disabilitarla tramite setIgnore(true). VariantFilter viene applicato nella fase di configurazione, prima della creazione delle attività, quindi una variante disabilitata non genera attività assemble e install.
Il filtraggio è utile anche per accelerare le compilazioni. Se un progetto ha 8 varianti ma uno sviluppatore lavora su una sola, le restanti 7 varianti passano comunque attraverso la configurazione. Utilizzando variantFilter, le varianti disabilitate non creano attività, riducendo il tempo di configurazione del 30-50% per progetti con 6+ dimensioni di flavor. In CI/CD, puoi filtrare dinamicamente le varianti tramite parametri da riga di comando -PbuildOnly=paidRelease.
android {
variantFilter { variant ->
// Disabilitare mock per release e demo per production
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)
}
}
}
// Filtraggio dinamico tramite parametri
if (project.hasProperty("buildOnly")) {
def target = project.property("buildOnly")
android.variantFilter { variant ->
variant.setIgnore(variant.name != target)
}
}
Domande Frequenti
Non c’è limite, ma Gradle crea il prodotto cartesiano di tutti i flavor e tipi. Se hai 3 dimensioni con 3 flavor ciascuna e 3 build types, ottieni 27 varianti. Troppe varianti rallentano la configurazione. Si consiglia di non avere più di 10–12 varianti in un modulo.
I flavorDimensions raggruppano i Product Flavors in assi indipendenti. Ad esempio, la dimensione “tier” (free, paid) e la dimensione “region” (us, eu). Senza dimensioni, tutti i flavor appartengono a un unico asse e Gradle selezionerà solo un flavor da tutti (non puoi avere free+us e paid+eu come varianti separate).
Nel blocco productFlavor o buildType, specifica applicationId. Ad esempio, per la versione gratuita: free { applicationId “com.example.app.free” }. Nel manifesto, usa ${applicationId} — Gradle sostituirà automaticamente il valore. Ciò consente di installare entrambe le varianti sullo stesso dispositivo.
In iOS, l’equivalente dei Build Variants è la combinazione di Scheme + Configuration. Gli Xcode Schemes vengono configurati tramite le configurazioni Debug/Release con parametri diversi. Per più versioni (free/paid), vengono utilizzate Build Configurations e Preprocessor Macros. Su Android, il concetto è più formalizzato e integrato in Gradle.
Sì, ogni variante può avere dimensioni APK diverse. Le build debug includono informazioni di debug, SDK e risorse non supportate. Le build release con minification e resource shrinking producono le dimensioni minime. Anche il Product Flavor influisce sulle dimensioni: una versione gratuita senza librerie a pagamento sarà più piccola della versione a pagamento per la dimensione di quelle librerie.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche