Product Flavor: Τι Είναι, Διαμόρφωση και Παραδείγματα στο Gradle

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-05-30 Χρόνος ανάγνωσης: 9 λεπ

Το Product Flavor στην ανάπτυξη Android είναι ένας μηχανισμός του Gradle που επιτρέπει τη δημιουργία πολλαπλών παραλλαγών μιας εφαρμογής από μια κοινή βάση κώδικα. Κάθε flavor μπορεί να έχει δικό του applicationId, πόρους, εξαρτήσεις και λειτουργικότητα — για παράδειγμα, δωρεάν και επί πληρωμή εκδόσεις. Σύμφωνα με το Google Android Developers, 2025, τα Product Flavors αποτελούν μέρος του συστήματος Build Variants και συνδυάζονται με Build Types μέσω flavorDimensions. Είναι η τυπική προσέγγιση για δημοσίευση πολλαπλών εκδόσεων μιας εφαρμογής στο Google Play.

Βασικά Σημεία

  • Product Flavor — παραλλαγή προϊόντος με μοναδικό applicationId, πόρους και κώδικα.
  • Flavor Dimensions ομαδοποιούν τα flavor σε ανεξάρτητους άξονες για πολυδιάστατη διαμόρφωση.
  • Source sets για ένα flavor παρακάμπτουν τους main πόρους: εικονίδια, συμβολοσειρές, manifest.
  • Gradle δημιουργεί αυτόματα ένα Build Variant για κάθε συνδυασμό flavor + build type.
  • Google Play υποστηρίζει τη δημοσίευση πολλαπλών flavor ως ξεχωριστές εφαρμογές ή μία με διαφορετικές διαμορφώσεις.

Τι Είναι το Product Flavor;

Product Flavor είναι μια διαμόρφωση Gradle στο μπλοκ android.productFlavors που περιγράφει μια παραλλαγή προϊόντος. Κάθε flavor μπορεί να παρακάμπτει το applicationId, versionName, versionCode, minSdkVersion, targetSdkVersion, signingConfig και άλλες παραμέτρους του defaultConfig. Τα Product Flavors δεν έχουν περιορισμό στον αριθμό: ένα έργο μπορεί να περιέχει 2, 5, 10 flavor — το Gradle θα επεξεργαστεί όλους τους συνδυασμούς.

Το Product Flavor λύνει το πρόβλημα codebase reuse — όταν από ένα αποθετήριο πρέπει να δημιουργηθούν πολλές διαφορετικές εφαρμογές. Τυπικά σενάρια: δωρεάν έκδοση με διαφημίσεις και επί πληρωμή χωρίς, demo έκδοση με περιορισμένη λειτουργικότητα, εταιρική και καταναλωτική έκδοση, white-label εφαρμογές για διαφορετικούς πελάτες. Χωρίς Product Flavors, κάθε έκδοση θα έπρεπε να συντηρείται σε ξεχωριστό έργο, οδηγώντας σε αντιγραφή κώδικα κατά 60-70%.

Ιστορικά, τα Product Flavors εμφανίστηκαν στο Android Gradle Plugin 0.9 (2013) ως αντικατάσταση των ant-διαμορφώσεων. Πριν από αυτό, οι προγραμματιστές χρησιμοποιούσαν ξεχωριστά έργα για διαφορετικές εκδόσεις ή χειροκίνητη αντικατάσταση πόρων πριν από τη μεταγλώττιση. Η ενσωμάτωση των flavor στο AGP ενοποίησε την προσέγγιση και την έκανε πρότυπο. Σύμφωνα με έρευνα της JetBrains, 2024, το 78% των Android έργων με πολλαπλές εκδόσεις χρησιμοποιούν Product Flavors, ενώ τα υπόλοιπα χρησιμοποιούν χειροκίνητη εναλλαγή μέσω BuildConfig ή reflection.

Product Flavor vs Build Type

Το Build Type διαχειρίζεται τη διαδικασία μεταγλώττισης (debug με αποσφαλμάτωση, release με βελτιστοποίηση). Το Product Flavor διαχειρίζεται το περιεχόμενο της μεταγλώττισης (free χωρίς λειτουργίες επί πληρωμή, paid με αυτές). Το Build Type είναι μια υποδομική ρύθμιση, ενώ το Product Flavor είναι προϊοντική. Και οι δύο έννοιες είναι ορθογώνιες: η debug μεταγλώττιση του free-flavor διαφέρει από την release μεταγλώττιση του free-flavor μόνο στις παραμέτρους μεταγλώττισης, όχι στη λειτουργικότητα. Το Product Flavor δεν μπορεί να χρησιμοποιηθεί για απενεργοποίηση του αποσφαλματωτή — αυτή είναι δουλειά του Build Type.

