使用 Gradle 配置 Android 工程

自从将 Android 开发工具从 eclipse 迁移到 AndroidStudio 中之后,Android 工程构建越来越方便。见过很多项目中各式各样的 Gradle 配置语法,懵懵懂懂。遂花费近两周时间将官方文档通读一遍并整理出常用 Gradle 配置 Demo,以此博客作为说明。

1. 准备工作

一个最新版本的 AndroidStudio 毋庸置疑。下载地址:请戳。此处使用 3.5 版本。

Gradle 版本 5.4.1 ,无须单独下载,在工程中配置好即可。

2. 基本概念

2.1. Module

软件工程中,通常采用模块化开发的策略,将一个项目划分成若干部分,多人协作开发中,每人完成一部分,最终完成整个项目。因此 AndroidStudio 也采用模块化管理的方式编译 Android 工程 。各个模块之间的依赖关系类似于树形结构,主 Module 作为根节点,其余多个库 Module 都直接或简介的被主 Module 引用。

2.2. Project

基于前面的描述,Project 即 AndroidStudio 打开的一个目录,其中包含 N+1 个 Module ,通过 Gradle 构建多个 Module 并最终合并成一个 apk 文件。

其实,在一个 Project 目录中,可以有多个主 Module ,每个主 Module 可以依赖相同的库 Module 并负责生成一个 apk 文件。通常不建议这么使用,因为这样会破坏 Module 之间的树形依赖关系,不利于后期维护。推荐将复用频率较高的库 Module 封装成 aar 文件,通过直接依赖或远程依赖的方式用于不同的主 Module 之中。

2.3. buildType & productFlavor

通过直译的方式 ,可以理解成 构建类型产品特性 。构建类型中,默认包含 debug 和 release 两种类型。一些基本的差异如 :debug 包允许调试,release 包不允许调试。产品特性可以根据实际需要自行定义,如:收费和免费版本,汉语、英语及日语。不同的特性之间通过维度的概念区分。

最终如上图所示,生成 2x3x2 共 12 种不同的 apk 文件。

3. 工程结构

精简后的 AndroidStudio 工程结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
├── app
│   ├── build.gradle
│   ├── proguard-project.txt
│   └── src
│   └── main
│   ├── AndroidManifest.xml
│   ├── java
│   │   └── com
│   │   └── flueky
│   │   └── demo
│   │   └── MainActivity.java
│   └── res
│   ├── layout
│   │   └── activity_main.xml
│   └── values
│   ├── strings.xml
│   └── styles.xml
├── build.gradle
├── gradle
│   └── wrapper
│   ├── gradle-wrapper.jar
│   └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
  1. 文件夹 app 作为一个 Module ,必须有 build.gradle 文件,src 文件夹包含全部源文件:Java 文件、资源文件、清单文件等。
  2. 文件 build.gradle 不同于 app/build.gradle , 它用于整个工程的配置,而 app/build.gradle 只作用于 app Module 的配置。
  3. gradle 包含使用的 gradle 插件版本 。若未指定 ,AndroidStudio 会自动生成最新的 gradle 插件目录。
  4. gradlew、gradlew.bat 分别是 Mac/Linux 和 Windows 的脚本文件。关联于 gradle 插件版本,不可单独删除。
  5. settings.gradle 包含整个工程的所有 Module 的声明。

settings.gradle 示例:

1
2
3
4
5
6
7
8
// 主Module
include ':app'
// 库Module
include ':library'

// 添加外部库
include 'other-lib'
project(':other-lib').projectDir = new File(rootDir, "../other-sample/library")

4. 配置 Project

4.1. 构建脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
buildscript {
repositories {
// 仓库地址
google()
jcenter()
mavenCentral()
}
// 工程的依赖配置
dependencies {
// 必须包含的 gradle 版本的构建工具。
classpath 'com.android.tools.build:gradle:3.4.1'
}
}

