什么是 Gradle
Gradle 是一个高级构建工具。
图片来自于 Gradle 官网,根据官网介绍,上图从左往右,依次体现了:Build Anything >> Automate Everything >> Deliver Faster. 据此,我们可以看到它的主要特点:可以构建的种类多,自动化构建过程,快速交付。
Android Studio 就是使用了这种工具,来完成 APK 的打包过程。如上面所说,这个过程原本是自动化完成的。自动化的东西都是流水线,我们总会有一些自己的需求,这时,就需要自定义配置这个过程。
下面,主要讨论下构建变体的使用。
构建变体的需求
先来说一下这种需求。
通常,在开发环境中,我们会使用 debug 版本的应用;在发布是时候使用 release 版本。
在 debug 版本中个,我们会有显示日志的需求;在 release 版本中是不需要的,日志是为了方便我们调试,而且可能包含一些敏感数据。
在 release 版本中,通常我们需要进行代码混淆,否则,很容易被别人反编译,就像是一个人没有穿衣服,显然,debug 版本就不需要。
从应用功能上来讲,同一个 APP 在发布的时候,有时候会根据需要发布不同的版本,比如在不同的应用市场上会有一些定制,即使是在同一个市场上,可能也要提供付费版和免费版等等。
为了解决上述问题,不管是从构建方式的角度,还是应用功能差异性的角度构建 apk 包,我们都需要对这些版本进行管理。Gradle 就提供了这样一个途径,使得我们可以通过修改 构建配置(文件) 的方式以满足特定版本的需求,然后让 Gradle 根据我们的选择自动构建我们想要的应用。
当然,处理问题的方式不止一种,我们也会有很多替代方案。但是,明明离终点只差一步,为什么还要翻过一座大山呢?
什么是构建变体
谈完需求,下面我们来讲讲什么是构建变体,它是通过 BuildType 和 BuildFlavor 组合实现的。如下所示,共产生 6 个变体。
所谓 BuildType 和 BuildFlavor,即构建类型和构建特征(或者叫产品风味)。
BuildType,构建类型,主要针对开发生命周期的不同阶段进行配置。一个模块或者项目,默认有两种类型,release 和 debug。 debug 类型下 debuggable 属性是 true,从而使得我们可以打断点进行调试。debug 类型在打包的时候,会使用默认的自动生成的签名,对于 release 类型来说,发布的时候需要使用我们自己的密钥进行签名。同时,我们还可以在发布的时候,进行代码混淆。
signingConfigs {
release {
storeFile file("xxxx.jks")
storePassword "xxxx"
keyAlias "xxxx"
keyPassword "xxxx"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix ".debug"
}
//这是自定义的 buildType,继承自 debug
/*jnidebug {
initWith debug
applicationIdSuffix ".jnidebug"
jniDebuggable true
}*/
}
BuildFlavor,构建特征,主要是用以发布给用户不同的应用版本。需要注意的是,这里的版本并非是版本号,而是功能。比如,我们上说所说的免费版、付费版。
flavorDimensions "default"
productFlavors {
free {
dimension "default"
applicationIdSuffix ".free"
versionNameSuffix "-free"
buildConfigField "String", "NAME", "\"免费版\""
}
paid {
dimension "default"
applicationIdSuffix ".paid"
versionNameSuffix "-paid"
buildConfigField "String", "NAME", "\"付费版\""
}
cmpy {
dimension "default"
applicationIdSuffix ".cmpy"
versionNameSuffix "-cmpy"
buildConfigField "String", "NAME", "\"内部使用\""
}
}
如何使用构建变体
上一节,说了什么是构建变体,但是,我们该怎么使用以满足我们的需求呢?有两个地方,给我们带来了可能性:BuildConfig 和 SourceSet(源集)。
BuildConfig
也许你已经注意到了上面 productFlavor 中的
buildConfigField "String", "NAME", "\"免费版\""
事实上,对于每一种变体,都会有一个 BuildConfig 与之一一对应。
我们来看看构建变体 free.debug 的BuildConfig:
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.ygs.test.free.debug";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "free";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0-free";
// Fields from product flavor: free
public static final String NAME = "免费版";
}
这些字段都是静态常量,在项目中,我们都可以通过 BuildConfig 直接访问。比如,你可以通过 BuildConfig.DEBUG 判断当前版本是否是 debug 版本;可以通过 BuildConfig.FLAVOR 判断当前的 productFlavor,已决定是否启用某个功能。当然,我们也可以自定义字段,比如:
buildConfigField "String", "NAME", "\"免费版\""
上述这些字段,无论是自定义的,还是默认的,都可以成为我们在项目中的判断条件。
SourceSet
所以源集,即代码和资源的分组。这可能有点抽象,举个栗子,src/main 就是一个源集。
每个 buildType 可以有一个源集,每个 buildFlavor 可以有一个源集,每个 buildVariant 同样可以有一个源集。
其中,src/main 这个源集是所有源集所共有的,除此之外,源集之间互斥。
如上图所示,一共有三个源集:freeDebug、freeRelease、main。
注意,freeDebug、freeRelease 实际上是使用 buildVariant 的构建的源集,需要和构建变体的名字一样(当然可以通过配置修改这种默认行为)。
这里有几点注意事项:
- 对于 java 文件来讲,freeDebug 中不能出现和 main 中一样在同一个包下类名相同的 java 文件,因为他们之间是共享关系,相当于将 main 中的所有 java 和 freeDebug 合并在一起。如果出现两个相同名字的 java 文件,会报错。freeDebug 和 freeRelease 不同,它们所有的资源都是互斥的,相互不影响,因为我们在构建的时候,也只能选择其中一种进行构建。假设我们选择了 freeDebug,那么 freeRelease 就会被剔除在外。
- 对于 res 文件夹下的这些资源文件,freeDebug 和 freeRelease 这些源集之间同样是互斥的,相互没有任何影响。但是对于不同源集和 main之间,却存在着资源合并或替换的情况。
对于 drawable 及其相关文件夹下的图片而言,freeDebug 中的图片会直接覆盖 main 中的同名图片。对于 layout 文件夹下的布局文件也是这样。
但是,对于 values 文件夹下的 xml 文件来说,确是文件内容的合并。比如在源集 main 中的 strings.xml 中
<resources>
<string name="app_name">App</string>
<string name="hello_world">Hello world!</string>
</resources>
在源集 freeDebug 中的 strings.xml 中
<resources>
<string name="app_name">FreeDebug</string>
</resources>
它们合并之后就是
<resources>
<string name="app_name">FreeDebug</string>
<string name="hello_world">Hello world!</string>
</resources>
最后,非常重要的一点,合并或者替换时会遵照优先级:
buildVariant > buildType > buildFlavor> main > 库依赖项
总结
通过 Gradle 创建 构建变体,等于说给了我们告知 Gradle 选择哪些 class 哪些资源编译打包成 apk 的权利,使我们的版本管理更加灵活,方便。