1. 配置 BuildTypes
配置 buildTypes 主要用于区分 debug 和 release 包。配置 buildTypes 需要使用到签名信息,为了确保签名信息的安全,可以把签名信息放在 local.properties
文件中,如下所示。
local.properties
文件通常会被配置为 git 忽略文件,不会提交到代码仓库。
# 签名信息
storePassword=123456
keyPassword=123456
keyAlias=key0
storeFile=keystore.jks
sdk.dir=C\:\\Develop\\AndroidSDK
打开 app
下的 build.gradle
配置签名及 buildTypes
如下:
Groovy 配置
// 读取 local.properties 配置信息
def keystorePropertiesFile = rootProject.file("local.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
// ...
// 签名配置
signingConfigs {
release {
def appStoreFile = rootProject.file(keystoreProperties['storeFile'])
def appStorePass = keystoreProperties['storePassword']
def appKeyAlias = keystoreProperties['keyAlias']
def appKeyPass = keystoreProperties['keyPassword']
storeFile file(appStoreFile)
storePassword appStorePass
keyAlias appKeyAlias
keyPassword appKeyPass
v1SigningEnabled true
v2SigningEnabled true
}
}
// 构建类型配置
buildTypes {
release {
minifyEnabled true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
android.applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "渠道测试-${variant.versionName}-${variant.versionCode}-${variant.buildType.name}.apk"
}
}
}
debug {
minifyEnabled false
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
Kotlin DSL 配置
// 读取 local.properties 配置信息
val localProperties = gradleLocalProperties(rootDir)
android {
// ...
// 签名配置
signingConfigs {
val release by creating {
storeFile = rootProject.file(localProperties["storeFile"].toString())
storePassword = localProperties["storePassword"].toString()
keyAlias = localProperties["keyAlias"].toString()
keyPassword = localProperties["keyPassword"].toString()
}
}
// 构建类型配置
buildTypes {
val release by getting {
isMinifyEnabled = true
signingConfig = signingConfigs.getByName("release")
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
android.applicationVariants.all { variant ->
variant.outputs.all {
if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) {
outputFileName = "渠道测试-${variant.versionName}-${variant.versionCode}-${variant.buildType.name}.apk"
}
}
true
}
}
getByName("debug") {
isMinifyEnabled = true
signingConfig = signingConfigs.getByName("release")
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}
配置完成后,即可在 Build -> Generate Signed Bundle or APK
中看到配置的打包选项,如图 1.1 所示。
由于前面通过 outputFileName
指定了APK文件名,由于中文乱码,运行项目可能会出现以下报错信息。
Installation did not succeed.
The application could not be installed.
List of apks:
[0] 'C:\WorkSpace\Android\FlavorsDemo\app\build\intermediates\apk\debug\娓犻亾娴嬭瘯-1.0-1.apk'
Installation failed due to: 'Invalid File: C:\WorkSpace\Android\FlavorsDemo\app\build\intermediates\apk\debug\娓犻亾娴嬭瘯-1.0-1.apk'
Retry
Failed to launch an application on all devices
找到 Android Studio
安装目录下的 bin/studio64.exe.vmoptions
文件,在末尾添加如下内容,重启 Android Studio
即可。
-Dfile.encoding=UTF-8
2. 渠道配置
修改 app
下的 build.gradle
文件中的打包文件名配置并新增渠道配置如下:
Groovy 配置
// ...
android {
// ...
buildTypes {
release {
// ...
// 修改打包文件名配置
android.applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "渠道测试-${variant.flavorName}-${variant.versionName}-${variant.versionCode}-${variant.buildType.name}.apk"
}
}
}
// ...
}
// 定义一个纬度
// 解决 All flavors must now belong to a named flavor dimension
flavorDimensions "default"
// 配置渠道
productFlavors {
product1 {
dimension "default"
}
product2 {
dimension "default"
}
}
}
Kotlin DSL 配置
// ...
android {
// ...
buildTypes {
val release by getting {
// ...
// 修改打包文件名配置
android.applicationVariants.all { variant ->
variant.outputs.all {
if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) {
outputFileName = "渠道测试-${variant.flavorName}-${variant.versionName}-${variant.versionCode}-${variant.buildType.name}.apk"
}
}
true
}
}
// ...
}
// 定义一个维度
// 解决报错: All flavor must now belong to a named flavor dimension
flavorDimensions += listOf("default")
// 渠道配置
productFlavors {
create("product1") {
dimension = "default"
}
create("product2") {
dimension = "default"
}
}
}
这样我们打包的时候就可以选择不同渠道、不同类型的包,如图2.1所示。
3. 为渠道包定制名称图标
每个渠道可以指定不同的包名,版本信息等,如下所示。
Groovy 配置
// ...
android {
// ...
// 配置渠道
productFlavors {
product1 {
dimension "default"
// 每个渠道可以指定不同的包名,版本信息等
applicationId "com.viifo.flavorsdemo.product1"
versionCode 1
versionName "1.0"
// buildConfigField 会配置到 BuildConfig 文件中, 在代码中使用用于判断是什么渠道
// 实际上配置了渠道 BuildConfig 已经存在渠道信息变量 FLAVOR, 这里的 PRODUCT_FLAVORS 只做演示
buildConfigField("String", "PRODUCT_FLAVORS", "\"product1\"")
// 修改 AndroidManifest.xml 里渠道变量, 在 AndroidManifest。xml 中使用 ${app_icon} 引用
manifestPlaceholders = [app_icon: "@mipmap/ic_launcher"]
// 动态添加 string.xml 字段
// 必须要保证 string.xml 中没有该字段,否则会出现同名的字符串,引起冲突
resValue "string", "app_name", "渠道测试1"
}
product2 {
dimension "default"
// 每个渠道可以指定不同的包名,版本信息等
applicationId "com.viifo.flavorsdemo.product2"
versionCode 1
versionName "1.0"
// buildConfigField 会配置到 BuildConfig 文件中, 在代码中使用用于判断是什么渠道
// 实际上配置了渠道 BuildConfig 已经存在渠道信息变量 FLAVOR, 这里的 PRODUCT_FLAVORS 只做演示
buildConfigField("String", "PRODUCT_FLAVORS", "\"product2\"")
// 修改 AndroidManifest.xml 里渠道变量
manifestPlaceholders = [app_icon: "@mipmap/ic_launcher"]
// 动态添加 string.xml 字段
// 必须要保证 string.xml 中没有该字段,否则会出现同名的字符串,引起冲突
resValue "string", "app_name", "渠道测试2"
}
}
}
Kotlin DSL 配置
// ...
android {
// ...
// 渠道配置
productFlavors {
create("product1") {
dimension = "default"
// 每个渠道可以指定不同的包名,版本信息等
applicationId = "com.viifo.flavorsdemo.product1"
versionCode = 1
versionName = "1.0"
// buildConfigField 会配置到 BuildConfig 文件中, 在代码中使用用于判断是什么渠道
// 实际上配置了渠道 BuildConfig 已经存在渠道信息变量 FLAVOR, 这里的 PRODUCT_FLAVORS 只做演示
buildConfigField("String", "PRODUCT_FLAVORS", "\"product1\"")
// 修改 AndroidManifest.xml 里渠道变量, 在 AndroidManifest。xml 中使用 ${app_icon} 引用
manifestPlaceholders += mapOf("app_icon" to "@mipmap/ic_launcher")
// 动态添加 string.xml 字段
// 必须要保证 string.xml 中没有该字段,否则会出现同名的字符串,引起冲突
resValue("String", "app_name", "渠道测试1")
}
create("product2") {
dimension = "default"
// 每个渠道可以指定不同的包名,版本信息等
applicationId = "com.viifo.flavorsdemo.product2"
versionCode = 1
versionName = "1.0"
// buildConfigField 会配置到 BuildConfig 文件中, 在代码中使用用于判断是什么渠道
// 实际上配置了渠道 BuildConfig 已经存在渠道信息变量 FLAVOR, 这里的 PRODUCT_FLAVORS 只做演示
buildConfigField("String", "PRODUCT_FLAVORS", "\"product2\"")
// 修改 AndroidManifest.xml 里渠道变量, 在 AndroidManifest。xml 中使用 ${app_icon} 引用
manifestPlaceholders += mapOf("app_icon" to "@mipmap/ic_launcher")
// 动态添加 string.xml 字段
// 必须要保证 string.xml 中没有该字段,否则会出现同名的字符串,引起冲突
resValue("String", "app_name", "渠道测试2")
}
}
}
4. 为渠道包定制资源
以上方法使用新增 string.xml
字符串的方式修改应用名称不便于国际化,且渠道包部分页面布局可能有不同的要求。这种情况下就需要为不同的渠道包指定不同的资源,如下所示。
Groovy 配置
// ...
android {
// ...
// 配置渠道
productFlavors {
product1 {
dimension "default"
// 每个渠道可以指定不同的包名,版本信息等
applicationId "com.viifo.flavorsdemo.product1"
versionCode 1
versionName "1.0"
}
product2 {
dimension "default"
// 每个渠道可以指定不同的包名,版本信息等
applicationId "com.viifo.flavorsdemo.product2"
versionCode 1
versionName "1.0"
}
}
// 指定渠道资源目录
sourceSets {
// 默认使用资源( 配置夜间模式资源 res-night )
main.res.srcDirs = ['src/main/res', "src/main/res-night"]
// 默认使用 assets 资源
main.assets.srcDirs = ['src/assets']
// 渠道资源配置:渠道名称.res.srcDirs
// 资源目录需要手动创建
product1.res.srcDirs = ['src/main/res-product1']
product1.assets.srcDirs = ['src/assets-product1']
product2.res.srcDirs = ['src/main/res-product2']
product2.assets.srcDirs = ['src/assets-product2']
}
}
Kotlin DSL 配置
// ...
android {
// ...
// 渠道配置
productFlavors {
create("product1") {
dimension = "default"
// 每个渠道可以指定不同的包名,版本信息等
applicationId = "com.viifo.flavorsdemo.product1"
versionCode = 1
versionName = "1.0"
}
create("product2") {
dimension = "default"
// 每个渠道可以指定不同的包名,版本信息等
applicationId = "com.viifo.flavorsdemo.product2"
versionCode = 1
versionName = "1.0"
}
}
// 指定渠道资源目录
sourceSets {
// 默认使用资源( 配置夜间模式资源 res-night )
getByName("main") {
res.srcDirs("src/main/res", "src/main/res-night")
assets.srcDirs("src/assets")
}
// 渠道资源配置
getByName("product1") {
res.srcDirs("src/main/res-prod")
assets.srcDirs("src/assets-product1")
}
getByName("product2") {
res.srcDirs("src/main/res-other")
assets.srcDirs("src/assets-product2")
}
}
}
这样就可以为不同的渠道定制不同的资源文件了(包括 drawable资源、mipmap资源、layout资源等),如图 4.1 所示。
5. flavorDimensions
关于 flavorDimensions
,实际上是对不同维度和渠道的排列组合,如下所示:
Groovy 配置
// ...
android {
// ...
// 定义纬度
flavorDimensions "product", "style"
// 配置渠道
productFlavors {
product1 {
dimension "product"
}
product2 {
dimension "product"
}
style1 {
dimension "style"
}
style2 {
dimension "style"
}
}
}
Kotlin DSL 配置
// ...
android {
// ...
// 定义纬度
flavorDimensions += listOf("product", "style")
// 渠道配置
productFlavors {
create("product1") {
dimension = "default"
}
create("product2") {
dimension = "default"
}
create("style1") {
dimension = "style"
}
create("style2") {
dimension = "style"
}
}
}
即有两个维度分别是 product
和 style
,product
维度衍生出两个不同产品,style
维度衍生出两个不同的样式。维度和渠道排列组合后可组合成 产品1样式1
、产品1样式2
,产品2样式1
、产品2样式2
,如图 5.1 所示。