4.2. 全工程配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 全部工程配置,作用于每个 Module
allprojects {
repositories {
// 仓库地址
google()
jcenter()
mavenCentral()

// 依赖仓库,添加用于查找依赖项的目录,可以多个。
flatDir {
// jar、aar 存放的目录
dirs 'libs'
}
}
}

4.3. 扩展变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 扩展变量,用于统一管理每个 Module 的有关配置
ext {
// 应用 id
applicationId = "com.flueky.demo"
// 统一控制各个 Module 使用的 SDK 版本
compileSdkVersion = 29
buildToolsVersion = "29.0.0"
minSdkVersion = 19
targetSdkVersion = 29

// 定义 Module 的版本号
app = [
versionCode: 1,
versionName: '1.0.0'
]
}

4.4. 加载配置文件

1
2
// 加载其他的 gradle 配置文件,可选
apply from: "config.gradle"

5. 配置 Module

5.1. 加载插件

1
2
3
4
5
6
// 声明 Module 是主 Module,生成 apk 文件
apply plugin: 'com.android.application'
// 声明 Module 是库 Module,生成 aar 文件
apply plugin: 'com.android.library'
// 声明 Module 是 Java 库,生成 jar 文件
apply plugin: 'java-library'

还有很多 Plugin 可以用,如: java web maven-publish 等。

另一种写法,一个 Module 中使用多个插件,如:

1
2
3
4
5
plugins {
id 'java'
id 'war'
id 'maven-publish'
}

5.2. 使用SDK

1
2
3
4
5
6
android {
// 定义编译的 sdk 版本,只有使用最新版本的sdk,才能在代码中只用最新的 api 方法
compileSdkVersion rootProject.ext.compileSdkVersion
// 定义构建工具的版本
buildToolsVersion rootProject.ext.buildToolsVersion
}

rootProject.ext 是在 Project 的 build.gradle 文件中声明。

5.3. 默认配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
android{
defaultConfig {
// 应用 id
applicationId rootProject.ext.applicationId
// 最小 sdk 版本,低于此 Android 版本的手机不能安装
minSdkVersion rootProject.ext.minSdkVersion
// 目标 sdk 版本,低于此 Android 版本的手机完美兼容,高于此 Android 版本的手机,部分特性不能使用
// 升级 target 需要针对高版本做兼容。
targetSdkVersion rootProject.ext.targetSdkVersion
// 应用版本号,覆盖安装时,升级版本依据
versionCode rootProject.ext.app.versionCode
// 版本名称,
versionName rootProject.ext.app.versionName
// 指定需要编译 abi 版本的 so
ndk{
abiFilters 'armeabi-v7a'
}
// 设置编译的资源
resConfigs "zh-rCN"
// 多 dex 支持 ,minSdkVersion >= 20 使用
multiDexEnabled true
}
}

5.4. 签名配置

签名文件,至少配置一个。如未使用,debug 包,会使用 user home/.android/debug.keystore 的签名文件。 release 包默认不签名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 可以将签名文件信息配置在 keystore.properties 文件中
def ksPropFile = rootProject.file("keystore.properties")
def ksProp = new Properties()
// 加载签名配置文件
ksProp.load(new FileInputStream(ksPropFile))

android{
signingConfigs {
// 生产签名,读取配置文件
release {
keyAlias ksProp['keyAlias']
keyPassword ksProp['keyPassword']
storeFile file(ksProp['storeFile'])
storePassword ksProp['storePassword']
}
// 测试签名,静态配置
debug {
keyAlias 'flueky'
keyPassword 'android'
storeFile file('../demo.keystore')
storePassword 'android'
}
}
}

使用配置的好处是,将生产签名文件信息保存在配置文件中,不添加到版本控制工具中,可以有效防范签名文件信息泄露,被他人使用。

针对签名文件的校验,会有专门的防篡改技术,防止应用被反编译 apk 后二次打包。如签名文件泄露,会构成很大威胁。