Flavor Dimensions: Οργάνωση Διαστάσεων

Σειρά Διαστάσεων και Προτεραιότητα

Τα Flavor Dimensions είναι ένας μηχανισμός ομαδοποίησης των Product Flavors σε ανεξάρτητες κατηγορίες. Αν μια εφαρμογή έχει δωρεάν/επί πληρωμή έκδοση και ξεχωριστά αμερικανική/ευρωπαϊκή περιοχή, τα flavor ομαδοποιούνται σε δύο διαστάσεις: “tier” (free, paid) και “region” (us, eu). Το Gradle δημιουργεί το καρτεσιανό γινόμενο των διαστάσεων: freeUs, freeEu, paidUs, paidEu — 4 παραλλαγές. Χωρίς διαστάσεις, το Gradle θα αντιλαμβανόταν και τα τέσσερα flavor ως ένα επίπεδο και θα μπορούσε να επιλεγεί μόνο ένα.

Οι διαστάσεις δηλώνονται στο μπλοκ flavorDimensions ως συμβολοσειρά ή λίστα συμβολοσειρών. Η σειρά των διαστάσεων επηρεάζει την προτεραιότητα των source sets: η πρώτη διάσταση έχει την υψηλότερη προτεραιότητα. Αν η διάσταση A (tier) δηλωθεί πρώτη, τότε το src/free/ θα παρακάμπτει το src/us/ σε περίπτωση σύγκρουσης πόρων. Επίσης, η σειρά επηρεάζει το πώς σχηματίζεται το όνομα Variant: πρώτα έρχονται τα flavor της πρώτης διάστασης, μετά της δεύτερης και μετά το 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 γίνεται δυσανάγνωστος.

groovy
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 Flavors στο build.gradle

Kotlin DSL για Product Flavors

Για τη δημιουργία ενός Product Flavor, πρέπει να προστεθεί το μπλοκ productFlavors μέσα στο android, να οριστεί το όνομα του flavor και οι παράμετροί του. Η ελάχιστη δήλωση flavor είναι το όνομα και η διάσταση. Όλες οι άλλες παράμετροι κληρονομούνται από το defaultConfig και μπορούν να παρακαμφθούν. Το flavor κληρονομεί πλήρως το defaultConfig, συμπεριλαμβανομένων των applicationId, versionCode, testInstrumentationRunner.

Κάθε flavor μπορεί να παρακάμπτει το applicationId — αυτό επιτρέπει την εγκατάσταση πολλαπλών εκδόσεων της εφαρμογής στην ίδια συσκευή ταυτόχρονα. Για παράδειγμα, η free-έκδοση θα είναι com.example.app.free, η paid — com.example.app.paid. Αν το applicationId δεν παρακαμφθεί, όλα τα flavor θα έχουν το ίδιο αναγνωριστικό και δεν θα μπορούν να εγκατασταθούν παράλληλα. Το applicationId πρέπει να ταιριάζει με το package στο manifest (αν δεν χρησιμοποιείται applicationIdSuffix).

Το AGP 8+ συνιστά τη χρήση Kotlin DSL αντί Groovy για το build.gradle. Το Kotlin DSL παρέχει type-safe πρόσβαση στη διαμόρφωση: το IDE προτείνει ονόματα παραμέτρων, ελέγχει τύπους κατά τη μεταγλώττιση και επισημαίνει σφάλματα. Η μετάβαση από Groovy σε Kotlin DSL για Product Flavors συνήθως συνίσταται στην αντικατάσταση εισαγωγικών με παρενθέσεις και στην προσθήκη τύπων. Το AGP είναι προς τα πίσω συμβατό — και οι δύο συντακτικές λειτουργούν παράλληλα στο ίδιο έργο.

kotlin
// 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")
        }
    }
}

Πόροι και Κώδικας για Διαφορετικά Flavor

Κάθε Product Flavor δημιουργεί το δικό του source set — τον κατάλογο src/<flavorName>/. Σε αυτόν τον κατάλογο μπορούν να τοποθετηθούν παρακαμφθέντες πόροι, πηγαίος κώδικας και manifest. Το source set flavor λειτουργεί ως επικάλυψη πάνω από το main: αρχεία από το src/free/res/ παρακάμπτουν αρχεία από το src/main/res/ με τα ίδια ονόματα. Αυτό επιτρέπει διαφορετικές συμβολοσειρές, εικονίδια, χρώματα και διατάξεις για κάθε flavor χωρίς αλλαγή του βασικού κώδικα.

