目录
一、前言
二、buildTypes
三、buildType
1、buildTypes存在形式
2、buildTypes 中属性的意义
3、buildTypes 中方法的意义
四、写在最后
一、前言
继 上一篇博客 分享了defaultConfig 中可配置参数的含义,今天我们来分享另一个我们也很熟悉的 buildTypes。
二、buildTypes
buildTypes 也是存在于每个应用级模块中的 android 下的,即如下所示,是每次构建完项目之后自动生成的结构。
android {
buildTypes{
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
buildTypes 可以配置我们需要的构建类型,例如我们常用到的 “测试类型” 和 “本地类型”,则可以使用如下配置
buildTypes{
// 发布类型
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
// 测试类型,给测试人员
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
// 本地类型,和后端联调使用
local {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
增加完这些配置后,我们可以在 android studio 看到多了 “debug” 和 “local” 两个可以构建的类型,在 “点击运行” 时,便会使用我们所选择的构建类型。假设此时选择的是 “debug” 类型,我们此时运行代码,则是 debug 下的配置参数。
当然运行编译成 apk 时,也不例外,各自使用的也是各自类型的配置。
而这里所说的 “release”,“debug”,“local”,三个构建类型其实便是三个 buildType,buildType 所能配置的参数便是我们今天要来捋清楚的。
三、buildType
buildType 官方文档传送门
1、buildType存在形式
从上一篇博客我们知道,每个配置最终会被映射为一个类,或是一个属性、或一个方法。buildType 也不例外,他会被映射为 com.android.build.gradle.internal.dsl.BuildType,继承结构如下
我们重新看一下 DefaultConfig 的继承结构,可以看到都会继承到 BaseConfigImpl 类,说明两者会有一定的交集。这也说明了为什么我们看 gradle 文件时,总感觉一个配置参数哪里都能出现的情况(后面会进行更多的比较,来解除我们这种疑惑)
2、buildType 中属性的意义
我们先来一个约定,避免使用的代码过于冗长。
buildTypes{
debug {
// 我们下面的 “使用方法” 代码都是基于这一块,除非特殊说明。
}
}
2.1 applicationIdSuffix
- 类型:String
- 描述:会追加在 applicationId 字符串的后面,形成最终的包名。
- 值得一提:在 defaultConfig 中也有这个属性,但一般不会使用。而会在 buildTypes 中使用,这样可以让包名不同,同时安装多个类型的应用。例如我们上面所提到的 release包、debug包、local包,都可以同时存在而不会覆盖,方便调试。
- 使用方法:
debug {
applicationIdSuffix '.debug'
}
2.2 consumerProguardFiles
- 类型:List< File >
- 描述:这个属性只作用于我们创建的
library中,包括我们以aar形式导入的 library ,或是直接创建的 library。它的作用是,负责该 library 被进行编译时的混淆规则,我们在 主App 的模块下则可以不用再管理各个 library 的混淆规则,会直接使用各个 library 的混淆规则文件。 - 值得一提:这个属性 和 proguardFiles 的区别在于,consumerProguardFiles 会被 主App模块 作为混淆文件使用导入,而 proguardFiles 则不会。
- 使用方法:
debug {
consumerProguardFiles 'consumer-rules.pro'
......省略其他配置
}
// 因为该属性是一个 List<File> 类型,如果需要多个文件配置,则如下所示
debug {
consumerProguardFiles 'consumer-rules.pro','zincPower-rules.pro'
......省略其他配置
}
2.3 crunchPngs
- 类型:boolean
- 描述:是否对 PNG 图片进行压缩处理。设置为true时,会对未进行最佳压缩的PNG资源进行压缩,但也会增加构建时间。
- 值得一提:默认情况下,release 类型是开启的,debug 类型则为关闭。
- 使用方法:
debug {
crunchPngs false
......省略其他配置
}
2.4 debuggable
- 类型:boolean
- 描述:是否可以对应用进行调试。
- 值得一提:release 的构建类型默认为不可调试,即为false。而 debug 默认为true,既可以调试。所谓的 无法调试 即下图的 “虫子” 标记无法让应用启动,而正常的运行是可以的。
- 使用方法:
debug {
debuggable true
}
2.5 javaCompileOptions
- 类型:JavaCompileOptions
- 描述:配置编译时 java 的一些参数,例如我们使用
annotationProcessor时所需要的参数。 - 使用方法:
debug {
javaCompileOptions {
annotationProcessorOptions{
arguments = []
classNames ''
....
}
}
......省略其他配置
}
JavaCompileOptions 可以配置的具体参数,请进传送门
2.6 jniDebuggable
- 类型:boolean
- 描述:是否可以对应用的 native 代码进行调试
- 值得一提:release 的构建类型默认为不可调试 native 代码,即为false。而 debug 默认为true,既可以调试 native 代码。无法对 native 代码调试,指的是即使在native方法打了断点,代码也不会被 “停住”。
- 使用方法:
debug {
jniDebuggable true
}
2.7 manifestPlaceholders
- 类型:Map<String, Object>
- 描述:配置可以在
AndroidManifest.xml中使用的参数。 - 使用方法: 这里想配置我们应用的 logo 为测试版本的logo,方便测试人员区分不同类型的包,则可以在 gradle 中使用下面这段
debug {
manifestPlaceholders = [APP_LOGO_ICON: "@mipmap/ic_logo"]
}
然后在 AndroidManifest.xml 中使用,使用 ${你配置的变量名}
// 在 application 中使用替换,还需要多添加 tools:replace 这一标签,将我们需要替换的名称写上,例如这里的 android:icon
<application
android:allowBackup="true"
android:icon="${APP_LOGO_ICON}"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:icon">
......
2.8 minifyEnabled
- 类型:boolean
- 描述:是否开启混淆,代码优化。true开始,false则关闭。
- 使用方法:
debug {
minifyEnabled true
}
2.9 proguardFiles
- 类型:List< File >
- 描述:配置混淆规则文件,只有
minifyEnabled设置为 true 的时候会使用这个参数,文件中需要申明哪些文件不被优化和混淆。 - 值得一提:因为被混淆后端代码,类名和方法名都会有所变化,所以进行反射会失败,这是我们就需要进行申明他们不被混淆。(这里只是举了使用这个参数的一个场景,如果应用本来是正常的,开了混淆后,出现了莫名奇妙的bug,那就思考下是否因为混淆导致了这一bug)
- 使用方法:
debug {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
2.10 multiDexEnabled
64K 引用限制问题官方文档 传送门
- 类型:Boolean
- 描述:是否开启分包。因为安卓中方法索引值为两个字节,四位十六进制的一个数值,即[0, 0xffff],所以最大方法数为65536个。一旦超出了,就需要进行分包,所以我们就需要开启这个参数。
- 使用方法:
android{
buildTypes {
debug {
multiDexEnabled true
...
}
}
}
// 添加依赖
dependencies {
// 如果使用的为 AndroidX,则使用下面这个导入
// implementation 'androidx.multidex:multidex:2.0.1'
// 如果不使用 AndroidX,则使用下面这段
compile 'com.android.support:multidex:1.0.3'
}
有两种开启 MultiDex 方法:
// 第一种:让你应用的 Application 继承 MultiDexApplication。
public class MyApplication extends MultiDexApplication {
}
// 第二种:重写应用的 Application 方法 attachBaseContext
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
最后别忘了在 AndroidManifest.xml 中使用我们在上面的 Application。
2.11 multiDexKeepFile
- 类型:File
- 描述:将我们需要的类打包进主包,即
classs.dex。我们在第 2.10 小点,分享到使用了多包处理,有时我们需要将一些主要的类打包进主包,则可以使用该属性。 - 使用方法:
debug {
multiDexKeepFile file('multidex-config.txt')
...
}
multidex-config.txt 中的书写则如下,每一个文件则为一行
com/example/MyClass.class
com/example/TestClass.class
2.12 multiDexKeepProguard
- 类型:File
- 描述:将我们需要的类打包进主包,和第 2.11 点的功能相同,区别在于写法。
- 使用方法:
debug {
multiDexKeepFile file('multidex-config.pro')
...
}
multidex-config.pro 中的写法如下
// 将会保留所有的在com.example package的类
-keep class com.example.** { *; }
2.13 name
- 类型:String
- 描述:构建类型的名字。
- 使用方法:
// debug 则会被赋值至 name 属性,而且 name 是final 类型,无法在改动。
debug{
...
}
2.14 pseudoLocalesEnabled
- 类型:boolean
- 描述:这个属性用于开启伪语言,用于发现UI中潜在的可本地化问题。
- 使用方法:
第一步:我们在配置中开启该属性
debug{
pseudoLocalesEnabled true
}
第二步:安装我们的应用 第三步:打开设置 -> 系统 -> 语言和输入法 -> 语言 -> 添加语言
会在 “添加语言” 页面中看到如下图的选项,选择图中红色框的内容,各自的不同在图中也有标明,我们往下走看看具体不同的表现是什么。
小盆友的测试机是 荣耀8,不同机子会有些许不同
选择之后,选择不同的语言会有不同效果,如图所示
(1)左边为未开启时的样子;
(2)中间为选择了Pseudo locale(相当于en-XA);
(3)右边为选择了Bidirection test locale(相当于en-XB);
2.15 renderscriptDebuggable
- 类型:boolean
- 描述:是否可以对 renderscript 代码进行调试
- 使用方法:
debug {
renderscriptDebuggable true
}
2.16 renderscriptOptimLevel
- 类型:int
- 描述:设置渲染脚本等级
- 使用方法:
debug {
renderscriptOptimLevel 3
}
2.17 shrinkResources
shrinkResources 官方使用手册 传送门
- 类型:boolean
- 描述:是否压缩资源,如果开启,gradle在编译时帮我们把没有使用的资源给移除。
- 值得一提:shrinkResources 的开启需要 minifyEnabled 也为开启状态,否则无法运行。
- 使用方法:
debug {
shrinkResources false
}
开启后编译完会看到如下日志
2.18 signingConfig
- 类型:SigningConfig
SigningConfig 的可配置参数 传送门
- 描述:配置签名配置。apk包能被安装是需要被签名的,我们直接运行的时候,是使用了系统默认的签名证书,当我们要发布release包时,则需要使用属于个人或企业的签名。
- 使用方法:
buildTypes {
release {
signingConfig {
// 不建议将签名证书的信息写在这里,而应该是写在 properties 文件中,将其引入使用
config {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
...
}
}
2.19 testCoverageEnabled
- 类型:boolean
- 描述:测试覆盖率,可以获取测试覆盖率的报告
- 值得一提:记得将 minifyEnabled 置为 false,否则报告也会是以混淆后的类名出现。
- 使用方法:
第一步:在 build.gradle 中开启 testCoverageEnabled
debug {
testCoverageEnabled true
}
第二步:连接上一台可用设备,因为我们执行的测试代码是要基于设备的,如果不连接,测试阶段会报如下错
第三步:在 android studio 的 终端(即 Terminal)中输入下面这行命令,查看所有可运行任务。
小盆友是 mac 环境,所以 ./gradlew 开头。windows 的环境则直接使用 gradlew 即可。
./gradlew app:tasks
输入运行后,可以看到图中高亮部分的可运行task。
我们以驼峰式(即第一个字母加上后面每个单词的首字母且以大写的形式)将其运行,代码如下
// create + 你的构建类型名(这里为debug) + CoverReport
./gradlew app:cDCR
运行完之后,就是查看测试报告的阶段了。我们进入coverage目录,具体路径如下图所示。
最后用浏览器打开 index.html 文件,就可以看报告啦,如下图所示,因为小盆友的这个项目没有写测试代码,所以覆盖率为0。
2.20 useProguard
- 类型:boolean
- 描述:是否开启总是开启 proguard
- 使用方法:
debug {
useProguard true
}
2.21 versionNameSuffix
- 类型:String
- 描述:追加在 versionName 之后
- 使用方法:
debug {
// 如果 versionName "1.0.0" ,则最终的版本名为 1.0.0.test
versionNameSuffix ".test"
.....
}
2.22 zipAlignEnabled
- 类型:boolean
- 描述:是否开启zipAlign。会对应用程序进行字节对齐,对齐后会减少了运行应用程序时消耗的内存。
- 使用方法:
debug {
zipAlignEnabled true
.....
}
2.23 matchingFallbacks
- 类型:List< String >
- 描述:用于处理 本地依赖library 间 BuildType 的冲突。
matchingFallbacks 还可以处理 维度风味的 问题,但我们这里先不讨论,后续的文章会介绍。
- 使用方法:
一个东西的出现是为了处理至少一个问题,所以我们需要先了解下这个问题是怎么产生的。
如上图所示,我们的 主Module(一般是App),存在了三个构建类型,即我们一开始所提的 “release”、“debug”、“local” 三种。
此时我们在项目添加了一个 module 名字为 library,并依赖进我们的app中。当我们构建 “release”、“debug” 两种版本时,都不会有任何问题,因为 library 默认也提供了 “release”、“debug” 两种构建版本。
但是当我们使用 “local” 时,便会出问题了,因为此时 gradle 的脚本也会默认选择 “library” 的 “local”,但 “library” 中并没有。会报下面这样的错误
ERROR: Unable to resolve dependency for ':app@local/compileClasspath': Could not resolve project :lib:library.
Show Details
Affected Modules: app
此时就需要帮 “local” 从 “library” 中选择一个可用的构建类型,则是通过 matchingFallbacks 进行设置。
铺垫了这么多,接下来就是怎么使用了,在 app 的 build.gradle 中添加如下代码即可。
local{
matchingFallbacks = ['zinc','release']
...
}
值得一提的是,我们可以配置多个,gradle在编译的时候,会按照从头开始匹配的原则,例如这里的 “zinc” 会匹配不到,则匹配 “release”,因为 “release” 匹配到了,则会进行使用,中断后面的匹配。
3、buildTypes 中方法的意义
3.1 buildConfigField(type, name, value)
- 描述:我们可以在 BuildConfig 类中添加值,最终会在 BuildConfig 中添加如下一行代码。
// 值的注意的是 value 的值是原样放置,我们通过使用方法一节来了解
<type> <name> = <value>
- 使用方法:
debug {
// 可以通过 BuildConfig 进行获取
buildConfigField('String', 'name', '"zinc"')
buildConfigField('int', 'age', '26')
.....
}
最终会生成如下图的配置,我们可以通过下面代码进行获取
String name = BuildConfig.name;
int age = BuildConfig.age;
值的一提的是,我们设置 String 类型的参数时,需要加上 "" 双引号(如例子中的name属性)。切记!
3.2 consumerProguardFile(proguardFile)
- 描述:和上面分享的 2.2 小点的属性 consumerProguardFiles 是一样的作用。只是这里只能设置一个 混淆文件。
- 使用方法:
debug {
consumerProguardFile('consumer-rules.pro')
}
3.3 consumerProguardFiles(proguardFiles)
- 描述:和上面分享的 2.2 小点的属性 consumerProguardFiles 是一样的作用,而且也是多个混淆文件。
- 使用方法:
debug {
consumerProguardFile('consumer-rules.pro', 'zincPower-rules.pro',.....)
}
3.4 externalNativeBuild(action)
- 类型:ExternalNativeBuildOptions
- 描述:这里我们设置 ndk 编译过程的一些参数。分为 cmake 和 ndkBuild 两个参数。
- 使用方法:
debug {
externalNativeBuild {
ndkBuild {
// Passes an optional argument to ndk-build.
arguments "NDK_MODULE_PATH+=../../third_party/modules"
}
// For ndk-build, instead use the ndkBuild block.
cmake {
// Passes optional arguments to CMake.
arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
// Sets a flag to enable format macro constants for the C compiler.
cFlags "-D__STDC_FORMAT_MACROS"
// Sets optional flags for the C++ compiler.
cppFlags "-fexceptions", "-frtti"
// Specifies the library and executable targets from your CMake project
// that Gradle should build.
targets "libexample-one", "my-executible-demo"
}
}
}
3.5 initWith(that)
- 描述:会拷贝给定的 buildType(即参数的值)
- 使用方法:
buildTypes {
debug{
}
local{
// 会拷贝 debug 的配置
initWith debug{
// 在这里进行我们自己的配置
}
}
}
3.6 proguardFile(proguardFile)
- 描述:添加混淆文件,和 2.9小点 的功能一致,只是传入的是一个文件,这里就不再赘述
- 使用方法:
debug {
proguardFile 'proguard-rules.pro'
}
3.7 proguardFiles(files)
- 描述:添加混淆文件,和 2.9小点 的功能一致,这里就不再赘述
- 使用方法:
debug {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
3.8 setProguardFiles(proguardFileIterable)
- 描述:添加混淆文件,和 2.9小点 的功能一致,只是写法稍微不同,这里就不再赘述
- 使用方法:
debug {
proguardFiles = [getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro']
}
3.9 resValue(type, name, value)
- 描述:添加 value 资源
- 使用用法:
debug {
// 添加至 res/value,通过 R.string.age 获取
resValue('string', 'age', '12 years old')
}
四、写在最后
Gradle 的配置文件看起来好像挺乱,其实是因为我们没有进行整体的梳理。很多配置其实不是没有用,而是我们没有对他有一个整体的了解,正所谓 “无知最可怕”。
如果喜欢的话请给我一个赞,并关注我吧。文章中如有写的不妥的地方,请评论区与我讨论吧,共同进步。
项目地址:传送门