5.5. 构建类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
android{
buildTypes {
// 测试版本
debug {
// 允许 Java 代码调试(默认允许)
debuggable true
// 允许 JNI 代码调试(默认允许)
jniDebuggable true
// 签名 信息
signingConfig signingConfigs.debug
// 版本名称后缀
versionNameSuffix = '-beta'
// 应用 Id 后缀
applicationIdSuffix '.debug'
}
// 发行版本
release {
// 禁止 Java 代码调试(默认禁止)
debuggable false
// 禁止 JNI 代码调试 (默认禁止)
jniDebuggable false
signingConfig signingConfigs.release
// build/intermediates/proguard-files 目录下存在三个混淆配置文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
// 允许代码压缩、混淆、优化,默认情况下会使用 R8 压缩
minifyEnabled true
// 允许资源压缩,包括清理无用资源。在生成 apk 时,如需保留被删除的图片使用,keep.xml 声明 。
shrinkResources true
// 配置需要放在主 dex 中的类
// multiDexKeepFile file('multidex-config.txt')
// 或者使用下面的配置,语法同 proguard file
// multiDexKeepProguard file('multidex-config.pro')
}
// 内部使用版本
inner {
// 使用 debug 的配置
initWith debug
applicationIdSuffix '.inner'
}
}
}

AndroidStudio 默认支持 debug 和 release 两种类型。并有一些默认的配置。常用配置见上。

5.6. 产品特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
android{
// 定义两种纬度
flavorDimensions 'stage', 'api'

productFlavors {
// 开发阶段
dev {
dimension 'stage'
}
// 生产阶段
pro {
dimension 'stage'
}

minApi21 {
dimension 'api'
minSdkVersion 21
}
minApi23 {
dimension 'api'
minSdkVersion 23
}
minApi26 {
dimension 'api'
minSdkVersion 26
}
}
}

产品特性和构建类型,不仅仅是在构建的时候修改些配置,还可以在 src 目录下,定义同名的文件夹,存放 java 、res 、assets 和 AndroidManifest.xml 等。用于实现不同的业务逻辑,资源图片等。

5.7. 过滤变体

产品特性和构建类型可以通过组合的方式生成多个 apk 文件。但在实际中可能不需要部分组合方式。忽略后,可加速编译过程。

如,内部使用不需要生产阶段的 apk 文件,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
android{
// 设置不需要生成 apk 的类型和特性
variantFilter { variant ->
// 将多个 flavor 组合转成字符串数组
def names = variant.flavors*.name
// 获取到 buildType 名称
def type = variant.buildType.name
// 忽略部分不需要生成的 apk
if (names.contains('pro') && type.equals("inner")) {
setIgnore(true)
}
}
}

5.8. 细分APK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
android{
splits {
// 注意和 nkd.abiFilters 的冲突
abi {
enable true
// 包含所有版本so 的apk 文件。此配置仅用于 abi 。
// density 中默认会生成包含所有资源的 apk
universalApk true
// 去除 x86 和 x86_64
exclude 'x86', 'x86_64'
}
// 注意和 resConfigs 的冲突
density {
enable true
reset()
include "xhdpi"
// compatibleScreens 'small', 'normal', 'large', 'xlarge'
}
}
}

universalApk 只用在 abi 的配置中。true 表示按照默认的方式将全部 so 打包 。density 中,此值必须为 true。
使用 abi 和 density 要注意同 ndk.abiFilters 和 resConfigs 使用的冲突。

5.9. 添加依赖

依赖第三方库,在新版本 Gradle 中,常用的有 implementationapi

它们区别是:

  1. A 中 implementation B , B 中 implementation C 。 B 可以使用 C 的类和方法,A 可以使用 B 的类和方法。
  2. A 中 implementation B , B 中 api C 。 B 可以使用 C 的类和方法,A 可以使用 B 的类和方法。A 还可以使用 C 的类和方法。

这样的好处是,如果只修改了 Module C , 情况 1 只会重新编译 B 和 C ,情况 2 会重新编译 A B C 。 因此在实际应用中,避免大量使用 api 的方式。

