Product Flavor trong phát triển Android là một cơ chế Gradle cho phép tạo nhiều biến thể của cùng một ứng dụng từ một cơ sở mã dùng chung. Mỗi flavor có thể có applicationId, tài nguyên, phụ thuộc và chức năng riêng — ví dụ: phiên bản miễn phí và trả phí. Theo Google Android Developers, 2025, Product Flavors là một phần của hệ thống Build Variants và được kết hợp với Build Types thông qua flavorDimensions. Đây là cách tiếp cận tiêu chuẩn để xuất bản nhiều phiên bản ứng dụng trên Google Play.
Những điểm chính
Product Flavor là một cấu hình Gradle trong khối android.productFlavors mô tả một biến thể sản phẩm. Mỗi flavor có thể ghi đè applicationId, versionName, versionCode, minSdkVersion, targetSdkVersion, signingConfig và các tham số defaultConfig khác. Product Flavors không có giới hạn số lượng: một dự án có thể chứa 2, 5 hoặc 10 flavor — Gradle xử lý tất cả các tổ hợp.
Product Flavor giải quyết vấn đề tái sử dụng cơ sở mã (codebase reuse) — khi cần xây dựng nhiều ứng dụng khác nhau từ một kho lưu trữ duy nhất. Các kịch bản điển hình: phiên bản miễn phí có quảng cáo và phiên bản trả phí không có; phiên bản dùng thử với chức năng hạn chế; phiên bản doanh nghiệp và người tiêu dùng; ứng dụng white-label cho các khách hàng khác nhau. Nếu không có Product Flavors, mỗi phiên bản sẽ phải được duy trì trong một dự án riêng biệt, dẫn đến trùng lặp mã 60-70%.
Trong lịch sử, Product Flavors xuất hiện trong Android Gradle Plugin 0.9 (2013) như một sự thay thế cho các cấu hình ant. Trước đó, các nhà phát triển sử dụng các dự án riêng biệt cho các phiên bản khác nhau hoặc thay thế tài nguyên thủ công trước khi xây dựng. Việc giới thiệu flavor trong AGP đã thống nhất cách tiếp cận và biến nó thành tiêu chuẩn. Theo một khảo sát của JetBrains, 2024, 78% dự án Android có nhiều phiên bản sử dụng Product Flavors, phần còn lại sử dụng chuyển đổi thủ công qua BuildConfig hoặc reflection.
Build Type quản lý quá trình xây dựng (debug với gỡ lỗi, release với tối ưu hóa). Product Flavor quản lý nội dung xây dựng (free không có chức năng trả phí, paid có chúng). Build Type là cài đặt cơ sở hạ tầng, Product Flavor là cài đặt sản phẩm. Cả hai khái niệm đều trực giao: một bản build debug của flavor free khác với bản build release của flavor free chỉ ở tham số biên dịch, không phải chức năng. Product Flavor không thể được sử dụng để vô hiệu hóa trình gỡ lỗi — đó là nhiệm vụ của Build Type.
Flavor Dimensions là một cơ chế nhóm Product Flavors thành các danh mục độc lập. Nếu một ứng dụng có phiên bản miễn phí/trả phí và riêng biệt khu vực Mỹ/Châu Âu, các flavor được nhóm thành hai chiều: "tier" (free, paid) và "region" (us, eu). Gradle tạo tích Descartes của các chiều: freeUs, freeEu, paidUs, paidEu — 4 biến thể. Nếu không có chiều, Gradle sẽ coi cả bốn flavor như một mặt phẳng duy nhất và chỉ có thể chọn một.
Các chiều được khai báo trong khối flavorDimensions dưới dạng một chuỗi hoặc danh sách chuỗi. Thứ tự các chiều ảnh hưởng đến mức độ ưu tiên của source sets: chiều đầu tiên có mức ưu tiên cao nhất. Nếu chiều A (tier) được chỉ định trước, thì src/free/ sẽ ghi đè src/us/ trong trường hợp xung đột tài nguyên. Thứ tự cũng ảnh hưởng đến cách tên Variant được hình thành: đầu tiên là flavor của chiều thứ nhất, sau đó là chiều thứ hai, sau đó là Build Type: freeUsDebug.
Số lượng chiều không bị giới hạn, nhưng mỗi chiều mới nhân số lượng Build Variants. Đối với một dự án có 4 chiều (mỗi chiều 2 flavor) và 2 build types, bạn nhận được 2 × 2 × 2 × 2 × 2 = 32 biến thể. Giới hạn thực tế là 3 chiều (tối đa 8-12 biến thể). Vượt quá giới hạn đó, cấu hình Gradle chậm lại và bảng Build Variants trong Android Studio trở nên khó đọc.
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
}
}
}
// Kết quả: freeMinApi21, freeMinApi26, paidMinApi21, paidMinApi26
// Mỗi × debug/release = 8 Build Variants
Để tạo Product Flavor, bạn cần thêm khối productFlavors bên trong android, chỉ định tên flavor và các tham số của nó. Khai báo flavor tối thiểu là tên và chiều. Tất cả các tham số khác được kế thừa từ defaultConfig và có thể được ghi đè. Flavor kế thừa hoàn toàn defaultConfig, bao gồm applicationId, versionCode, testInstrumentationRunner.
Mỗi flavor có thể ghi đè applicationId — điều này cho phép cài đặt nhiều phiên bản ứng dụng trên cùng một thiết bị cùng lúc. Ví dụ: phiên bản miễn phí sẽ là com.example.app.free, trả phí — com.example.app.paid. Nếu applicationId không được ghi đè, tất cả các flavor sẽ có cùng mã định danh và không thể cài đặt song song. applicationId phải khớp với gói trong tệp kê khai (trừ khi sử dụng applicationIdSuffix).
AGP 8+ khuyến nghị sử dụng Kotlin DSL thay vì Groovy cho build.gradle. Kotlin DSL cung cấp quyền truy cập an toàn về kiểu vào cấu hình: IDE gợi ý tên tham số, kiểm tra kiểu tại thời điểm biên dịch và đánh dấu lỗi. Việc di chuyển từ Groovy sang Kotlin DSL cho Product Flavors thường bao gồm việc thay thế dấu ngoặc kép bằng dấu ngoặc đơn và thêm kiểu. AGP tương thích ngược — cả hai cú pháp đều hoạt động song song trong cùng một dự án.
// 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")
}
}
}
Mỗi Product Flavor tạo source set riêng của nó — một thư mục src/<flavorName>/. Thư mục này có thể chứa các tài nguyên, tệp nguồn và tệp kê khai đã được ghi đè. Source set của flavor hoạt động như một lớp phủ trên main: các tệp từ src/free/res/ ghi đè các tệp từ src/main/res/ có cùng tên. Điều này cho phép có các chuỗi, biểu tượng, màu sắc và bố cục khác nhau cho mỗi flavor mà không cần sửa đổi mã chính.
Để ghi đè các lớp Java/Kotlin, có hai cách tiếp cận: triển khai cụ thể theo flavor (triển khai một lớp trừu tượng trong mỗi flavor) và trường BuildConfig (rẽ nhánh trong mã). Cách tiếp cận đầu tiên sạch hơn: bạn định nghĩa một giao diện hoặc lớp trừu tượng trong main và các triển khai cụ thể trong src/free/ và src/paid/. Trong quá trình xây dựng, chỉ có triển khai của flavor hiện tại được biên dịch. Điều này mang lại lợi ích đồng thời: kích thước APK nhỏ hơn (mã trả phí không vào phiên bản miễn phí) và bảo mật (không thể vô tình gọi hàm trả phí).
AndroidManifest.xml trong source set của flavor không thay thế mà hợp nhất với tệp kê khai chính. Việc hợp nhất tuân theo các quy tắc Android: các thuộc tính trùng lặp trong cùng một phần tử được ghi đè, các thuộc tính duy nhất được thêm vào. Ví dụ: nếu tệp kê khai chính khai báo quyền INTERNET và free không khai báo, quyền internet vẫn được giữ. Tuy nhiên, tools:node="replace" cho phép thay thế toàn bộ khối tệp kê khai cho một flavor cụ thể. Điều này hữu ích khi các flavor khác nhau yêu cầu các quyền khác nhau (ghi thẻ SD cho paid, máy ảnh cho free).
<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>
Hãy xem xét một kịch bản điển hình: free — phiên bản có quảng cáo và các chức năng cơ bản, paid — không có quảng cáo, với chức năng mở rộng. Đối với phiên bản miễn phí, applicationId được đặt thành "com.example.app.free", cho trả phí — "com.example.app.paid". Cả hai phiên bản có thể được cài đặt trên cùng một thiết bị cùng lúc, vì applicationId là mã định danh duy nhất của ứng dụng trong hệ thống Android.
Về mặt kiến trúc, sự phân tách được xây dựng thông qua giao diện + triển khai flavor. Trong source set chính, giao diện PaymentService được khai báo. Trong src/free/, có một triển khai hiển thị quảng cáo trước khi thanh toán qua AdMob. Trong src/paid/ — một triển khai chuyển trực tiếp đến cổng thanh toán. Mã sử dụng PaymentService không biết triển khai nào được tải — điều này được giải quyết tại thời điểm biên dịch. Cách tiếp cận này đảm bảo rằng mã quản lý đăng ký sẽ không vào phiên bản miễn phí, ngay cả khi nhà phát triển vô tình gọi nó.
Kích thước APK cho các flavor khác nhau có thể chênh lệch 5-15 MB do bao gồm/loại trừ các phụ thuộc. Để loại trừ một thư viện khỏi một flavor cụ thể, hãy sử dụng phụ thuộc cụ thể theo flavor trong build.gradle: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Phụ thuộc này sẽ chỉ được thêm cho biến thể miễn phí và sẽ không làm tăng kích thước của phiên bản trả phí. Đối với các phụ thuộc dùng chung, hãy sử dụng implementation — tất cả các flavor đều bao gồm chúng.
// 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)
}
}
Trong các dự án đa mô-đun, các mô-đun thư viện có thể không có Product Flavors riêng, điều này tạo ra vấn đề: thư viện được xây dựng một lần (dưới dạng release), trong khi mô-đun ứng dụng có flavor mong đợi thư viện với biến thể tương ứng. Bắt đầu từ AGP 8.1, các thư viện có thể xuất bản nhiều biến thể thông qua khối publishing.multipleVariants — điều này cho phép xuất bản tất cả các biến thể flavor của thư viện vào một kho lưu trữ maven duy nhất và mô-đun ứng dụng sẽ tự động chọn đúng biến thể.
Một cách tiếp cận thay thế là khai báo các flavorDimensions và productFlavors giống nhau trong thư viện như trong mô-đun ứng dụng. AGP tự động khớp các flavor bằng cách khớp chính xác tên trong một chiều. Nếu tên flavor trong thư viện khớp với tên trong ứng dụng, AGP sẽ tạo các biến thể nhất quán. Để dễ bảo trì, nên trích xuất các định nghĩa flavor chung vào Convention Plugin — một plugin Gradle được áp dụng cho tất cả các mô-đun của dự án.
Đối với các thư viện không nhằm mục đích xuất bản (mô-đun nội bộ), chỉ cần đồng bộ hóa các flavor thông qua build.gradle của dự án gốc là đủ. Gradle cung cấp phương thức subprojects, cho phép áp dụng cấu hình cho tất cả các dự án con. Tuy nhiên, hãy nhớ rằng quá nhiều cấu hình trong subprojects làm chậm giai đoạn cấu hình. Nên sử dụng Convention Plugins — chúng được biên dịch một lần và tái sử dụng, giảm thời gian cấu hình 15-30%.
Câu hỏi thường gặp
Không có giới hạn về số lượng, nhưng mỗi chiều nhân số lượng Build Variants. 4 flavor trong một chiều + 2 build types = 8 biến thể. 4 + 4 trong hai chiều = 16 biến thể. Không nên sử dụng quá 3 chiều và tổng cộng 10-12 biến thể.
Có, thông qua source set src/<flavor>/AndroidManifest.xml. Tệp kê khai hợp nhất với tệp chính. Để thay thế toàn bộ khối, hãy sử dụng tools:node="replace". Ví dụ: thay thế nhãn ứng dụng hoặc quyền cho một flavor cụ thể.
Sử dụng cấu hình <flavorName>Implementation. Ví dụ: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Phụ thuộc này sẽ chỉ được bao gồm khi xây dựng biến thể miễn phí. Cho trả phí: paidImplementation. Các phụ thuộc chung được chỉ định thông qua implementation.
Product Flavor xác định phiên bản sản phẩm (free, paid, demo), Build Type xác định phương pháp xây dựng (debug, release). Các flavor có thể ghi đè applicationId, versionName, tài nguyên. Build Type kiểm soát debuggable, minification, signing. Cả hai đều trực giao và kết hợp thành Build Variant.
Có, Product Flavors hoạt động với Compose mà không bị hạn chế. Các flavor khác nhau có thể có màn hình Compose khác nhau thông qua source sets hoặc triển khai lớp trừu tượng. Bạn cũng có thể thêm phụ thuộc Compose cụ thể theo flavor: freeImplementation 'androidx.compose.ui:ui-tooling'.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm