Build Variant trong phát triển Android là sự kết hợp của một build type và một product flavor, quyết định cách APK hoặc AAB sẽ được xây dựng: với những tham số, tài nguyên và mã nguồn nào. Mỗi biến thể build đại diện cho một cấu hình Gradle riêng với applicationId, khóa ký và các phụ thuộc riêng. Theo Google Android Developers, 2025, cấu hình đúng cách Build Variants giúp giảm thời gian build tới 40% nhờ loại bỏ tài nguyên không cần thiết cho mỗi biến thể. Hệ thống biến thể build là nền tảng của quản lý cấu hình trong các dự án Android hiện đại.
Điểm Chính
Build Variant là kết quả của việc kết hợp một Build Type và một Product Flavor. Nếu không có Product Flavors nào được xác định trong dự án, Build Variant sẽ trùng với Build Type. Gradle tự động tạo tập hợp đầy đủ các biến thể dưới dạng tích Descartes của tất cả FlavorDimensions, Product Flavors và Build Types. Ví dụ, với flavors free/paid và types debug/release, sẽ có 4 biến thể được tạo: freeDebug, freeRelease, paidDebug, paidRelease.
Mỗi Build Variant nhận tên riêng theo định dạng <Flavor><Type> với flavor được viết hoa. Gradle tạo các tác vụ riêng cho biến thể này: assembleFreeDebug, installFreeDebug, bundleFreeRelease. Trong Android Studio, việc chuyển đổi giữa các biến thể có sẵn qua bảng Build Variants (View → Tool Windows → Build Variants). Việc chọn biến thể ảnh hưởng đến mã nào được biên dịch, tài nguyên nào được bao gồm và APK/AAB nào được tạo ra.
Hệ thống Build Variants giải quyết ba nhiệm vụ chính: tách biệt cấu hình cho các môi trường khác nhau (dev/staging/production), tạo nhiều phiên bản ứng dụng (free/paid) và kiểm thử A/B các bản build. Nếu không có Build Variants, các nhà phát triển phải thủ công chuyển đổi cờ và cấu hình, dẫn đến lỗi do yếu tố con người. Theo nghiên cứu của Gradle Inc., 2024, việc áp dụng Build Variants giảm 60% lỗi build trong các dự án có ba môi trường triển khai trở lên.
AGP (Android Gradle Plugin) tính toán tất cả các tổ hợp ở giai đoạn cấu hình. Nếu một dự án có hai chiều với lần lượt hai và ba flavors, Gradle sẽ tạo 2 × 2 × 3 = 12 tổ hợp, nhân với số lượng Build Types (thường là 2). Mỗi tổ hợp nhận một tên duy nhất và một tập hợp các tác vụ. AGP tự động thêm source set cho mỗi biến thể: src/freeDebug/, src/paidRelease/, cũng như các source set tổng quát src/free/ và src/debug/. Thứ tự ưu tiên đọc tài nguyên: variant → flavor → type → main.
// Ví dụ: 4 Build Variants
// flavorDimensions "version", "server"
// version: demo, prod
// server: mock, live
// buildTypes: debug, release
// Tổng: 2 × 2 × 2 = 8 biến thể
android {
flavorDimensions "version", "server"
productFlavors {
demo { dimension "version" }
prod { dimension "version" }
mock { dimension "server" }
live { dimension "server" }
}
}
Build Type xác định cách xây dựng ứng dụng — có hoặc không có thông tin gỡ lỗi, có hoặc không tối ưu hóa, với chữ ký nào. Product Flavor xác định xây dựng cái gì — phiên bản nào của sản phẩm. Build Type là cơ chế build (debug, release, staging). Product Flavor là biến thể sản phẩm (free, paid, enterprise, demo). Cả hai khái niệm đều trực giao: bất kỳ Build Type nào cũng có thể áp dụng cho bất kỳ Product Flavor nào.
Các Build Types mặc định bao gồm debug (debuggable=true, minification=false, signing=debug.keystore) và release (debuggable=false, minification=true, signing=production.keystore). Product Flavor mặc định là một, không tên (thực chất là main source set). Các nhà phát triển có thể thêm Build Types riêng (ví dụ, “staging” với debuggable=true và minification=true) và bất kỳ số lượng Product Flavors nào. Một khác biệt khác là Build Types không thể được nhóm thành các chiều, nhưng Product Flavors thì có thể.
Khác biệt thực tế chính: defaultConfig trong build.gradle áp dụng cho tất cả Variants nhưng có thể được ghi đè trong productFlavors và buildTypes. BuildConfigField được thêm vào buildType hiển thị trong tất cả flavors của loại đó, trong khi thêm vào productFlavor hiển thị trong tất cả loại của flavor đó. Nếu một trường được xác định ở cả hai, buildType được ưu tiên (nó được áp dụng cuối cùng trong chuỗi).
| Đặc tính | Build Type | Product Flavor |
|---|---|---|
| Mục đích | Cách xây dựng | Xây dựng cái gì |
| Ví dụ | debug, release, staging | free, paid, demo, enterprise |
| Mặc định | debug + release | một (main) |
| Source Set | src/debug/, src/release/ | src/free/, src/paid/ |
| Chiều | không | flavorDimensions |
| Thứ tự áp dụng | sau flavor, ghi đè | sau defaultConfig |
| BuildConfigField | ghi đè flavor | ghi đè defaultConfig |
Việc cấu hình Build Variants được thực hiện trong khối android của tệp build.gradle ở cấp mô-đun. Đầu tiên, các buildTypes được khai báo với tham số của chúng, sau đó là flavorDimensions và productFlavors. Gradle tự động tạo các biến thể dựa trên các khai báo này. Mỗi biến thể kế thừa defaultConfig của mô-đun, ghi đè các trường được chỉ định. Thứ tự khai báo ảnh hưởng đến ưu tiên: buildTypes được áp dụng sau productFlavors.
Để truy cập một Build Variant cụ thể trong tập lệnh Gradle, sử dụng android.applicationVariants (cho mô-đun app) hoặc android.libraryVariants (cho mô-đun thư viện). Đây là một tập hợp có thể duyệt để sửa đổi cấu hình của mỗi biến thể tại thời điểm chạy cấu hình. Ví dụ, bạn có thể thêm buildConfigField theo chương trình cho tất cả các biến thể chứa từ “demo”.
Android Gradle Plugin 8.x đã thêm hỗ trợ cho onVariants — một API sạch hơn để cấu hình các biến thể thông qua lambdas. API cũ (variantOutput, variantFilter) đã được đánh dấu là không dùng nữa. Nên sử dụng onVariants cùng với onEach cho các mô-đun thư viện. Di chuyển từ variantOutput sang onVariants là bước được khuyến nghị khi nâng cấp AGP từ 7.x lên 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)
}
}
Mỗi Build Variant nhận một hệ thống phân cấp source sets riêng — các thư mục chứa mã nguồn, tài nguyên và tệp kê khai. Một source set nằm tại src/<variantName>/ (ví dụ, src/freeDebug/) và có thể chứa java/, res/, AndroidManifest.xml, assets/. Nếu một tệp tồn tại trong source set của biến thể, nó sẽ ghi đè tệp cùng tên từ source set chính (src/main/). Đối với tài nguyên, việc hợp nhất diễn ra thay vì thay thế — hệ thống hợp nhất tài nguyên từ tất cả các source sets đang hoạt động, ưu tiên các tài nguyên dành riêng cho biến thể.
Các source sets cho một Build Variant được xây dựng theo chuỗi: src/main/ → src/flavor/ → src/type/ → src/flavorType/. Ví dụ, đối với paidRelease, main được áp dụng trước, sau đó là paid, rồi release, rồi paidRelease. Mỗi source set tiếp theo sẽ ghi đè source set trước đó. Điều này có nghĩa là src/release/res/values/strings.xml sẽ ghi đè các chuỗi tương tự từ src/paid/, nhưng src/paidRelease/res/ có ưu tiên cao hơn.
Sử dụng source sets cho các biến thể là cách được khuyến nghị để tùy chỉnh tài nguyên. Thay vì kiểm tra BuildConfig.FLAVOR trong mã và rẽ nhánh logic, bạn chỉ cần đặt các tệp khác nhau vào các source sets khác nhau. Ví dụ, biểu tượng cho các phiên bản free và paid được đặt trong src/free/res/ và src/paid/res/ tương ứng, và AndroidManifest với các quyền khác nhau được đặt trong src/free/AndroidManifest.xml và src/paid/AndroidManifest.xml. Cách này sạch hơn, nhanh hơn (tài nguyên được biên dịch, không kiểm tra khi chạy) và an toàn hơn (bạn không thể vô tình đưa chức năng trả phí vào phiên bản miễn phí do lỗi trong mã).
Trong các dự án đa mô-đun, mỗi mô-đun (thư viện) có thể có Build Variants riêng. AGP tự động đồng bộ hóa các biến thể: nếu mô-đun app xây dựng paidRelease, tất cả các thư viện phụ thuộc cũng được xây dựng trong các biến thể tương ứng với paidRelease. Vấn đề phát sinh khi một thư viện không có product flavors nhưng mô-đun app có — khi đó thư viện được xây dựng một lần (release hoặc debug tùy theo loại).
Đối với các mô-đun thư viện, Build Variant mặc định trùng với Build Type của mô-đun app, vì các thư viện không có product flavors. Nếu một thư viện cần thích ứng với flavor của mô-đun app, các flavorDimensions và productFlavors tương tự phải được khai báo trong thư viện. AGP so khớp flavors bằng tên chính xác. Gradle khuyến nghị đồng bộ hóa flavors thông qua cấu hình build trong dự án gốc bằng cách sử dụng subprojects hoặc Convention Plugins.
Bắt đầu từ AGP 8.1, các thư viện có thể xuất bản nhiều biến thể — xuất bản tất cả các biến thể thư viện vào một kho lưu trữ maven cùng một lúc. Điều này giải quyết vấn đề khi mô-đun app sử dụng flavor trả phí nhưng thư viện chỉ được xuất bản cho miễn phí. Multiple variants publishing (MVP) cho phép dự án phụ thuộc tự động chọn biến thể cần thiết. Để bật MVP, thêm publishing { multipleVariants { ... } } vào build.gradle của thư viện.
Đôi khi cần vô hiệu hóa một số Build Variants — ví dụ, nếu tổ hợp mockRelease không có ý nghĩa (máy chủ mock không nên đi vào môi trường production). Gradle cung cấp variantFilter — một khối DSL nơi bạn có thể kiểm tra các thuộc tính của mỗi biến thể và vô hiệu hóa nó qua setIgnore(true). VariantFilter được áp dụng ở giai đoạn cấu hình, trước khi tạo tác vụ, do đó biến thể bị vô hiệu hóa sẽ không tạo các tác vụ assemble và install.
Việc lọc cũng hữu ích để tăng tốc độ build. Nếu một dự án có 8 biến thể nhưng nhà phát triển chỉ làm việc trên một, 7 biến thể còn lại vẫn phải trải qua cấu hình. Khi sử dụng variantFilter, các biến thể bị vô hiệu hóa không tạo tác vụ, giảm thời gian cấu hình xuống 30-50% cho các dự án có 6+ chiều flavor. Trong CI/CD, bạn có thể lọc động các biến thể qua tham số dòng lệnh -PbuildOnly=paidRelease.
android {
variantFilter { variant ->
// Vô hiệu hóa mock cho release và demo cho 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)
}
}
}
// Lọc động qua tham số
if (project.hasProperty("buildOnly")) {
def target = project.property("buildOnly")
android.variantFilter { variant ->
variant.setIgnore(variant.name != target)
}
}
Câu hỏi Thường gặp
Không có giới hạn, nhưng Gradle tạo tích Descartes của tất cả các flavor và loại. Nếu bạn có 3 chiều với mỗi chiều 3 flavor và 3 build types, bạn sẽ có 27 biến thể. Quá nhiều biến thể làm chậm cấu hình. Nên không quá 10–12 biến thể trong một mô-đun.
flavorDimensions nhóm các Product Flavors thành các trục độc lập. Ví dụ, chiều “tier” (free, paid) và chiều “region” (us, eu). Không có chiều, tất cả các flavor thuộc về một trục và Gradle sẽ chỉ chọn một flavor từ tất cả (bạn không thể có free+us và paid+eu là các biến thể riêng biệt).
Trong khối productFlavor hoặc buildType, chỉ định applicationId. Ví dụ, cho phiên bản miễn phí: free { applicationId “com.example.app.free” }. Trong tệp kê khai, sử dụng ${applicationId} — Gradle sẽ tự động thay thế giá trị. Điều này cho phép cài đặt cả hai biến thể trên một thiết bị.
Trong iOS, tương đương với Build Variants là sự kết hợp của Scheme + Configuration. Các Xcode Schemes được cấu hình thông qua các cấu hình Debug/Release với các tham số khác nhau. Đối với nhiều phiên bản (free/paid), Build Configurations và Preprocessor Macros được sử dụng. Trên Android, khái niệm này được chính thức hóa hơn và tích hợp sẵn trong Gradle.
Có, mỗi biến thể có thể có kích thước APK khác nhau. Các bản build debug bao gồm thông tin gỡ lỗi, SDK và tài nguyên không được hỗ trợ. Các bản build release với minification và resource shrinking tạo ra kích thước tối thiểu. Product Flavor cũng ảnh hưởng đến kích thước: phiên bản miễn phí không có thư viện trả phí sẽ nhỏ hơn phiên bản trả phí bằng kích thước của các thư viện đó.
Tổng kế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