Για την παράκαμψη κλάσεων Java/Kotlin, υπάρχουν δύο προσεγγίσεις: flavor-specific implementation (υλοποίηση αφηρημένης κλάσης σε κάθε flavor) και BuildConfig field (διακλάδωση στον κώδικα). Η πρώτη προσέγγιση είναι καθαρότερη: ορίζετε μια διεπαφή ή αφηρημένη κλάση στο main και συγκεκριμένες υλοποιήσεις στα src/free/ και src/paid/. Κατά τη μεταγλώττιση, μεταγλωττίζεται μόνο η υλοποίηση του τρέχοντος flavor. Αυτό προσφέρει ταυτόχρονα πλεονεκτήματα: μικρότερο μέγεθος APK (ο κώδικας επί πληρωμή δεν μπαίνει στη free-έκδοση) και ασφάλεια (αδυναμία τυχαίας κλήσης λειτουργίας επί πληρωμή).

Το AndroidManifest.xml στο source set flavor δεν αντικαθιστά, αλλά συγχωνεύεται με το main manifest. Η συγχώνευση γίνεται σύμφωνα με τους κανόνες Android: ίδια χαρακτηριστικά στο ίδιο στοιχείο παρακάμπτονται, μοναδικά προστίθενται. Για παράδειγμα, αν στο main manifest δηλώνεται το INTERNET permission και στο free όχι, το διαδίκτυο παραμένει. Αλλά το tools:node="replace" επιτρέπει την αντικατάσταση ολόκληρου μπλοκ manifest για συγκεκριμένο flavor. Αυτό είναι χρήσιμο όταν διαφορετικά flavor απαιτούν διαφορετικά permissions (εγγραφή σε SD για paid, κάμερα για free).

xml

<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 — χωρίς διαφημίσεις, με εκτεταμένη λειτουργικότητα. Για τη free-έκδοση ορίζεται applicationId "com.example.app.free", για την paid — "com.example.app.paid". Και οι δύο εκδόσεις μπορούν να εγκατασταθούν στην ίδια συσκευή ταυτόχρονα, καθώς το applicationId είναι το μοναδικό αναγνωριστικό της εφαρμογής στο σύστημα Android.

Αρχιτεκτονικά, ο διαχωρισμός γίνεται μέσω interface + flavor implementation. Στο main source set δηλώνεται η διεπαφή PaymentService. Στο src/free/ βρίσκεται μια υλοποίηση που εμφανίζει διαφημίσεις πριν από την πληρωμή μέσω AdMob. Στο src/paid/ — μια υλοποίηση που πηγαίνει κατευθείαν στην πύλη πληρωμής. Ο κώδικας που χρησιμοποιεί το PaymentService δεν γνωρίζει ποια υλοποίηση έχει φορτωθεί — αυτό αποφασίζεται κατά τη μεταγλώττιση. Αυτή η προσέγγιση εγγυάται ότι στη free-έκδοση δεν θα εισαχθεί κώδικας διαχείρισης συνδρομών, ακόμα κι αν ο προγραμματιστής τον καλέσει κατά λάθος.

Το μέγεθος APK για διαφορετικά flavor μπορεί να διαφέρει κατά 5-15 MB λόγω συμπερίληψης/αποκλεισμού εξαρτήσεων. Για να αποκλειστεί μια βιβλιοθήκη από συγκεκριμένο flavor, χρησιμοποιούνται flavor-specific dependencies στο build.gradle: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Αυτή η εξάρτηση θα προστεθεί μόνο για τη free-παραλλαγή και δεν θα αυξήσει το μέγεθος της paid-έκδοσης. Για κοινές εξαρτήσεις χρησιμοποιείται implementation — περιλαμβάνονται από όλα τα flavor.

kotlin
// 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 Flavor σε Πολυαρθρωτικά Έργα

Σε πολυαρθρωτικά έργα, τα αρθρώματα βιβλιοθήκης μπορεί να μην έχουν δικά τους Product Flavors, δημιουργώντας ένα πρόβλημα: η βιβλιοθήκη μεταγλωττίζεται μία φορά (ως release), ενώ το άρθρωμα app με flavor αναμένει τη βιβλιοθήκη με την αντίστοιχη παραλλαγή. Από το AGP 8.1, οι βιβλιοθήκες μπορούν να δημοσιεύουν multiple variants μέσω του μπλοκ publishing.multipleVariants — αυτό επιτρέπει τη δημοσίευση όλων των flavor-παραλλαγών της βιβλιοθήκης σε ένα maven αποθετήριο και το άρθρωμα app επιλέγει αυτόματα την κατάλληλη.