下面列举了对 jar 文件、 aar 文件和 maven 库文件的依赖方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
dependencies {
// 依赖同级的 libs 目录,只包含jar
implementation fileTree(include: '*.jar', dir: 'libs')
// 依赖同级的 libs 目录,包含 jar 和 aar,还可以选择不包含指定文件
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
// debug 类型的依赖
debugImplementation fileTree(include: '*.jar', dir: 'src/debug/libs')
// release 类型的依赖
releaseImplementation fileTree(include: '*.jar', dir: 'src/release/libs')
// 同理 devImplementation minApi21Implementation innerImplementation
// 如果 需要组合使用,如 devMinApi21DebugImplementation,见 configurations

// 依赖本地 module
implementation project(':library')
implementation project(':other-lib')
// 等同于带 path 参数
// implementation project(path: ':library')
// 依赖远程库,不建议在版本号中使用 + 通配符,括号可以省略
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation('com.android.support:support-v4:28.0.0') {
// 一个远程库可能包含多个第三方库,可以排除指定库
exclude group: 'com.android.support', module: 'collections'
}
// 等同于复杂形势
// implementation group: 'com.android.support', name: 'support-v4', version: '28.0.0'
implementation 'com.google.dagger:dagger:2.24'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
// 5.0 之前多 dex 支持,minSdkVersion < 20 必须使用。
implementation 'androidx.multidex:multidex:2.0.1'
}

注意到上面的配置中存在 debugImplementation 和 releaseImplementation 。这是对不同构建类型单独指定的依赖方式。

产品特性也可以使用上面的配置,但是需要先声明,方式如下:

1
2
3
4
5
6
7
configurations {
// 添加多个依赖项配置
devMinApi21DebugImplementation {}
devMinApi21Api {}
devDebugCompileOnly {}
minApi21DebugRuntimeOnly {}
}

篇幅有限 ,以上介绍均是常用配置。更多配置请见源码

  1. 修改生成的 apk 文件目录。
  2. 修改生成的 apk 文件名称。
  3. 动态修改版本号。
  4. 替换 build 缓存目录。
  5. AndroidManifest.xml 注入变量。

关于 AndroidManifest.xml 合并冲突,有机会在后面的文章中讲。

6. 常见问题

6.1. 问题1

主 Module 有 inner 的构建类型,库 Module 中、没有 ,构建 app 时会报错。

可在库 Module 中添加 inner 的构建类型,或者在主 Module 的 inner 类型中,添加下面的配置。

1
2
// inner 匹配失败 , debug 匹配成功, release 忽略
matchingFallbacks = ['inner', 'debug', 'release']

如需匹配 release 时,将 release 放在 debug 前,或者直接删除 debug 。

6.2. 问题2

主 Module 有的产品特性,库 Module 中没有,构建 app 时会报错。

错误原因同 问题 1 。如,库 Module 中存在其他类似的产品特性,可使用匹配的方式 ,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
android{
productFlavor{
minApi21 {
minSdkVersion 21
// 库 module 没有 minApi21 ,匹配较高版本
// 同时需要在 AndroidManifest.xml 中使用 overrideLibrary
matchingFallbacks = ['minApi23']
}
minApi26 {
minSdkVersion 26
// 库 module 没有 minApi26 ,匹配较低版本,可兼容
matchingFallbacks = ['minApi23']
}
}
}

如,库 Module 中没有定义任何产品特性,可以直接在 defauleConfig 中,忽略对维度的依赖 。

1
2
3
4
5
6
android{
defauleConfig{
// 库 module 没有开发进度的维度,因此忽略
missingDimensionStrategy 'dev', 'pro'
}
}

6.3. 问题3

主 Module 的 minSdk 小于 库 Module 的 minSdk。

使用 overrideLibrary ,指定复写库 Module 的 packageName 。

1
2
3
4
<manifest xmlns:tools="http://schemas.android.com/tools">
<!-- minSdkVersion 存在冲突的解决方案-->
<uses-sdk tools:overrideLibrary="com.flueky.library" />
</manifest>

源码地址

觉得有用?那打赏一个呗。[去打赏](/donate/)

Author: flueky
Link: http://example.com/024/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.