Gradle
Gradle是Android项目的主流编译工具,是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各种繁琐配置。
编译周期
在解析Gradle的编译过程中会涉及到Gradle非常重要的两个对象:Project和Task。
每个项目编译至少有一个Project,其中一个build.gradle就代表一个Project,而每个Project又包含多个Task,其中Task又包含多个Action,而Action是一个代码块,里面包含需要被执行的代码,它们的包含关系如下:
Project > Task > Action
在编译过程中,Gradle会根据Build相关文件来聚合所有的Project和Task,并执行Task中的Action。
所有的Task的执行都按照一定的逻辑顺序,这种逻辑称之为依赖逻辑。
编译过程分为三个阶段:
初始化阶段:创建Project对象,如果有多个build.gradle则会创建多个Project。
配置阶段:会执行所有的编译脚本,同时还会创建Project中的所有Task,为下个阶段做准备。
执行阶段:Gradle会根据传入的参数决定执行这些Task,真正的Action执行代码就在这里。
Gradle结构
Android项目中的Gradle最基础的文件配置如下:
一个项目包含一个settings.gradle、包括一个顶层的build.gradle文件,每个Module都有各自的build.gradle文件。
setting.gradle
setting.gradle
中的定义的是哪些Module应该被加入到编译过程,对于单Module项目可以不用该文件。对于多Module项目则需要该文件来指示需要加载哪些Module,它的代码在初始化阶段就会被执行。
include ':app'
include ':Module1'
include ':Module2'
rootProject.name = "MyApp"
多个Module通过include标签进行标明,项目就会根据指定的Module进行加载操作。
Project - build.gradle
一般Android项目中包含两个build.gradle,Project最顶层的build.gradle文件的配置会被应用到所有项目中。
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.1"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
buildscript{}
定义了Android编译工具的类路径,其中repositories里是gradle脚本执行所需的依赖,分别是对应Maven
和插件。
allprojects{}
中定义的属性会被应用到所有的Module中,但是要保证项目的独立性,不要在这里操作过多共有的配置。
App - build.gradle
每个Module都有单独的build.gradle,可以针对不同的Module进行配置,如果这里的配置和顶层的build.gradle相同的话,后者将会覆盖前者。
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.legend.demo"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
......
}
plugins{}
指定了Android程序的Gradle插件,plugin中提供了Android编译、测试和打包的所有Task。在早期的Gradle版本中不是使用id
的方式指定插件的,而是通过如下方式(两种都可以使用):
apply plugin: 'com.android.application'
或
plugins {
id 'com.android.application'
}
android{}
是编译文件中最大的代码块,关于Android所有的特殊配置都在这里。其中'defaultConfig{}'就是程序的默认配置,如果在AndroidMainfest.xml
文件定义相同的属性的话,会以这里的为准。
Gradle Wrapper
Gradle不断的更新版本,新版本会对以往项目存在向后兼容的问题,所以Gradle Wrapper就应运而生。
在Android Studio构建的项目中会自带gradle-wrapper.jar文件,它还拥有一个配置文件:Project/gradle/wrapper/gradle-wrapper.properties
,它的内容如下所示:
#Sun Jan 17 11:15:10 CST 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
distributionUrl
是要下载的gradle地址以及版本,gradle-wrapper会去wrapper/list目录下查找,如果没有对应版本的gradle采取下载,当然我们也可以手动下载Gradle版本放入相应目录即可。
全路径为C:\Users<user_name>.gradle\wrapper\dists\gradle-x.x-bin\<url-hash>
Gradle有三种类型的版本:
1、gradle-xx-all.zip是完整版,包含了各种二进制文件,源代码文件,和离线的文档。
2、gradle-xx-bin.zip是二进制版,只包含了二进制文件(可执行文件),没有文档和源代码。
3、gradle-xx-src.zip是源码版,只包含了Gradle源代码,不能用来编译你的工程。
如果是直接从eclipse 中的项目转换过来的,程序并不会自动创建wrapper脚本,我们需要手动创建。在命令行输入以下命令即可
gradle wrapper --gradle-version 2.4
它会自动构建相应的目录结构。
Gradle 命令
Gradle 会根据build 文件的配置生成不同的task,我们可以直接单独执行每一个task。
gradlew tasks:列出项目中所有的task。
以上命令可以在Android Studio的Terminal面板中使用,如果出现以下提示:
'.gradlew' 不是内部或外部命令,也不是可运行的程序或批处理文件。
出现这种情况则需要在Android Studio的Gradle面板下的build setup中执行wrapper即可解决。
Gradle命令的基本语法如下所示:
gradle [taskName...] [--option-name...]
注意:gradlew是包装器,其中./gradlew、gradle是通用的。
Gradle常用命令如下:
gradle --help:帮助命令
gradle -v:查看版本
gradle [taskName]:执行特定的任务
gradle build:构建
gradle build -x test:跳过测试构建构建
gradle build --continue:继续执行任务而忽略前面失败的任务
gradle -m build:试运行build
gradle build --profile:产生build运行时间的报告,结果存储在build/report/profile目录。
gradlle tasks --all:显示任务间的依赖关系
gradle -q dependencies --configuration testCompile:查看testCompile的依赖关系
gradle clean:清空所有编译、打包生成的文件(即:清空build目录)
gradle -b [file_path] [task]:使用指定的Gradle文件调用任务
gradle -q -p [dir] helloWorld:使用指定的目录调用任务
gradle --gui:Gradle的图形界面
Android tasks
Gradle中有四个基本的task,Android继承它们并进行了自己的实现:
assemble:对所有的 buildType 生成 apk 包。
clean:移除所有的编译输出文件,比如apk
check:执行lint检测编译。
build:同时执行assemble和check命令
在实际项目中会根据不同的配置,会对这些task 设置不同的依赖。比如 默认的assmeble会依赖 assembleDebug 和assembleRelease,如果直接执行assmeble,最后会编译debug和release 的所有版本出来。我们运行的许多命令除了会输出到命令行,还会在build文件夹下生产一份运行报告。
BuildConfig
我们可以通过BuildConfig.DEBUG来判断当前版本是否是debug版本,以此输出只有在debug环境下才会执行的操作。这个类是有gradle根据配置文件生成的。其中Gradle使用的是Groovy语言,它是一种JVM语言,所以即使语法不同,它们最终都会生成JVM字节码文件。
在app/gradle.build中的android{} 下有一个buildTypes{}可以配置key-value的键值对,它的功能非常强大。
比如我们可以为debug何release两种环境定义不同的服务器地址,比如:
android {
......
buildTypes{
release {
buildConfigField "String", "API_URL", "\"http://10.0.0.1/api/\""
buildConfigField "Boolean", "LOG_HTTP_CALLS", "true"
......
}
debug {
buildConfigField "String", "API_URL", "\"http://legend.com/api/\""
buildConfigField "Boolean", "LOG_HTTP_CALLS", "false"
......
}
}
......
}
除此之外,我们还可以为不同编译类型来设置不同的资源文件,比如:
android {
......
buildTypes{
release {
resValue "string", "app_name", "Example"
......
}
debug {
resValue "string", "app_name", "Example DEBUG"
......
}
}
......
}
然后在BuildConfig.java文件中会生成相应的静态变量,直接引用即可。
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.legend.demo";
public static final String BUILD_TYPE = "debug";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
// Field from build type: debug
public static final String API_URL = "http://legend.com/api/";
// Field from build type: debug
public static final Boolean LOG_HTTP_CALLS = false;
}
共享变量
在Android开发过程中,一个项目会有多个Module,如果想保持所有的Module和主Module的配置保持相同的话,可以在这里配置,具体步骤如下:
1、在Project的build.gradle中定义版本号的常量,使用ext{}
包裹:
......
// Define versions in a single place
ext {
compileSdkVersion = 30
buildToolsVersion = "30.0.3"
applicationId = "com.legend.demo"
minSdkVersion = 16
targetSdkVersion = 30
versionCode = 1
versionName = "1.0"
}
......
2、在app下的build.gradle中使用 $rootProject.xxx的方式引用即可。
......
android {
compileSdkVersion rootProject.compileSdkVersion
buildToolsVersion rootProject.buildToolsVersion
defaultConfig {
applicationId rootProject.applicationId
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
versionCode rootProject.versionCode
versionName rootProject.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
......
}
......
Repositories
Repositories 就是代码仓库,我们平时的添加的一些 dependency 就是从这里下载的,Gradle 支持三种类型的仓库:
Maven、Ivy、以及一些静态文件或者文件夹
gradle 支持多种Maven仓库,除了默认的jCenter()
外,还可以添加国内镜像或公司的私有仓库:
repositories {
maven {
url "http://repo.legend.com/maven"
}
}
如果仓库存在密码,也可以同时传入用户名和密码
repositories {
maven {
url "http://repo.legend.com/maven"
credentials {
username 'legend'
password '123456'
}
}
}
我们还可以使用相对路径配置本地仓库,以此来引用项目中存在的静态文件夹作为本地仓库:
repositories {
flatDir {
dirs "aars"
}
}
如果Gradle下载依赖过慢的情况下我们可以在这里配置阿里云的镜像:
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/jcenter/'}
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.1"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/jcenter/'}
}
}
Dependencies
我们在引用库的时候,每个库名称包含三个元素:组名 : 库名称 : 版本号,如下:
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
}
如果我们要保证我们依赖的库始终处于最新状态,我们可以通过添加通配符的方式,比如:
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0.+'
implementation 'com.google.android.material:material:1.1.0.+'
}
使用这种做法每次编译都会去网络请求查看新版本,导致编译慢之外,新版本也可能存在很多问题。
依赖方式
dependencies{}
中库的引入有三种方式:compile、implementation、api,它们的区别如下所示:
compile:gradle升级3.+版本后,原来的依赖方法compile替换成了implementation和api,其中api是用来替换compile的。
implementation:当前Module的依赖,使用implementation指令的依赖不会传递。(只编译当前模块,构建速度快)
api:等同于compile,是用来替代compile的方式,使用api指令的依赖会传递。(编译所有模块,构建速度慢)
所谓的依赖传递的意思是:假如ModuleA依赖于ModuleB,而ModuleB依赖于ModuleC,那么ModuleA可以引用ModuleC中的依赖。
其中还有一些其他的依赖方式如下:
compileOnly(provided):只在编译时有效,不会参与打包。
runtimeOnly(apk):只在生成apk的时候参与打包,编译时不会参与,很少用。
本地依赖
文件依赖
通过files()
方法可以添加文件依赖,如果有很多jar文件,我们也可以通过fileTree()
方法添加一个文件夹,除此之外,我们还可以通过通配符的方式添加,如下:
dependencies{
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
Native依赖
配置本地 .so
库。在配置文件中做如下配置,然后在对应位置建立文件夹,加入对应平台的.so
文件。
android{
sourceSets.main{
jniLibs.srcDir 'src/main/libs'
}
}
文件结构如下:
模块依赖
如果我们要写一个library项目让其他的项目引用,我们的bubild.gradle的plugin 就不能是andrid plugin了,需要引用如下plugin:
apply plugin: 'com.android.library'
引用的时候在setting文件中include
即可。目前新版的Gradle已经更换为如下方式引入:
plugins {
id 'com.android.library'
}
如果我们不方便直接引用项目,需要通过文件的形式引用,我们也可以将项目打包成aar
文件,注意,这种情况下,我们在项目下面新建arrs
文件夹,并在build.gradle 文件中配置 仓库:
repositories{
flatDir{
dir 'aars'
}
}
当需要引用里面的某个项目时,通过如下方式引用:
dependencies{
implementation(name: 'libraryName', ext:'aar')
}
构建多版本
在开发中我们可能会有这样的需求:
- 我们需要在debug 和 release 两种情况下配置不同的服务器地址;
- 当打市场渠道包的时候,我们可能需要打免费版、收费版,或者内部版、外部版的程序。
- 渠道首发包通常需要要求在欢迎页添加渠道的logo。等等
- 为了让市场版和debug版同时存在与一个手机,我们需要编译的时候自动给debug版本不一样的包名。
这些需求都需要在编译的时候动态根据当前的编译类型输出不同样式的apk文件。
Build Type
buildTypes{}
定义了编译类型,针对不同的类型可以有不同的配置和编译命令。默认的有debug和release选项。
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的Build Variants面板中看到debug
和local
两个构建类型,点击运行即可构建。
资源配置
每当创建一个新的build type 的时候,gradle 默认都会创建一个新的source set。我们可以建立与main
文件夹同级的文件夹,根据编译类型的不同我们可以选择对某些源码直接进行替换。
除了代码可以替换,我们的资源文件也可以替换
除此之外,不同编译类型的项目,我们的依赖都可以不同,比如,如果我需要在local和debug两个版本中使用不同的Glide框架,我们这样配置:
dependencies{
implementation fileTree(dir: 'libs', include: ['*.jar'])
debugImplementation 'androidx.appcompat:appcompat:1.2.0'
debugCompile 'com.github.bumptech.glide:glide:4.9.0'
}
对于各个渠道还可以单独依赖属于渠道特有的包,通过:渠道名+implementation指定,
比如:debugImplementation、releaseImplementation、localImplementation。
产品维度
如果我们需要针对同一份源码编译不同的程序(包名也不同),比如 免费版和收费版。我们就需要Product flavors
。
注意:Product flavors和Build Type是不一样的,而且他们的属性也不一样。所有的 product flavor 版本和defaultConfig 共享所有属性!
像Build Type 一样,Product Flavor 也可以有自己的source set
文件夹。
除此之外,Product Flavor 和 Build Type 可以结合,他们的文件夹里面的文件优先级甚至高于单独的Build Type和Product Flavor文件夹的优先级。
如果你想对于 blue类型的release 版本有不同的图标,我们可以建立一个文件夹叫blueRelease
,注意,这个顺序不能错,一定是 flavor + buildType 的形式。
更复杂的情况下,我们可能需要多个product 的维度进行组合,比如我想要 color 和 price 两个维度去构建程序。这时候我们就需要使用flavorDimensions
:
android{
flavorDimensions "color", "price"
productFlavors{
red{
flavorDimension "color"
}
blue{
flavorDimension "color"
}
free{
flavorDimension "price"
}
paid{
flavorDimension "price"
}
}
}
根据我们的配置,再次查看我们的task,发现多了这些task:
blueFreeDebug and blueFreeRelease
bluePaidDebug and bluePaidRelease
redFreeDebug and redFreeRelease
redPaidDebug and redPaidRelease
在Build Type中定义的资源优先级最大,在Library 中定义的资源优先级最低。
Build Type > Flavor > Main > Dependencies
签名配置
如果我们打包市场版的时候,我们需要输入我们的keystore数据。如果是debug 版本,系统默认会帮我们配置这些信息。这些信息在gradle 中都配置在signingConfigs
中。
signingConfigs {
debug {
keyAlias 'key0'
keyPassword 'xxxxxx'
storeFile file('../keys/debug.jks')
storePassword 'xxxxxx'
}
release {
keyAlias 'key0'
keyPassword 'xxxxxx'
storeFile file('../keys/debug.jks')
storePassword 'xxxxxx'
}
}
配置之后我们需要在Build Type中直接使用
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
优化构建速度
可以通过以下方式加快gradle 的编译:
- 开启并行编译:在项目根目录下面的
gradle.properties
中设置
org.gradle.parallel=true
- 开启编译守护进程:该进程在第一次启动后回一直存在,当你进行二次编译的时候,可以重用该进程。同样是在
gradle.properties
中设置。
org.gradle.daemon=true
- 加大可用编译内存:
org.gradle.jvmargs=-Xms256m -Xmx1024m
在编译的时候,我们可能会有很多资源并没有用到,此时就可以通过shrinkResources
来优化我们的资源文件,除去那些不必要的资源。
android{
buildTypes{
release{
minifyEnable = true
shrinkResource = true
}
}
}
某些情况下,一些资源是需要通过动态加载的方式载入的,这时候我也需要像 Progard 一样对我们的资源进行keep操作。方法就是在res/raw/
下建立一个keep.xml
文件,通过如下方式 keep 资源:
<?xmlversion="1.0"encoding="utf-8"?>
<resourcesxmlns:tools="http://schemas.android.com/tools" tools:keep="@drawable/aaa_*,@values/aaa_*,@layout/aaa_*"/>
对于尺寸文件我们也可以这样做
android{
defaultConfig{
resConfigs "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
}
}