Μια εναλλακτική προσέγγιση είναι να δηλωθούν οι ίδιες flavorDimensions και productFlavors στη βιβλιοθήκη όπως και στο άρθρωμα app. Το AGP αντιστοιχίζει αυτόματα τα flavor με πλήρη ταύτιση ονόματος από μία διάσταση. Αν το όνομα flavor στη βιβλιοθήκη ταιριάζει με το όνομα στο app, το AGP θα δημιουργήσει συνεπείς παραλλαγές. Για ευκολία συντήρησης, συνιστάται η εξαγωγή κοινών ορισμών flavor σε ένα Convention Plugin — ένα Gradle-πρόσθετο που εφαρμόζεται σε όλα τα αρθρώματα του έργου.

Για βιβλιοθήκες που δεν προορίζονται για δημοσίευση (εσωτερικά αρθρώματα), αρκεί ο συγχρονισμός των flavor μέσω του build.gradle του ριζικού έργου. Το Gradle παρέχει τη μέθοδο subprojects που επιτρέπει την εφαρμογή διαμόρφωσης σε όλα τα υποέργα. Ωστόσο, αξίζει να θυμόμαστε ότι η υπερβολική διαμόρφωση στα subprojects επιβραδύνει τη φάση διαμόρφωσης. Συνιστάται η χρήση Convention Plugins — μεταγλωττίζονται μία φορά και επαναχρησιμοποιούνται, μειώνοντας τον χρόνο διαμόρφωσης κατά 15-30%.

Συχνές Ερωτήσεις

Πόσα Product Flavors μπορώ να δημιουργήσω;

Δεν υπάρχει περιορισμός στον αριθμό, αλλά κάθε διάσταση πολλαπλασιάζει τον αριθμό των Build Variants. 4 flavor σε μία διάσταση + 2 build types = 8 παραλλαγές. 4 + 4 σε δύο διαστάσεις = 16 παραλλαγές. Συνιστώνται όχι περισσότερες από 3 διαστάσεις και 10-12 συνολικές παραλλαγές.

Μπορώ να παρακάμψω το manifest για ένα flavor;

Ναι, μέσω του source set src/<flavor>/AndroidManifest.xml. Το manifest συγχωνεύεται με το κύριο. Για αντικατάσταση ολόκληρου μπλοκ, χρησιμοποιήστε tools:node="replace". Για παράδειγμα, αντικαταστήστε την ετικέτα της εφαρμογής ή τα δικαιώματα για συγκεκριμένο flavor.

Πώς προσθέτω εξαρτήσεις για συγκεκριμένο flavor;

Χρησιμοποιήστε τη διαμόρφωση <flavorName>Implementation. Παράδειγμα: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Αυτή η εξάρτηση θα συμπεριληφθεί μόνο κατά τη μεταγλώττιση της free-παραλλαγής. Για paid: paidImplementation. Οι κοινές εξαρτήσεις ορίζονται μέσω implementation.

Ποια είναι η διαφορά μεταξύ Product Flavor και Build Type;

Το Product Flavor καθορίζει την έκδοση προϊόντος (free, paid, demo), ενώ το Build Type καθορίζει τον τρόπο μεταγλώττισης (debug, release). Τα flavor μπορούν να παρακάμπτουν applicationId, versionName, πόρους. Το Build Type διαχειρίζεται debuggable, minification, signing. Και τα δύο είναι ορθογώνια και συνδυάζονται σε ένα Build Variant.

Μπορώ να χρησιμοποιήσω Product Flavor με Jetpack Compose;

Ναι, τα Product Flavors λειτουργούν με Compose χωρίς περιορισμούς. Διαφορετικά flavor μπορούν να έχουν διαφορετικές Compose οθόνες μέσω source sets ή υλοποίησης αφηρημένων κλάσεων. Επίσης, μπορείτε να προσθέσετε flavor-specific Compose εξαρτήσεις: freeImplementation 'androidx.compose.ui:ui-tooling'.

Σύνοψη

  • Product Flavor — μηχανισμός Gradle για δημιουργία πολλαπλών εκδόσεων μιας εφαρμογής από έναν κώδικα.
  • Flavor Dimensions ομαδοποιούν τα flavor σε διαστάσεις, επιτρέποντας τον συνδυασμό διαφορετικών πτυχών της εφαρμογής.
  • Source sets για flavor παρακάμπτουν πόρους, κώδικα και manifest χωρίς αλλαγή του κύριου καταλόγου.
  • Interface + flavor implementation — καθαρή αρχιτεκτονική προσέγγιση για διαχωρισμό λειτουργικότητας.
  • Flavor-specific dependencies αποτρέπουν την εισαγωγή περιττών βιβλιοθηκών σε ακατάλληλες εκδόσεις.
  • Πολυαρθρωτικά έργα απαιτούν συγχρονισμό flavor μέσω Convention Plugins ή multiple variants publishing.
  • Σύσταση: όχι περισσότερες από 3 διαστάσεις flavor και όχι περισσότερα από 10 συνολικά Build Variants στο έργο.

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης