目录
一、前言
二、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 的配置文件看起来好像挺乱,其实是因为我们没有进行整体的梳理。很多配置其实不是没有用,而是我们没有对他有一个整体的了解,正所谓 “无知最可怕”。
如果喜欢的话请给我一个赞,并关注我吧。文章中如有写的不妥的地方,请评论区与我讨论吧,共同进步。
项目地址:传送门