Build-Varianten können in Android Studio erstellt werden, indem man in Gradle einen bestimmten Satz von Regeln verwendet, um Einstellungen, Code und Ressourcen zu kombinieren, die in den Build-Typen und den Product Flavors konfiguriert sind.
Jede Build-Variante steht für eine andere Version der App. Man kann z. B. eine kostenlose Version einer App mit einer begrenzten Anzahl von Funktionen erstellen und eine kostenpflichtige Version, die einen größeren Funktionsumfang besitzt. Es ist auch möglich, verschiedene Versionen einer App zu erstellen, die z.B. für unterschiedliche Kunden erstellt wird. Das ist besonders bei White Label Apps sehr interessant.White-Label-Produkte sind Produkte eines Herstellers, die nicht unter dessen eigener Marke, sondern als (scheinbares) Produkt eines anderen Herstellers bzw. Händlers unter anderer Marke verkauft werden.
Die offizielle Doku kann man unter https://developer.android.com/studio/build/build-variants finden.
Bei White Label Apps handelt es sich um Produkte, bei dem ein Hersteller eine App in dem Layout und einem angepassten Funktionsumfang für seine Kunden erstellt. Die Nutzer der App nehmen dann die App als individuelle App des Anbieters wahr.
Beispiel:
https://scootapi.com/
Anlegen von Build-Varianten in Android Studio
Um Build Varianten in Android Studio anzulegen kann man die Funktion „Build“ -> „Edit Flavors…“ verwenden oder direkt die build.gradle Datei der app editieren.
Edit Flavors…
Mit dem „+“ Button unter dem Flavors Reiter wir nun als erstes eine neue „Flavor Dimension“ angelegt, da mindestens eine Flavor-Dimension mit der Eigenschaft flavorDimensions angeben werden muss und dann jedes Product Flavor einer Flavor-Dimension zuweisen werden muss.
Andernfalls erhält man folgenden Erstellungsfehler:
Error:All flavors must now belong to a named flavor dimension.
The flavor 'flavor_name' is not assigned to a flavor dimension.
Ich wähle hier einfach „default“, da ich nur eine Flavor-Dimension anlege und diese nicht weiter beachte. Ausführliche Infos dazu findet man aber unter https://developer.android.com/reference/tools/gradle-api/4.1/com/android/build/api/dsl/ProductFlavor#dimension.
Nun sehen die Build-Varianten der Projekt-Struktur wie folgt aus:
Als nächstes kann man ein neues Product Flavor anlegen und konfigurieren.
OK, hier fängt es an kompliziert zu werden und wie man in dem Bild sieht, kann man in einem Product Flavor so einige Eigenschaften anpassen.
Application-ID
Jede Android-App hat eine eindeutige Application-ID, die normalerweise dem Java-Paketname entspricht, z. B. com.example.myapp. Diese ID identifiziert Ihre App eindeutig auf dem Android-Gerät und im Google Play Store. Wenn man die Application-ID ändern, behandelt der Google Play Store die APK als eine völlig andere App. Sobald Sie also Ihre App veröffentlicht haben, sollten Sie die Anwendungs-ID niemals ändern.
Dies ist für White Label Apps eine wichtige Eigenschaft, da es nicht möglich ist mehrere Apps mit der gleichen Application-ID im Google play Store zu veröffentlichen.
Application-ID-Suffix
Eine weitere Möglichkeit, die Application-ID für eine Build-Variante anzupassen ist es mit dem applicationIdSuffix zu arbeiten. Statt für jedes Flavor innerhalb des productFlavors-Blocks die Eigenschaft applicationId neu zu definieren kann man mit dem applicationIdSuffix ein Segment an die Standard-Anwendungs-ID anhängen.
android {
defaultConfig {
applicationId "com.example.myapp"
}
productFlavors {
free {
applicationIdSuffix ".free"
}
pro {
applicationIdSuffix ".pro"
}
}
}
Version-Code
Die Version-Code Zahl, dient als interne Versionsnummer. Diese Zahl wird verwendet, um festzustellen, ob eine Version neuer ist als eine andere, wobei höhere Zahlen neuere Versionen anzeigen. Diese Nummer kann durch die Einstellung versionName für das productFlavor festgelegt werden. Der Android-Build Prozess verwendet dann den Wert versionCode der productFlavor Konfiguration um die App vor Downgrades zu schützen. Das ist nötig, da Jede Variante der App ja ihre eigenen Update-Zyklen unterliegt und nicht immer alle Varianten neu gebaut und veröffentlicht werden müssen.
Version-Name
Die Versionsnummer die den Benutzern angezeigt wird. Diese Einstellung kann als String oder als Verweis auf eine String-Ressource angegeben werden. Für Version-Name gilt das gleiche wie für Version-Code und es ist wichtig, ein Release-Management für seine Product-Flavors zu nutzen, um nicht den Überblich zu verlieren.
Version-Name-Suffix
Genau wie bei der Application-ID kann auch hier mit einem Suffix gearbeitet werden, mit dem man unterschiedliche Versionen unterscheiden kann.
flavorDimensions 'app'
productFlavors {
free {
dimension 'app'
versionNameSuffix '.free'
}
staging {
dimension 'app'
versionNameSuffix '.staging'
}
}
Da das Suffix Thema generell etwas komplexer ist empfehle ich man den Artikel unter https://tynn.medium.com/utilize-android-app-version-names-and-suffixes-a63d67efd782 zu lesen. Hier wird noch mal übersichtlich erklärt, wie sich eine Version zusammensetzt, wenn man die versionNameSuffix Eigenschaft intensiv nutzt.
Target Sdk Version
Gibt die API-Stufe an, auf der die Build-Variante ausgeführt werden soll. In einigen Fällen ermöglicht dies der Build-Variante, Manifest-Elemente oder Verhaltensweisen zu verwenden, die in der Ziel-API-Stufe definiert sind, anstatt nur diejenigen zu verwenden, die für die Mindest-API-Stufe definiert sind.
Min Sdk Version
Die Mindestversion der Android-Plattform, auf der die Build-Variante ausgeführt wird, angegeben durch die API-Level-Kennung der Plattform.
Signing Config
Jedem Product-Flavor eine eigene Signing Config zu auch möglich. Damit wird es möglich, jedem Flavor eine eigene Signing-Config zuzuweisen und mit unterschiedlichen Schlüsseln zu signieren. Das ist vor allem dann sehr sinnvoll, wenn man die APK Datei nicht aus Android Studio heraus baut sondern von einem Jenkins oder einem anderen Tool in der CI-CD Pipeline erstellen lässt.
Daneben gibt es noch einige andere Werte, die überschrieben werden können auf die ich hier nicht weiter eingehen möchte.
Eine sehr einfache Konfiguration mit 2 productFlavors ohne spezielle Eigenschaften könne z.B. wie folgt aussehen:
android {
...
flavorDimensions 'default'
productFlavors {
flavor1 {
dimension 'default'
}
flavor2 {
dimension 'default'
}
}
}
Build Variante auswählen
Hat man erst mal seine Build-Varianten erstellt und konfiguriert, kann man in Android Studio auswählen, welches Flavor man bauen und testen möchte. Dazu kann man mit der Funktion „Build“ -> „Select Build Variant…“ einen zusätzlichen View im Android Studio aktivieren.
In dem View unten Links kann man nun für jedes Modul (In diesem Beispiel nur das App Modul) seine Variante wählen und so die gewünschte Konfiguration auswählen die von Android Studio gebaut werden soll.
Nach der Auswahl der Build-Variante kann man mit er Funktion „Build“ -> „Make Projekt“ (STRG F9) den Gradle Build-Prozess starten und in der Build-Ausgabe sehen, dass der entsprechende Gradle Task ausgeführt wird.
Executing tasks: [:app:assembleFlavor2Debug] in project C:\Projekte\ProductFlavorsTest
> Task :app:preBuild UP-TO-DATE
> Task :app:preFlavor2DebugBuild UP-TO-DATE
> Task :app:compileFlavor2DebugAidl NO-SOURCE
> Task :app:compileFlavor2DebugRenderscript NO-SOURCE
> Task :app:generateFlavor2DebugBuildConfig
> Task :app:javaPreCompileFlavor2Debug
> Task :app:generateFlavor2DebugResValues
> Task :app:generateFlavor2DebugResources
> Task :app:checkFlavor2DebugAarMetadata
> Task :app:createFlavor2DebugCompatibleScreenManifests
> Task :app:extractDeepLinksFlavor2Debug
> Task :app:processFlavor2DebugMainManifest
> Task :app:processFlavor2DebugManifest
> Task :app:mergeFlavor2DebugNativeDebugMetadata NO-SOURCE
> Task :app:mergeFlavor2DebugShaders
> Task :app:compileFlavor2DebugShaders NO-SOURCE
> Task :app:generateFlavor2DebugAssets UP-TO-DATE
> Task :app:mergeFlavor2DebugAssets
> Task :app:compressFlavor2DebugAssets
> Task :app:desugarFlavor2DebugFileDependencies
> Task :app:processFlavor2DebugJavaRes NO-SOURCE
> Task :app:mergeFlavor2DebugJniLibFolders
> Task :app:validateSigningFlavor2Debug
> Task :app:mergeFlavor2DebugResources
> Task :app:checkFlavor2DebugDuplicateClasses
> Task :app:mergeFlavor2DebugJavaResource
> Task :app:mergeLibDexFlavor2Debug
> Task :app:mergeFlavor2DebugNativeLibs
> Task :app:stripFlavor2DebugDebugSymbols NO-SOURCE
> Task :app:processFlavor2DebugManifestForPackage
> Task :app:processFlavor2DebugResources
> Task :app:compileFlavor2DebugJavaWithJavac
> Task :app:compileFlavor2DebugSources
> Task :app:dexBuilderFlavor2Debug
> Task :app:mergeExtDexFlavor2Debug
> Task :app:mergeProjectDexFlavor2Debug
> Task :app:packageFlavor2Debug
> Task :app:assembleFlavor2Debug
BUILD SUCCESSFUL in 13s
26 actionable tasks: 26 executed
Build Analyzer results available
An den Task Namen kann man erkennen, dass Android Studio hier den Task entsprechend dem ausgewählten Product-Flavor gewählt hat.
Build Variante in den Ressourcen
Bei White Label Apps steht man oft vor der Aufgabe, in einer App unterschiedliche Farben, Texte oder Layouts zu verwenden ohne die eigentliche Funktion der App zu verändern. Geanu für diesen Fall gibt es die Möglichkeit, Product-Flavors spezifische Ressourcen anzulegen.
Beim Anlegen einer neuen Resource kann man einfach das entsprechende „Source set“ auswählen und schon wird diese Resource für die ausgewählte Variante verwendet.
In der Projekt-Struktur sieht man nun hinter dem Namen der Resource in Klammern den Namen des Android Product Flavors das gerade ausgewählt ist.
Beim Wechsel zwischen den Flavors kann Android Studio schon mal ein wenig ins Stocken geraten. Aber es ist trotzdem noch besser, als zwischen unterschiedlichen Android Studio Projekten hin und her wechseln zu müssen.
Build Variante im Source-Code
Gelegentlich ist es nötig, abhängig von der aktuellen Build-Variante unterschiedlichen Code auszuführen und auch das ist kein Problem, da die aktuelle Konfiguration in der generierten Klasse BuildConfig eingetragen wird.
package com.jentsch.productflavorstest;
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.jentsch.productflavorstest";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "flavor1";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
}
Alternativ kann man auch abhängig von den Werten in den entsprechenden Ressourcen unterschiedlich reagieren, aber ich bevorzuge hier BuildConfig.FLAVOR.
Android Product Flavors Pro und Kontra
Vorteile
- Es muss nicht für jede Variante der App eine eigene Projektcodebasis existieren
- Redundanter Code kann einfach vermieden werden
Nachteile
- Je mehr Varianten, desto größer die Komplexität
- In einigen Fällen ist die Verwendung von Modulen einfacher
Update:
Gerade noch eine Webseite zu dem Thema gefunden und direkt mal verlinkt.