项目结构:
![795730-20180721235347460-145586553.png](https://i-blog.csdnimg.cn/blog_migrate/ce92b6b8a7f820f5e59a7e59af688205.png)
project 目录的 build.gradle 文件
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: "config/config_dependencies.gradle" //目的是将所依赖的系统版本、第三方库版本等抽到专门的文件中进行统一的管理
apply from: "config/config_android.gradle" //后面引用的 gradle 配置中的属性会覆盖掉前面引用的配置中的属性
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
25
25
1
// Top-level build file where you can add configuration options common to all sub-projects/modules.
2
apply from: "config/config_dependencies.gradle" //目的是将所依赖的系统版本、第三方库版本等抽到专门的文件中进行统一的管理
3
apply from: "config/config_android.gradle" //后面引用的 gradle 配置中的属性会覆盖掉前面引用的配置中的属性
4
5
buildscript {
6
7
repositories {
8
google()
9
jcenter()
10
}
11
dependencies {
12
classpath 'com.android.tools.build:gradle:3.1.3'
13
}
14
}
15
16
allprojects {
17
repositories {
18
google()
19
jcenter()
20
}
21
}
22
23
task clean(type: Delete) {
24
delete rootProject.buildDir
25
}
自定义的部分只有最上面的那两行,其他都是默认的
自定义的两个 .gradle 文件
作用:对所依赖的系统版本、第三方库版本等进行统一的管理。
这两个文件我都放到了根目录下的新建的 config 目录中。
config_android.gradle
//作用:对所依赖的系统版本、第三方库版本等进行统一的管理。
// 注意:每一个此类型的文件必须包含一个根节点ext,且ext下必须包含一个属性android,除此之外可以随意定义其他属性或方法
ext {
android = [compileSdkVersion: 26,
buildToolsVersion: "27.0.3",
minSdkVersion : 14,
targetSdkVersion : 22,
versionName : getGitBranch() + "_" + getGitTag() + "_" + getGitSHA(), //根据git信息生成版本名
versionCode : getGitCommitCount(), //根据git提交次数生成版本号
]
}
//获取Git 分支名,参考 https://blog.csdn.net/ouyang_peng/article/details/77802596
static def getGitBranch() {
return 'git symbolic-ref --short -q HEAD'.execute().text.trim() //例如 master
}
//获取Git Tag
def getGitTag() {
return 'git describe --tags'.execute([], project.rootDir).text.trim() //例如 bqt20094
//注意,如果没有设置 tag 的话结果为:fatal: No names found, cannot describe anything.
}
//获取Git 版本号
static def getGitSHA() {
return 'git rev-parse --short HEAD'.execute().text.trim() //例如 3d5851e
}
//获取Git 提交次数
static def getGitCommitCount() {
return 'git rev-list --count HEAD'.execute().text.trim().toInteger() //例如 8
}
32
32
1
//作用:对所依赖的系统版本、第三方库版本等进行统一的管理。
2
// 注意:每一个此类型的文件必须包含一个根节点ext,且ext下必须包含一个属性android,除此之外可以随意定义其他属性或方法
3
ext {
4
android = [compileSdkVersion: 26,
5
buildToolsVersion: "27.0.3",
6
minSdkVersion : 14,
7
targetSdkVersion : 22,
8
versionName : getGitBranch() + "_" + getGitTag() + "_" + getGitSHA(), //根据git信息生成版本名
9
versionCode : getGitCommitCount(), //根据git提交次数生成版本号
10
]
11
}
12
13
//获取Git 分支名,参考 https://blog.csdn.net/ouyang_peng/article/details/77802596
14
static def getGitBranch() {
15
return 'git symbolic-ref --short -q HEAD'.execute().text.trim() //例如 master
16
}
17
18
//获取Git Tag
19
def getGitTag() {
20
return 'git describe --tags'.execute([], project.rootDir).text.trim() //例如 bqt20094
21
//注意,如果没有设置 tag 的话结果为:fatal: No names found, cannot describe anything.
22
}
23
24
//获取Git 版本号
25
static def getGitSHA() {
26
return 'git rev-parse --short HEAD'.execute().text.trim() //例如 3d5851e
27
}
28
29
//获取Git 提交次数
30
static def getGitCommitCount() {
31
return 'git rev-list --count HEAD'.execute().text.trim().toInteger() //例如 8
32
}
config_dependencies.gradle
//作用:对所依赖的系统版本、第三方库版本等进行统一的管理。
// 注意:每一个此类型的文件必须包含一个根节点ext,且ext下必须包含一个属性android,除此之外可以随意定义其他属性或方法
ext {
android = [] //因为后面引用的 gradle 配置中的属性会覆盖掉前面引用的配置中的属性,所以不应该在这里操作任何 android 属性的成员
version = [
support_v7: "27.1.1",
gson : "2.6.2", // gson
]
dependencies = [
"support_v7": "com.android.support:appcompat-v7:${version["support_v7"]}", // android support v7 library
"gson" : "com.google.code.gson:gson:${version["gson"]}", // gson
]
}
16
16
1
//作用:对所依赖的系统版本、第三方库版本等进行统一的管理。
2
// 注意:每一个此类型的文件必须包含一个根节点ext,且ext下必须包含一个属性android,除此之外可以随意定义其他属性或方法
3
ext {
4
android = [] //因为后面引用的 gradle 配置中的属性会覆盖掉前面引用的配置中的属性,所以不应该在这里操作任何 android 属性的成员
5
6
7
version = [
8
support_v7: "27.1.1",
9
gson : "2.6.2", // gson
10
]
11
12
dependencies = [
13
"support_v7": "com.android.support:appcompat-v7:${version["support_v7"]}", // android support v7 library
14
"gson" : "com.google.code.gson:gson:${version["gson"]}", // gson
15
]
16
}
app 目录的 build.gradle 文件
这个是最核心、最关键、最复杂的一个配置文件。
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
buildToolsVersion rootProject.ext.android["buildToolsVersion"]
//*******************************************************************************************************************************
// 【defaultConfig】
//*******************************************************************************************************************************
defaultConfig {
applicationId "com.bqt.test"
minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
versionName rootProject.ext.android["versionName"]
versionCode rootProject.ext.android["versionCode"]
flavorDimensions "bqt" //必须带上一个 flavorDimensions[维度、尺寸],值可以随意
testInstrumentationRunner "android.support.com.bqt.test.runner.AndroidJUnitRunner"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// 放在这里可以在任何地方获取到。key可以不必用引号括起来,但是字符串类型的值必须使用【'"】和【"'】括起来才可以
// 否则,值最匹配什么类型就是什么类型。比如【"1"】和【"@drawable/icon"】是int类型,而【'"1"'】才代表字符串类型
manifestPlaceholders = [release_time_value: releaseTime()]
}
//*******************************************************************************************************************************
// 【signingConfigs】
//*******************************************************************************************************************************
signingConfigs { //配置签名信息,例如各个不同产品的签名文件位置、密码、昵称、昵称密码等,要放在 buildTypes 上面配置
beakeystore { //除了默认的 debug 签名外,signingConfigs 中的其他签名必须完整的配置签名相关的信息
storeFile file('../config/bea.keystore')
storePassword 'beachinambk'
keyAlias 'bea.keystore'
keyPassword 'beachinambk'
v2SigningEnabled true
}
debug { //默认的 debug 签名,不需要指定签名信息,当然你也可以专门生成并配置一个 debug 签名
v2SigningEnabled true
}
beakeystore2.initWith(beakeystore) //initWith的作用是:拷贝指定签名 beakeystore 中的所有配置到新的签名中
beakeystore2 {
storeFile file('../config/bea2.keystore') //除了签名文件不一样外,密码、昵称、昵称密码等和 beakeystore 完全一样
}
}
//*******************************************************************************************************************************
// 【buildTypes】
//*******************************************************************************************************************************
buildTypes { // 用于区分release包和debug等不同构建类型的包。默认正式签名时会走release脚本,调试签名时会走debug脚本
release {
debuggable false //是否可调试。debug模式默认为true,release模式默认是false
zipAlignEnabled true// 是否启用Zipalign优化,对齐app所有资源。因为对齐处理发生在签名之后,所以启用时必须指定了签名信息
shrinkResources true// 在构建时是否自动移除无用的资源以减小apk包的大小(包括图片,布局,菜单等,但不包括value资源文件)
minifyEnabled true //是否启用混淆。debug与release的默认值都为false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' //配置混淆文件所在路径
//前一部分代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明,后一个文件是自己的定义混淆文件
consumerProguardFiles 'consumer-proguard-rules.pro'
signingConfig signingConfigs.beakeystore //使用自己配置的签名信息
manifestPlaceholders = [baidu_map_key_value: "20094"] //注意,这里的值用双引号括起来并不代表其就是String类型
buildConfigField "String", "BASE_URL", '"http://110.com/"' //自定义属性,可以在 BuildConfig 中获取设置的值
}
debug {
signingConfig signingConfigs.debug //使用默认的 debug 签名,即使不配置,也是使用的此默认签名
manifestPlaceholders = [baidu_map_key_value: "10086"] //注意,这里的值用双引号括起来并不代表其就是String类型
buildConfigField "String", "BASE_URL", '"http://120.com/"' //自定义属性,可以在 BuildConfig 中获取设置的值
}
pre.initWith(release) //initWith的作用是:拷贝指定构建类型 release 中的所有变量到 pre,然后gradle就会自动生成新的task
pre { //预发布版本,该版本除了下面指定的配置与release版本不同外,其他都与release相同
debuggable true
}
}
//*******************************************************************************************************************************
// 【productFlavors】
//*******************************************************************************************************************************
productFlavors { //用于为不同的产品分配专有属性;构建基于同一份代码的不同Android项目有差异的部分;多渠道打包
productA { //新建产品productA,在defaultConfig的基础上做修改
applicationId "com.bqt.test.productA" // defaultConfig 中可以设置的属性在这里基本也都可以设置
resValue "string", "app_icon_name", "产品A" //替换掉指定的资源文件,比如应用名
resValue "color", "color_footer", "#ffff0000" //替换掉的资源可以是没有定义过的
buildConfigField "boolean", "isHongkongUser", "true"//自定义属性,可以在 BuildConfig 中获取设置的值
buildConfigField "int", "countryCode", "20094"//自定义属性,可以在 BuildConfig 中获取设置的值
manifestPlaceholders = [app_logo : "@drawable/icon",//用于替换清单文件中的标签,比如应用logo,多渠道信息
app_channel_value: "小米应用市场",
]
}
productB { //新建产品productB
applicationIdSuffix ".productB" //在defaultConfig中默认applicationId的基础上在后面追加一段
versionNameSuffix "_productB" //在defaultConfig中默认versionName的基础上在后面追加一段
resValue "string", "app_icon_name", "产品B" //替换掉指定的资源文件,比如应用名
resValue "color", "color_footer", "#ff0000ff" //替换掉的资源可以是没有定义过的
buildConfigField "boolean", "isHongkongUser", "false"//自定义属性,可以在 BuildConfig 中获取设置的值
buildConfigField "int", "countryCode", "20095"//自定义属性,可以在 BuildConfig 中获取设置的值
manifestPlaceholders = [app_logo : "@drawable/icon2",//用于替换清单文件中的标签,比如应用logo,多渠道信息
app_channel_value: "应用宝",
]
}
// 批量处理所有的 productFlavors,常用于多渠道打包时的统一配置渠道名等信息
productFlavors.all {
//注意,这里配置后会覆盖掉上面每个Flavor已配置的 manifestPlaceholders ,所以设置时要保证上面配置的所有标签都要有才行
//flavor -> flavor.manifestPlaceholders = [app_logo: "@drawable/icon2", app_channel_value: name + " 首发"]
}
//自定义所有输出的APK包的位置与名称,多渠道打包时也非常有用,如果不设置,每个Flavor构建的APK都会
android.applicationVariants.all { variant ->
def sep = "_" //分隔符
def time = releaseTime() //打包时间
def buildType = variant.buildType.name //构建类型,比如 release 或 debug
def versionCode = variant.versionCode //版本号
variant.outputs.all { output ->
variant.productFlavors.each { flavor ->
def applicationId = flavor.applicationId == null ? defaultConfig.applicationId : flavor.applicationId // flavor没配置时为空
def flavorName = flavor.name //应用名
def channelName = flavor.manifestPlaceholders["app_channel_value"] //渠道名
if (buildType == "release") { //变更输出路径。变更后会导致通过Run方式构建的Apk无法自动安装
def apkPath = project.rootDir.absolutePath + "/apks/${applicationId}/" //可以通过占位符【${}】或【+】来拼接字符串
variant.getPackageApplication().outputDirectory = new File(apkPath)
}
def apkName = flavorName + sep + buildType + sep + channelName + sep + "v" + versionCode + sep + time + ".apk"
println "【文件名】" + apkName //例如【productB_release_应用宝_v16_20180719_21.18.15_星期四.apk】
output.outputFileName = apkName //重新对apk命名
//注意,Gradle4.0以下版本对apk重命名的方式为:variant.outputFile = new File(variant.outputFile.parent, apkName)
}
}
}
}
}
//***********************************************************************************************************************************
// 【dependencies】
//***********************************************************************************************************************************
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation rootProject.ext.dependencies["support_v7"] // android support library
implementation rootProject.ext.dependencies["gson"] // gson
}
static def releaseTime() {
return new Date().format("yyyyMMdd_HH.mm.ss_E", TimeZone.getDefault()) // 或 TimeZone.getTimeZone("GMT+08:00")
}
161
161
1
apply plugin: 'com.android.application'
2
3
android {
4
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
5
buildToolsVersion rootProject.ext.android["buildToolsVersion"]
6
7
//*******************************************************************************************************************************
8
// 【defaultConfig】
9
//*******************************************************************************************************************************
10
defaultConfig {
11
applicationId "com.bqt.test"
12
minSdkVersion rootProject.ext.android["minSdkVersion"]
13
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
14
versionName rootProject.ext.android["versionName"]
15
versionCode rootProject.ext.android["versionCode"]
16
17
flavorDimensions "bqt" //必须带上一个 flavorDimensions[维度、尺寸],值可以随意
18
testInstrumentationRunner "android.support.com.bqt.test.runner.AndroidJUnitRunner"
19
compileOptions {
20
sourceCompatibility JavaVersion.VERSION_1_8
21
targetCompatibility JavaVersion.VERSION_1_8
22
}
23
24
// 放在这里可以在任何地方获取到。key可以不必用引号括起来,但是字符串类型的值必须使用【'"】和【"'】括起来才可以
25
// 否则,值最匹配什么类型就是什么类型。比如【"1"】和【"@drawable/icon"】是int类型,而【'"1"'】才代表字符串类型
26
manifestPlaceholders = [release_time_value: releaseTime()]
27
}
28
29
//*******************************************************************************************************************************
30
// 【signingConfigs】
31
//*******************************************************************************************************************************
32
signingConfigs { //配置签名信息,例如各个不同产品的签名文件位置、密码、昵称、昵称密码等,要放在 buildTypes 上面配置
33
beakeystore { //除了默认的 debug 签名外,signingConfigs 中的其他签名必须完整的配置签名相关的信息
34
storeFile file('../config/bea.keystore')
35
storePassword 'beachinambk'
36
keyAlias 'bea.keystore'
37
keyPassword 'beachinambk'
38
39
v2SigningEnabled true
40
}
41
42
debug { //默认的 debug 签名,不需要指定签名信息,当然你也可以专门生成并配置一个 debug 签名
43
v2SigningEnabled true
44
}
45
46
beakeystore2.initWith(beakeystore) //initWith的作用是:拷贝指定签名 beakeystore 中的所有配置到新的签名中
47
beakeystore2 {
48
storeFile file('../config/bea2.keystore') //除了签名文件不一样外,密码、昵称、昵称密码等和 beakeystore 完全一样
49
}
50
}
51
52
//*******************************************************************************************************************************
53
// 【buildTypes】
54
//*******************************************************************************************************************************
55
buildTypes { // 用于区分release包和debug等不同构建类型的包。默认正式签名时会走release脚本,调试签名时会走debug脚本
56
release {
57
debuggable false //是否可调试。debug模式默认为true,release模式默认是false
58
zipAlignEnabled true// 是否启用Zipalign优化,对齐app所有资源。因为对齐处理发生在签名之后,所以启用时必须指定了签名信息
59
shrinkResources true// 在构建时是否自动移除无用的资源以减小apk包的大小(包括图片,布局,菜单等,但不包括value资源文件)
60
61
minifyEnabled true //是否启用混淆。debug与release的默认值都为false
62
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' //配置混淆文件所在路径
63
//前一部分代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明,后一个文件是自己的定义混淆文件
64
consumerProguardFiles 'consumer-proguard-rules.pro'
65
66
signingConfig signingConfigs.beakeystore //使用自己配置的签名信息
67
68
manifestPlaceholders = [baidu_map_key_value: "20094"] //注意,这里的值用双引号括起来并不代表其就是String类型
69
buildConfigField "String", "BASE_URL", '"http://110.com/"' //自定义属性,可以在 BuildConfig 中获取设置的值
70
}
71
72
debug {
73
signingConfig signingConfigs.debug //使用默认的 debug 签名,即使不配置,也是使用的此默认签名
74
75
manifestPlaceholders = [baidu_map_key_value: "10086"] //注意,这里的值用双引号括起来并不代表其就是String类型
76
buildConfigField "String", "BASE_URL", '"http://120.com/"' //自定义属性,可以在 BuildConfig 中获取设置的值
77
}
78
79
pre.initWith(release) //initWith的作用是:拷贝指定构建类型 release 中的所有变量到 pre,然后gradle就会自动生成新的task
80
pre { //预发布版本,该版本除了下面指定的配置与release版本不同外,其他都与release相同
81
debuggable true
82
}
83
}
84
85
//*******************************************************************************************************************************
86
// 【productFlavors】
87
//*******************************************************************************************************************************
88
productFlavors { //用于为不同的产品分配专有属性;构建基于同一份代码的不同Android项目有差异的部分;多渠道打包
89
productA { //新建产品productA,在defaultConfig的基础上做修改
90
applicationId "com.bqt.test.productA" // defaultConfig 中可以设置的属性在这里基本也都可以设置
91
92
resValue "string", "app_icon_name", "产品A" //替换掉指定的资源文件,比如应用名
93
resValue "color", "color_footer", "#ffff0000" //替换掉的资源可以是没有定义过的
94
95
buildConfigField "boolean", "isHongkongUser", "true"//自定义属性,可以在 BuildConfig 中获取设置的值
96
buildConfigField "int", "countryCode", "20094"//自定义属性,可以在 BuildConfig 中获取设置的值
97
98
manifestPlaceholders = [app_logo : "@drawable/icon",//用于替换清单文件中的标签,比如应用logo,多渠道信息
99
app_channel_value: "小米应用市场",
100
]
101
}
102
103
productB { //新建产品productB
104
applicationIdSuffix ".productB" //在defaultConfig中默认applicationId的基础上在后面追加一段
105
versionNameSuffix "_productB" //在defaultConfig中默认versionName的基础上在后面追加一段
106
107
resValue "string", "app_icon_name", "产品B" //替换掉指定的资源文件,比如应用名
108
resValue "color", "color_footer", "#ff0000ff" //替换掉的资源可以是没有定义过的
109
110
buildConfigField "boolean", "isHongkongUser", "false"//自定义属性,可以在 BuildConfig 中获取设置的值
111
buildConfigField "int", "countryCode", "20095"//自定义属性,可以在 BuildConfig 中获取设置的值
112
113
manifestPlaceholders = [app_logo : "@drawable/icon2",//用于替换清单文件中的标签,比如应用logo,多渠道信息
114
app_channel_value: "应用宝",
115
]
116
}
117
118
// 批量处理所有的 productFlavors,常用于多渠道打包时的统一配置渠道名等信息
119
productFlavors.all {
120
//注意,这里配置后会覆盖掉上面每个Flavor已配置的 manifestPlaceholders ,所以设置时要保证上面配置的所有标签都要有才行
121
//flavor -> flavor.manifestPlaceholders = [app_logo: "@drawable/icon2", app_channel_value: name + " 首发"]
122
}
123
124
//自定义所有输出的APK包的位置与名称,多渠道打包时也非常有用,如果不设置,每个Flavor构建的APK都会
125
android.applicationVariants.all { variant ->
126
def sep = "_" //分隔符
127
def time = releaseTime() //打包时间
128
def buildType = variant.buildType.name //构建类型,比如 release 或 debug
129
def versionCode = variant.versionCode //版本号
130
variant.outputs.all { output ->
131
variant.productFlavors.each { flavor ->
132
def applicationId = flavor.applicationId == null ? defaultConfig.applicationId : flavor.applicationId // flavor没配置时为空
133
def flavorName = flavor.name //应用名
134
def channelName = flavor.manifestPlaceholders["app_channel_value"] //渠道名
135
136
if (buildType == "release") { //变更输出路径。变更后会导致通过Run方式构建的Apk无法自动安装
137
def apkPath = project.rootDir.absolutePath + "/apks/${applicationId}/" //可以通过占位符【${}】或【+】来拼接字符串
138
variant.getPackageApplication().outputDirectory = new File(apkPath)
139
}
140
def apkName = flavorName + sep + buildType + sep + channelName + sep + "v" + versionCode + sep + time + ".apk"
141
println "【文件名】" + apkName //例如【productB_release_应用宝_v16_20180719_21.18.15_星期四.apk】
142
output.outputFileName = apkName //重新对apk命名
143
//注意,Gradle4.0以下版本对apk重命名的方式为:variant.outputFile = new File(variant.outputFile.parent, apkName)
144
}
145
}
146
}
147
}
148
}
149
150
//***********************************************************************************************************************************
151
// 【dependencies】
152
//***********************************************************************************************************************************
153
dependencies {
154
implementation fileTree(dir: 'libs', include: ['*.jar'])
155
implementation rootProject.ext.dependencies["support_v7"] // android support library
156
implementation rootProject.ext.dependencies["gson"] // gson
157
}
158
159
static def releaseTime() {
160
return new Date().format("yyyyMMdd_HH.mm.ss_E", TimeZone.getDefault()) // 或 TimeZone.getTimeZone("GMT+08:00")
161
}
设置完之后,点击下面 Build Variants 栏可以切换默认点击 Run 时运行的任务:
![795730-20180721235350861-1075918481.png](https://i-blog.csdnimg.cn/blog_migrate/6ac3b2b3c6ed195ab0aeab344d3c6456.png)
我们在上面的 productFlavors 中设置了两个产品,在 buildTypes 中设置了三个构建类型,所以我们可选的打包任务就有 2*3 = 6 个。
Generate Signed APK 时也有
2*3 = 6 个选择:
![795730-20180721235351426-1227315978.png](https://i-blog.csdnimg.cn/blog_migrate/e245a589ec5f3fb989c3b002ca8fe7ac.png)
![795730-20180721235352014-310983334.png](https://i-blog.csdnimg.cn/blog_migrate/4560f7c8341b334eea68c19ae1dceed8.png)
可以对不同的产品引入不同的包,例如:
productACompile "com....gson..." //产品A需要引入gson
productBCompile "com....glide..." //产品B需要引入glide
2
2
1
productACompile "com....gson..." //产品A需要引入gson
2
productBCompile "com....glide..." //产品B需要引入glide
可以对不同的产品引入不同的包,例如:
sourceSets {
main {
aidl.srcDirs = ['src/main/aidl']
jni.srcDirs 'src/main/jni'
}
productA {
java.srcDirs = ['src/productA/java']
}
productB {
java.srcDirs = ['src/productB/java']
}
}
12
12
1
sourceSets {
2
main {
3
aidl.srcDirs = ['src/main/aidl']
4
jni.srcDirs 'src/main/jni'
5
}
6
productA {
7
java.srcDirs = ['src/productA/java']
8
}
9
productB {
10
java.srcDirs = ['src/productB/java']
11
}
12
}
实际 build 时的配置信息
注意,下面的文件不是哪里生成的,而是我自己提取出来的。
{
"debuggable": "false",
"embedMicroApp": "true",
"jniDebuggable": "false",
"mBuildConfigFields": {
"BASE_URL": "http://120.com/",
"countryCode": 20094,
"isHongkongUser": "com.android.builder.internal.ClassFieldImpl@f39f32b9"
},
"mConsumerProguardFiles": [
"你指定的自动寻找的第三方库里的混淆文件"
],
"mManifestPlaceholders": {
"baidu_map_key_value": "20094"
},
"mProguardFiles": [
"C:/Android/_coder/_workspace_as/FragmentTest/build/intermediates/proguard-files/proguard-android.txt-3.1.3",
"C:/Android/_coder/_workspace_as/FragmentTest/app/proguard-rules.pro"
],
"mResValues": {
"app_icon_name": "com.android.builder.internal.ClassFieldImpl@efa088c5",
"color_footer":"com.android.builder.internal.ClassFieldImpl@391e62f"
},
"minifyEnabled": "true",
"name": "release",
"pseudoLocalesEnabled": "false",
"renderscriptDebuggable": "false",
"renderscriptOptimLevel": "3",
"signingConfig": {
"keyAlias": "bea.keystore",
"keyPassword": "beachinambk",
"name": "beakeystore",
"storeFile": "C:/Android/_coder/_workspace_as/FragmentTest/config/bea.keystore",
"storePassword": "beachinambk",
"storeType": "C:/Android/_coder/_workspace_as/FragmentTest/config/bea.keystore",
"v1SigningEnabled": "true",
"v2SigningEnabled": "true"
},
"testCoverageEnabled": "false",
"zipAlignEnabled": "true"
}
41
41
1
{
2
"debuggable": "false",
3
"embedMicroApp": "true",
4
"jniDebuggable": "false",
5
"mBuildConfigFields": {
6
"BASE_URL": "http://120.com/",
7
"countryCode": 20094,
8
"isHongkongUser": "com.android.builder.internal.ClassFieldImpl@f39f32b9"
9
},
10
"mConsumerProguardFiles": [
11
"你指定的自动寻找的第三方库里的混淆文件"
12
],
13
"mManifestPlaceholders": {
14
"baidu_map_key_value": "20094"
15
},
16
"mProguardFiles": [
17
"C:/Android/_coder/_workspace_as/FragmentTest/build/intermediates/proguard-files/proguard-android.txt-3.1.3",
18
"C:/Android/_coder/_workspace_as/FragmentTest/app/proguard-rules.pro"
19
],
20
"mResValues": {
21
"app_icon_name": "com.android.builder.internal.ClassFieldImpl@efa088c5",
22
"color_footer":"com.android.builder.internal.ClassFieldImpl@391e62f"
23
},
24
"minifyEnabled": "true",
25
"name": "release",
26
"pseudoLocalesEnabled": "false",
27
"renderscriptDebuggable": "false",
28
"renderscriptOptimLevel": "3",
29
"signingConfig": {
30
"keyAlias": "bea.keystore",
31
"keyPassword": "beachinambk",
32
"name": "beakeystore",
33
"storeFile": "C:/Android/_coder/_workspace_as/FragmentTest/config/bea.keystore",
34
"storePassword": "beachinambk",
35
"storeType": "C:/Android/_coder/_workspace_as/FragmentTest/config/bea.keystore",
36
"v1SigningEnabled": "true",
37
"v2SigningEnabled": "true"
38
},
39
"testCoverageEnabled": "false",
40
"zipAlignEnabled": "true"
41
}
里面的一些关键部分:
- mBuildConfigFields 对应通过 buildConfigField 自定义的属性
- mConsumerProguardFiles 对应通过 consumerProguardFiles 指定的自动寻找的第三方库里的混淆文件
- mManifestPlaceholders 对应通过 manifestPlaceholders 替换清单文件中的标签
- mProguardFiles 对应通过 proguardFiles 设置的混淆文件
- mResValues 对应通过 resValue 替换掉指定的资源文件
- signingConfig 代表设置的签名信息
其他字段基本见名知意。
以上内容我是这么产生的,首先将某一个 buildConfigField 的定义故意改错,比如定义了一个这个东西:
android {
productFlavors {
productB {
buildConfigField "int", "countryCode", 20095//正确的格式是要用引号包住值,这里没有加引号所以编译就会报错
}
}
}
7
7
1
android {
2
productFlavors {
3
productB {
4
buildConfigField "int", "countryCode", 20095//正确的格式是要用引号包住值,这里没有加引号所以编译就会报错
5
}
6
}
7
}
则编译时就会报错:
Could not find method buildConfigField() for arguments [int, countryCode, 20095] on ProductFlavor_Decorated{name=productB, dimension=null, minSdkVersion=null, targetSdkVersion=null, renderscriptTargetApi=null, renderscriptSupportModeEnabled=null, renderscriptSupportModeBlasEnabled=null, renderscriptNdkModeEnabled=null, versionCode=null, versionName=null, applicationId=null, testApplicationId=null, testInstrumentationRunner=null, testInstrumentationRunnerArguments={}, testHandleProfiling=null, testFunctionalTest=null, signingConfig=null, resConfig=null, mBuildConfigFields={isHongkongUser=com.android.builder.internal.ClassFieldImpl@f39f32b9}, mResValues={app_icon_name=com.android.builder.internal.ClassFieldImpl@efa088c6, color_footer=com.android.builder.internal.ClassFieldImpl@391e62f}, mProguardFiles=[], mConsumerProguardFiles=[], mManifestPlaceholders={}, mWearAppUnbundled=null} of type com.android.build.gradle.internal.dsl.ProductFlavor.
1
1
1
Could not find method buildConfigField() for arguments [int, countryCode, 20095] on ProductFlavor_Decorated{name=productB, dimension=null, minSdkVersion=null, targetSdkVersion=null, renderscriptTargetApi=null, renderscriptSupportModeEnabled=null, renderscriptSupportModeBlasEnabled=null, renderscriptNdkModeEnabled=null, versionCode=null, versionName=null, applicationId=null, testApplicationId=null, testInstrumentationRunner=null, testInstrumentationRunnerArguments={}, testHandleProfiling=null, testFunctionalTest=null, signingConfig=null, resConfig=null, mBuildConfigFields={isHongkongUser=com.android.builder.internal.ClassFieldImpl }, mResValues={app_icon_name=com.android.builder.internal.ClassFieldImpl , color_footer=com.android.builder.internal.ClassFieldImpl }, mProguardFiles=[], mConsumerProguardFiles=[], mManifestPlaceholders={}, mWearAppUnbundled=null} of type com.android.build.gradle.internal.dsl.ProductFlavor.
从这些错误信息里面我可以提取出构建时的一些配置信息,我是将它转换为了json格式。
清单文件
注意,清单文件里面设置的 package 和 build.gradle 中设置的 applicationId 的作用是不一样的。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bqt.test">
<!--下面 icon 的值 ${app_logo} 和下面的 meta-data 一样,是在 app 的 build.gradle 中通过 manifestPlaceholders 定义的-->
<!--下面 label 的值 @string/app_icon_name 是在 app 的 build.gradle 中通过 resValue 定义的-->
<application
android:allowBackup="false"
android:icon="${app_logo}"
android:label="@string/app_icon_name"
android:theme="@android:style/Theme.Holo.Light.DarkActionBar">
<!--下面 label 的值 @string/main_activity_label_name 是在各个产品目录(如productA)里面的 res/values/strings.xml 中定义的-->
<activity
android:name=".MainActivity"
android:label="@string/main_activity_label_name"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!--下面这个 Activity 也不是在 main 目录里面定义的,也是各个产品目录(如productA)里面的 java/完整包名/... 中定义的-->
<activity android:name=".SecondActivity"/>
<!--这些设置的值都可以在JAVA代码中通过 ApplicationInfo 获取到-->
<meta-data
android:name="chanel"
android:value="${app_channel_value}"/>
<meta-data
android:name="releaseTime"
android:value="${release_time_value}">
</meta-data>
<meta-data
android:name="baiduMapKey"
android:value="${baidu_map_key_value}">
</meta-data>
</application>
</manifest>
44
44
1
<?xml version="1.0" encoding="utf-8"?>
2
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
package="com.bqt.test">
4
5
<!--下面 icon 的值 ${app_logo} 和下面的 meta-data 一样,是在 app 的 build.gradle 中通过 manifestPlaceholders 定义的-->
6
<!--下面 label 的值 /app_icon_name 是在 app 的 build.gradle 中通过 resValue 定义的-->
7
<application
8
android:allowBackup="false"
9
android:icon="${app_logo}"
10
android:label="@string/app_icon_name"
11
android:theme="@android:style/Theme.Holo.Light.DarkActionBar">
12
13
<!--下面 label 的值 /main_activity_label_name 是在各个产品目录(如productA)里面的 res/values/strings.xml 中定义的-->
14
<activity
15
android:name=".MainActivity"
16
android:label="@string/main_activity_label_name"
17
android:windowSoftInputMode="stateAlwaysHidden|adjustResize">
18
<intent-filter>
19
<action android:name="android.intent.action.MAIN"/>
20
21
<category android:name="android.intent.category.LAUNCHER"/>
22
</intent-filter>
23
</activity>
24
25
<!--下面这个 Activity 也不是在 main 目录里面定义的,也是各个产品目录(如productA)里面的 java/完整包名/... 中定义的-->
26
<activity android:name=".SecondActivity"/>
27
28
<!--这些设置的值都可以在JAVA代码中通过 ApplicationInfo 获取到-->
29
<meta-data
30
android:name="chanel"
31
android:value="${app_channel_value}"/>
32
33
<meta-data
34
android:name="releaseTime"
35
android:value="${release_time_value}">
36
</meta-data>
37
38
<meta-data
39
android:name="baiduMapKey"
40
android:value="${baidu_map_key_value}">
41
</meta-data>
42
</application>
43
44
</manifest>
构建后生成的 generated 文件
生成的目录在【\app\build\generated\res\
resValues\
productA\debug\values\
generated.xml】
里面都是我们通过 resValue 用于替换掉指定的资源文件的配置
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Automatically generated file. DO NOT MODIFY -->
<!-- Values from product flavor: productA -->
<string name="app_icon_name" translatable="false">产品A</string>
<color name="color_footer">#ffff0000</color>
</resources>
7
7
1
<?xml version="1.0" encoding="utf-8"?>
2
<resources>
3
<!-- Automatically generated file. DO NOT MODIFY -->
4
<!-- Values from product flavor: productA -->
5
<string name="app_icon_name" translatable="false">产品A</string>
6
<color name="color_footer">#ffff0000</color>
7
</resources>
构建后生成的 BuildConfig 文件
package com.bqt.test;
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.bqt.test.productA";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "productA";
public static final int VERSION_CODE = 13;
public static final String VERSION_NAME = "master_tag_0718-2-g2a93c3e_2a93c3e";
// Fields from build type: debug
public static final String BASE_URL = "http://120.com/";
// Fields from product flavor: productA
public static final int countryCode = 20094;
public static final boolean isHongkongUser = true;
}
x
15
1
package com.bqt.test;
2
3
public final class BuildConfig {
4
public static final boolean DEBUG = Boolean.parseBoolean("true");
5
public static final String APPLICATION_ID = "com.bqt.test.productA";
6
public static final String BUILD_TYPE = "debug";
7
public static final String FLAVOR = "productA";
8
public static final int VERSION_CODE = 13;
9
public static final String VERSION_NAME = "master_tag_0718-2-g2a93c3e_2a93c3e";
10
// Fields from build type: debug
11
public static final String BASE_URL = "http://120.com/";
12
// Fields from product flavor: productA
13
public static final int countryCode = 20094;
14
public static final boolean isHongkongUser = true;
15
}
在代码中获取配置的值
public class MainActivity extends ListActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle metaData = getMetaData(this);
String[] array = {
"applicationId:" + BuildConfig.APPLICATION_ID + "\nApp的包名:" + getPackageName()
+ "\n类的限定类名:" + getLocalClassName() + " \n类所在的包名:" + getClass().getPackage().getName(),
"Flavor:" + BuildConfig.FLAVOR,
"versionName:" + BuildConfig.VERSION_NAME,
"versionCode:" + BuildConfig.VERSION_CODE,
"buildType:" + BuildConfig.BUILD_TYPE,
"debuggable:" + BuildConfig.DEBUG,
"",
"buildConfigField,是否是香港用户:" + BuildConfig.isHongkongUser,
"buildConfigField,国家代码:" + BuildConfig.countryCode,
"buildConfigField,域名:" + BuildConfig.BASE_URL,
"",
"meta-data,打包时间:" + metaData.getString("releaseTime"),
"meta-data,渠道名称:" + metaData.getString("chanel"),
"meta-data,百度地图密钥:" + metaData.getInt("baiduMapKey"),//注意这里是 int 类型
"",
"resValue,应用名称:" + getResources().getString(R.string.app_icon_name),
"resValue,设置的颜色:" + Integer.toHexString(getResources().getColor(R.color.color_footer)),
"",
"应用SHA1签名:" + getAppSignatureSHA1(this),
};
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<>(Arrays.asList(array))));
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
//注意:这个 SecondActivity 不是在 main 目录里面定义的,也是各个产品目录(如productA)里面的 java/完整包名/... 中定义的
startActivity(new Intent(this, SecondActivity.class));
}
public static Bundle getMetaData(Context mContext) {
try {
ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(), PackageManager.GET_META_DATA);
return applicationInfo.metaData;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return new Bundle();
}
}
public static String getAppSignatureSHA1(Context mContext) {
Signature[] signature = getAppSignature(mContext);
if (signature == null || signature.length <= 0) return "";
String encryptSHA1 = encryptSHA1ToString(signature[0].toByteArray());
return encryptSHA1.replaceAll("(?<=[0-9A-F]{2})[0-9A-F]{2}", ":$0");
}
public static Signature[] getAppSignature(Context mContext) {
try {
@SuppressLint("PackageManagerGetSignatures")
PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), PackageManager.GET_SIGNATURES);
return packageInfo == null ? null : packageInfo.signatures;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return null;
}
}
private static String encryptSHA1ToString(final byte[] data) {
return bytes2HexString(encryptSHA1(data));
}
private static String bytes2HexString(final byte[] bytes) {
if (bytes == null) return "";
int len = bytes.length;
if (len <= 0) return "";
char[] ret = new char[len << 1];
for (int i = 0, j = 0; i < len; i++) {
ret[j++] = HEX_DIGITS[bytes[i] >> 4 & 0x0f];
ret[j++] = HEX_DIGITS[bytes[i] & 0x0f];
}
return new String(ret);
}
private static byte[] encryptSHA1(final byte[] data) {
if (data == null || data.length <= 0) return null;
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(data);
return md.digest();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
private static final char HEX_DIGITS[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
}
96
96
1
public class MainActivity extends ListActivity {
2
3
protected void onCreate(Bundle savedInstanceState) {
4
super.onCreate(savedInstanceState);
5
6
Bundle metaData = getMetaData(this);
7
String[] array = {
8
"applicationId:" + BuildConfig.APPLICATION_ID + "\nApp的包名:" + getPackageName()
9
+ "\n类的限定类名:" + getLocalClassName() + " \n类所在的包名:" + getClass().getPackage().getName(),
10
"Flavor:" + BuildConfig.FLAVOR,
11
"versionName:" + BuildConfig.VERSION_NAME,
12
"versionCode:" + BuildConfig.VERSION_CODE,
13
"buildType:" + BuildConfig.BUILD_TYPE,
14
"debuggable:" + BuildConfig.DEBUG,
15
"",
16
"buildConfigField,是否是香港用户:" + BuildConfig.isHongkongUser,
17
"buildConfigField,国家代码:" + BuildConfig.countryCode,
18
"buildConfigField,域名:" + BuildConfig.BASE_URL,
19
"",
20
"meta-data,打包时间:" + metaData.getString("releaseTime"),
21
"meta-data,渠道名称:" + metaData.getString("chanel"),
22
"meta-data,百度地图密钥:" + metaData.getInt("baiduMapKey"),//注意这里是 int 类型
23
"",
24
"resValue,应用名称:" + getResources().getString(R.string.app_icon_name),
25
"resValue,设置的颜色:" + Integer.toHexString(getResources().getColor(R.color.color_footer)),
26
"",
27
"应用SHA1签名:" + getAppSignatureSHA1(this),
28
};
29
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<>(Arrays.asList(array))));
30
}
31
32
33
protected void onListItemClick(ListView l, View v, int position, long id) {
34
super.onListItemClick(l, v, position, id);
35
//注意:这个 SecondActivity 不是在 main 目录里面定义的,也是各个产品目录(如productA)里面的 java/完整包名/... 中定义的
36
startActivity(new Intent(this, SecondActivity.class));
37
}
38
39
public static Bundle getMetaData(Context mContext) {
40
try {
41
ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(), PackageManager.GET_META_DATA);
42
return applicationInfo.metaData;
43
} catch (PackageManager.NameNotFoundException e) {
44
e.printStackTrace();
45
return new Bundle();
46
}
47
}
48
49
public static String getAppSignatureSHA1(Context mContext) {
50
Signature[] signature = getAppSignature(mContext);
51
if (signature == null || signature.length <= 0) return "";
52
String encryptSHA1 = encryptSHA1ToString(signature[0].toByteArray());
53
return encryptSHA1.replaceAll("(?<=[0-9A-F]{2})[0-9A-F]{2}", ":$0");
54
}
55
56
public static Signature[] getAppSignature(Context mContext) {
57
try {
58
("PackageManagerGetSignatures")
59
PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), PackageManager.GET_SIGNATURES);
60
return packageInfo == null ? null : packageInfo.signatures;
61
} catch (PackageManager.NameNotFoundException e) {
62
e.printStackTrace();
63
return null;
64
}
65
}
66
67
private static String encryptSHA1ToString(final byte[] data) {
68
return bytes2HexString(encryptSHA1(data));
69
}
70
71
private static String bytes2HexString(final byte[] bytes) {
72
if (bytes == null) return "";
73
int len = bytes.length;
74
if (len <= 0) return "";
75
char[] ret = new char[len << 1];
76
for (int i = 0, j = 0; i < len; i++) {
77
ret[j++] = HEX_DIGITS[bytes[i] >> 4 & 0x0f];
78
ret[j++] = HEX_DIGITS[bytes[i] & 0x0f];
79
}
80
return new String(ret);
81
}
82
83
private static byte[] encryptSHA1(final byte[] data) {
84
if (data == null || data.length <= 0) return null;
85
try {
86
MessageDigest md = MessageDigest.getInstance("SHA1");
87
md.update(data);
88
return md.digest();
89
} catch (NoSuchAlgorithmException e) {
90
e.printStackTrace();
91
return null;
92
}
93
}
94
95
private static final char HEX_DIGITS[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
96
}
productA 目录中的部分内容
可以定义任意Java类,包括四大组件,比如Activity:
//注意:这个 SecondActivity 不是在 main 目录里面定义的,也是各个产品目录(如productA)里面的 java/完整包名/... 中定义的
public class SecondActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_a);
}
}
7
7
1
//注意:这个 SecondActivity 不是在 main 目录里面定义的,也是各个产品目录(如productA)里面的 java/完整包名/... 中定义的
2
public class SecondActivity extends Activity {
3
protected void onCreate(Bundle savedInstanceState) {
4
super.onCreate(savedInstanceState);
5
setContentView(R.layout.layout_a);
6
}
7
}
当然也可以定义任意资源,包括
清单文件 AndroidManifest.xml 以及任意 res 目录中的文件,比如 strings.xml:
<resources>
<string name="main_activity_label_name">productA目录中的字符串</string>
</resources>
3
1
<resources>
2
<string name="main_activity_label_name">productA目录中的字符串</string>
3
</resources>
注意,最终 productA 目录中的东西都是要合并到 main 中的。
PS:当前选取的产品的文件夹颜色、图标符号等会与未选取的有差异,并且由于未选中的并不会参与编译,所以 java代码、xml布局文件看起来会有明细的不同,例如:
![795730-20180721235354613-2072190138.png](https://i-blog.csdnimg.cn/blog_migrate/3664dc01dcd284af891902aaae0c16ed.png)
2018-7-18
附件列表