背景
在打APK包时,通常会根据上线的应用市场或渠道的区别,打不同的APP包。他们的主要代码逻辑都是一样的,只是有一些细微差别。(如包名不同、部分图片资源不同等)。
我们有些测试代码逻辑,通常我们期望只是buildType为debug的时候,才将这部分文件打包到APK中,线上环境不包含测试代码和资源。
想要实现以上目的, 就需要用的变体、产品变种等Gradle配置。
Gradle生命周期
Gradle 的生命周期只有三个阶段,分别是 Initialization (初始化)
、Configuration (配置)
和 Execution (执行)
,如下图所示:
- 在 初始化阶段(Initialization),主要是解析
settings.gradle(.kts)
文件,并创建Settings
实例。在解析settings.gradle(.kts)
文件时,Gradle 根据该文件来决定构建应该包含哪些项目,并为需要构建的项目创建Project
实例。 - 配置阶段(Configuration): 则会解析参与构建项目的
build.gradle(.kts)
文件,并根据 build.gradle 文件来创建构建需要的 Task 有向无环图(DAG) - 执行阶段(Execution) 则会执行在配置阶段构建好的任务图
构建术语
Build types(构建类型)
构建类型定义 Gradle 在构建和打包应用时使用的某些属性。通常针对开发生命周期的不同阶段进行配置。
例如,debug构建类型会启用调试选项,并会使用debug密钥为应用签名;而Release 构建类型则可能会缩减应用大小(配置项minifyEnabled
、shrinkResources
)、对应用进行混淆处理(配置项proguardFiles
),并使用Release密钥为应用签名。
配置了Build Type后,Gradle也会默认指定一个同名的目录在项目里面,但是不会把目录自动创建出来,我们可以手动创建一个同名的目录。
build Type示例
...
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles 'proguard_android.txt',
//noinspection GroovyAssignabilityCheck
debuggable false
multiDexEnabled true
}
debug {
//noinspection GroovyAssignabilityCheck
signingConfig signingConfigs.debugConfig
multiDexEnabled true
}
}
...
Product flavors(产品变种)
产品变种可以向用户发布的不同应用版本,如应用的免费版和付费版。可以自定义产品变种以使用不同的代码和资源,同时共享并重用所有应用版本共用的部分。产品变种是可选的,必须手动创建。
Product Flavor示例
android {
...
flavorDimensions "default"
productFlavors {
free {
applicationId 'xxx'
versionCode 'yyy'
minSdkVersion 26
dimension "default"
},
paid {
applicationId 'aaa'
versionCode 'bbb'
minSdkVersion 26
dimension "default"
}
}
...
}
Build variants(变体)
变体是 build type与product flavor的交叉产物,也是 Gradle 用来构建应用的配置。利用变体,可以在开发期间构建产品变种的调试版本,以及构建产品变种的已签名发布版本以供发布。虽然无法直接配置变体,但可以配置构成变体的build type和product flavor组成变体。创建额外的 build 类型或产品变种也会产生额外的 build 变体。
Manifest entries
可以在变体配置中为mainifest文件的某些属性指定值。这些 build 配置值会覆盖清单文件中的现有值。如果需要为应用生成多个变体,并且让每一个变体都具有不同的应用名称、最低 SDK 版本或目标 SDK 版本,便可运用这一技巧。当存在多个清单时,清单合并工具会合并清单设置。
Manifest文件合并优先级
合并工具会根据每个清单文件的优先级按顺序合并,将所有清单文件组合到一个文件中。例如,如果有三个清单文件,则会先将优先级最低的清单合并到优先级第二高的清单中,然后再将合并后的清单合并到优先级最高的清单中。
有三种基本的清单文件可以互相合并,它们的合并优先级如下(按优先级由高到低的顺序)
构建变体(Build variants)的清单文件
如果变体有多个源代码集,则其清单优先级如下:
构建变体(Build variants)清单(如
src/demoDebug/
)构建类型(Build types)清单(如
src/debug/
)产品变种(Product Flavor)清单(如
src/demo/
)如果使用的是变种维度,则清单优先级与每个维度在
flavorDimensions
属性中的列示顺序(按优先级由高到低的顺序)对应。
应用模块的主清单文件
所包含的库中的清单文件
如果您有多个库,则其清单优先级与依赖顺序(库出现在 Gradle
dependencies
代码块中的顺序)一致。
总结:变体的Manifest(变体Manifest > 构建类型Manifest > 产品变种Manifest) > 工程的Manifest > 组件库的Manifest
Signing
构建系统既允许在 build 配置中指定签名设置,也可以在构建流程中自动为应用签名。构建系统通过已知凭据使用默认密钥和证书为Debug版本签名,以避免在构建时提示输入密码。
Code and resource shrinking
可以为不同的变体指定混淆规则,当编译APP的时候,构建系统会使用一组合适的规则以使用其内置的缩减工具(如 R8)缩减您的代码和资源。 缩减代码和资源有助于缩减 APK 或 AAB 大小
Build配置文件
创建Android新项目时,Android Studio 会自动创建其中的部分文件(如下图所示),并为其填充默认值。
Android项目里有些默认的Build配置文件,在构建前,需要了解其作用和用法。
The Gradle Wrapper file
Gradle wrapper (gradlew
) 是一个包含在源代码中的小型应用,用于下载和启动 Gradle 本身。这可以创建更一致的构建执行。开发者下载应用源代码并运行 gradlew
。这将下载所需的 Gradle 发行版,并启动 Gradle 以构建应用。
gradle/wrapper/gradle-wrapper.properties
文件包含一个属性 distributionUrl
,该属性描述了用于运行 build 的 Gradle 版本。
gradle wrapper示例
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Gradle settings file
settings.gradle.kts
文件(对应 Kotlin DSL)或 settings.gradle
文件(对应 Groovy DSL)位于项目的根目录下。此设置文件会定义项目级代码库设置,并告知 Gradle 在构建应用时应将哪些模块包含在内。多模块项目需要指定应包含在最终 build 中的每个模块。
Source sets
这个对我们打包构建非常有帮助,尤其是我们只想针对某个build type添加一些代码逻辑,打该build type的APP时包含对应代码,其他build type不含该代码。
Android studio是以source set来组织源代码和资源的。当你创建一个新module,Android studio会自动在该module内创建一个名为main/
的source set。一个module中的main/
目录下所包含的源代码和资源,在编译打包时会被所有的变体(build variant)所使用。即无论打包选择什么变体,main/
目录下文件都会参与打包。
除了必选的main/
这个source set,其他的source set是可选的,而且在配置新的变体的时候Android studio不会自动创建这些source set。当然,就像main/
一样,可以手动创建其他source set,Gradle构建某个版本(这里版本指不同变体、构建类型和产品变种)的APP时,会用到对应的source set所包含的源代码和资源。
source set分类
src/main/
:无论构建哪个变体,该目录下的源代码和资源都会被使用到。src/buildType/
:只会在构建对应build type的APP时,会使用这个目录下的代码和资源src/productFlavor/
:只会在构建对应product flavor的APP时,会使用这个目录下的代码和资源src/productFlavorBuildType
/:只会在构建对应变体的APP时,会使用这个目录下的代码和资源
例如,如果要构建fullDebug
(有2个build type,分别为release和debug;2个product flavor,分别为full和free)变体的APP,构建系统将会合并下列目录里的源代码、配置和资源:
src/fullDebug/
(the build variant source set)src/debug/
(the build type source set)src/full/
(the product flavor source set)src/main/
(the main source set)
构建变体的时候,会用到变体本身对应的目录,build type对应的目录,product flavor对应的目录,以及无论什么变体都会使用的main目录。
合并优先级
上文说明了构建变体时参与的目录,这么多目录中可能包含相同的文件,需要规定好合并时的优先级,才能最终确定构建的APP中使用的是哪个目录的文件。
源代码合并
kotlin/
或java/
目录中的所有源代码将一起编译以生成单个输出。对于给定的 build 变体,如果 Gradle 遇到两个或更多个源代码集目录定义了同一个 Kotlin 或 Java 类的情况,就会抛出构建错误(duplicate class)。所以参与构建的所有source set中源代码文件不允许重复,否则会报错。Manifest文件
所有的Manifest文件最终会合并成一个Manifest文件。优先级是:
变体的Manifest(变体Manifest > 构建类型Manifest > 产品变种Manifest) > 工程的Manifest > 组件库的Manifest
values/
下的资源文件同名资源文件合并时
合并优先级:build variant > build type > product flavor > main
res/
和asset/
下的资源文件同名资源文件合并时
合并优先级:build variant > build type > product flavor > main