什么是Gradle
Gradle 是新一代的自动化构建工具,它是一个独立的项目,跟 AS、Android 无关,类似 Ant、Maven这类构建工具都是基于XML来进行描述的,很臃肿,而 Gradle 采用的是一种叫做 Groovy 的语言,语法跟 Java 语法很像,但是是一种动态语言,而且在 Java 基础上做了不少改进,用起来更加简洁、灵活,而且 Gradle 完全兼容 Maven、Ivy,这点基本上宣布了 Maven、Ivy 可以被抛弃了,Gradle 的推出主要以 Java 应用为主,当然目前还支持 Android、C、C++。
Gradle是一种构建工具,它通过编写一个名为build.gradle的脚本文件对项目进行设置,再根据这个脚本对项目进行构建(复杂的项目也有其他文件),帮你管理项目中的差异、依赖、编译、打包、部署……你可以定义满足自己需要的构建逻辑,写入到build.gradle中供日后复用。
Gradle 脚本本质上就是Groovy脚本,只不过高度利用了Groovy的语法糖,例如省略方法参数括号和省略句尾分号等,让代码看起来像DSL(特定领域语言)。Groovy语法跟 Java 语法很像,但是是一种动态语言,而且在 Java 基础上做了不少改进,用起来更加简洁、灵活,所以几乎所有Java和Groovy支持的语法,它的脚本都支持,而且 Gradle 完全兼容 Maven、Ivy。
构建工具
我们以前开发都是用 Eclipse ,而Eclipse 大家都知道是一种 IDE(集成开发环境),最初是用来做 Java 开发的,而 Android 是基于 Java 语言的,所以最初 Google 还是希望 Android 能在 Eclipse 上进行开发,为了满足这个需求,Google 开发了一个叫 ADT (Android Developer Tools)的东西,相信以前从 Eclipse 时代过来的对 ADT 应该都不陌生,正是因为有了 ADT ,从此我们只需要码好代码,然后直接在Eclipse 上进行编译、运行、签名、打包等一系列流程,而这背后的工作都是 ADT 的功劳。某种意义上 ADT 就是我们的构建工具。
而自 Google 推出 Android Studio 以来,就宣布默认使用 Gradle 来作为构建工具,并且之后放弃更新 ADT ,从此 Gradle 走入 Android 开发者的视野,而我也是在 AS 的 Beta 版开始接触并学习 Gradle。
一般来说,构建工具除了以上提到的编译、运行、签名、打包等,还具备依赖管理的功能,什么是依赖管理呢?还是拿 Eclipse 来说,我们以前在 Eclipse 上开发 Android ,如果需要用到第三方库的时候一般都是先下载 jar 文件,然后把 jar 文件添加到 libs 目录,然后项目中就可以引用了。但是你不觉得这种管理方式很麻烦么?假设第三方库有更新,需要下载最新的 Jar 文件,然后替换掉原来的,引用的库少还好,一旦引用的第三方库多,那简直麻烦死,可以说这种方式只有依赖,而没有管理。
现在大家不陌生的 Gradle 引用第三方库方式是这样的:compile 'com.android.support:support-v4:24.0.1'
类似这样的依赖方式,是不是很方便?而且很直观,直接可以看到源地址,升级的话直接改下版本号就可以了,这就是所谓的依赖管理。
所以构建工具就是对你的项目进行编译、运行、签名、打包、依赖管理等一系列功能的合集,传统的构建工具有 Make、Ant、Maven、Ivy等,而 Gradle 是新一代的自动化构建工具。
Ant编写容易,但功能有限,需要人工操作的过程也多;Maven依托于庞大的依赖仓库,因此有着强大的外部依赖管理,但添加本地依赖并不方便,且项目不能灵活修改。而Gradle能很好地结合Ant与Maven各自的优点,可以随意的编写任务并组合成项目,直接利用Maven仓库,并且能很好的支持传递依赖和内部依赖。
通俗一点类比成吃饭的话,大致就是:Ant是自己买菜洗菜烧水做饭,Maven是去饭店点餐,Gradle是3D打印食物。
Gradle应用场景
差异管理
国内有n个Android市场,n个手机品牌,n个手机尺寸……,一般公司都会针对不同的市场单独发包用来统计不同渠道的下载量等情况,可能需要针对不同(品牌,尺寸等各种硬件信息)的手机做一些特殊的处理,这个时候你可以针对不同的情况单独建一个工程,或者更好一点你可以通过一些变量来控制,像这样:
if(isMoto){
do something
}else if(isHuawei){
do something
}
依赖管理
软件开发可能需要依赖各种不同的jar、library,可以通过将.jar/library工程下载到本地再copy到你的工程中,但不知你是否听说过国外有个叫中央仓库的东西,在这个仓库里你可以找到所有你能想到以及你从来没听说过的jar、aar……这里可以找到所有你需要的依赖,而你需要的只是指定一个坐标
compile 'com.android.support:support-v4:24.0.1'
剩下的依赖的寻找、下载、添加到classpath等你都不需要去关心
什么是Gradle插件?
在使用Android Studio进行开发的时候,我们创建一个Android工程,会默认生成一个build.gradle脚本,打开脚本你会看到以下代码:
apply plugin: 'com.android.application'
如果我们创建一个Library的话,就会变成:
apply plugin: 'com.android.library'
其实这就是在Gradle脚本引用Android为我们提供的插件,plugin表示插件,’com.android.application’表示我们引用的插件名,通常是以包名来命名。
这个插件是怎么被引用进来的?我们可以在根目录找到另外一个build.gradle文件,打开可以看到以下代码:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
- buildscript方法是定义了全局的相关属性
- repositories定义了jcenter作为仓库。一个仓库代表着你的依赖包的来源,例如maven仓库
- dependencies用来定义构建过程
- classpath ‘com.android.tools.build:gradle:2.0.0’,就是将远程的插件下载到本地并将其构建到我们工程当中
Androd应用插件其实就是Gradle插件,用于提供Gradle构建环境,支持其在AndroidStudio中使用,通过classpath 'com.android.tools.build:gradle:1.2.3'
获取插件,apply plugin: 'java
应用插件。
理解Gradle脚本
当然我们现在讨论的所有内容都是基于Android Studio的,所以请先行下载相关工具。当我们创建一个新的工程,Android Studio会默认为我们创建三个gradle文件,两个build.gradle,一个settings.gradle,build.gradle分别放在了根目录和moudle目录下,下面是gradle文件的构成图:
MyApp
├── build.gradle
├── settings.gradle
└── app
└── build.gradle
setting.gradle解析
当你的app只有一个模块的时候,你的setting.gradle将会是这样子的:
include ':app'
setting.gradle文件将会在初始化时期执行,关于初始化时期,并且定义了哪一个模块将会被构建。举个例子,上述setting.gradle包含了app模块,setting.gradle是针对多模块操作的,所以单独的模块工程完全可以删除掉该文件。在这之后,Gradle会为我们创建一个Setting对象,并为其包含必要的方法,你不必知道Settings类的详细细节,但是你最好能够知道这个概念。
build.gradle的配置文件
基于grade构建的项目通常至少有一个build.gradle,那么我们来看看Android的build.gradle:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
这个就是实际构建开始的地方,在仓库地址中,我们使用了JCenter,JCenter类似maven库,不需要任何额外的配置,grade还支持其他几个仓库,不论是远程还是本地仓库。
构建脚本也定义了一个Android构建工具,这个就是Android plugin(Android应用插件)的来源之处。Android plugin提供了所有需要去构建和测试的应用。每个Android应用都需要这么一个插件:
apply plugin: 'com.android.application'
Android应用插件用于扩展Gradle脚本的能力,在一个项目中使用插件,这样该项目的构建脚本就可以定义该插件定义好的属性和使用它的tasks。
这里的Androd应用插件其实就是Gradle插件,用于提供Gradle构建环境,支持其在AndroidStudio中使用,通过classpath 'com.android.tools.build:gradle:1.2.3'
获取插件,apply plugin: 'java
应用插件
注意:当你在开发一个依赖库,那么你应该使用’com.android.library’,并且你不能同时使用他们2个,这将导致构建失败,一个模块要么使用Android application或者Android library插件,而不是二者。
当使用Android 插件的时候,Android标签将可以被使用,如下所示:
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
}
根目录的build.gradle
该gradle文件是定义在这个工程下的所有模块的公共属性,它默认包含二个方法:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
allprojects {
repositories {
jcenter()
}
}
repositories {
mavenCentral()
}
buildscript方法是定义了全局的相关属性,repositories定义了jcenter作为仓库。一个仓库代表着你的依赖包的来源,例如maven仓库。dependencies用来定义构建过程。这意味着你不应该在该方法体内定义子模块的依赖包,你仅仅需要定义默认的Android插件就可以了,因为该插件可以让你执行相关Android的tasks。
allprojects方法可以用来定义各个模块的默认属性,你可以不仅仅局限于默认的配置,未来你可以自己创造tasks在allprojects方法体内,这些tasks将会在所有模块中可见。
为什么repositories要声明两次哪?buildscript代码块中的声明与下半部分声明有什么不同?buildscript中的声明是gradle脚本自身需要使用的资源。可以声明的资源包括依赖项、第三方插件、maven仓库地址等。而在build.gradle文件中直接声明的依赖项、仓库地址等信息是项目自身需要的资源。
模块内的build.gradle
模块内的gradle文件只对该模块起作用,而且其可以重写任何的参数来自于根目录下的gradle文件。模块内的build.gradle主要分三个模块:apply plugin , android, dependencies。
该模块文件应该是这样:
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "com.gradleforandroid.gettingstarted"
minSdkVersion 14
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile
('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
}
apply plugin
apply plugin 声明了接下来要用到哪些插件的内容,上图表明使用了 Androd 插件,这里之所以能用 Android 插件,是因为主目录中声明了 Gradle for Android 的依赖,这里才能使用。Android应用插件,该插件其是Google的Android开发团队编写的插件,能够提供所有关于Android应用和依赖库的构建,打包和测试。
因此当我们需要使用其他插件,比如 retrolambda 时,首先需要在主目录 build.gradle 文件中添加依赖,然后在模块 build.gradle 中声明使用 retrolambda 插件。
备注:默认的 Android 插件是由 Google 官方维护的,为我们提供了构建、测试、打包 Android 应用的能力。除此之外我们还可以自定义插件。在逐渐加深对 Gradle 的了解后,我们将尝试自己写个 Gradle 插件。
android
在声明了 Android 插件后,我们就可以使用 Android 插件提供的内容进行构建配置。
android 构建配置中必须要有的是两个版本:
- compileSdkVersion : 编译应用的 Android API 版本
- buildToolsVersion : 构建工具版本
- 构建工具包括 aapt, zipalign, renderscript 等
- 用于在打包时生成各种中间产物,可以从 SDK Manager 中下载构建工具
defaultConfig 代码块用于配置应用的默认属性,可以覆盖 AndroidManifest.xml 中的属性,比如:
- applicationId : 覆盖了 AndroidManifest 中的 package name
- minSdkVersion : 覆盖了 AndroidManifest 中的属性,配置运行应用的最小 API
- targetSdkVersion : 一样,用于通知系统当前应用已经被这个版本测试过,和之前的 compileSdkVersion 没有关系
- versionCode : 一样,应用的版本号
- versionName : 版本名称
defaultConfig 还可以添加签名,占位符等等,这里只列这些。
buildTypes 用来定义如何构建和打包不同类型的应用,常见的就是测试和生产。
android 中还可以配置其他信息,比如 签名、渠道等,你可以在 Project Structure 面板中直观的查看,添加,也可以使用代码添加。
dependencies
上图中可以看到 依赖配置 在 android 代码块的外边,事实上依赖配置是 Gradle 配置的基础功能,也就是说除了 Android,其他类型的项目(比如 JavaEE )也可以这么用。
依赖模块作为Gradle默认的属性之一,为你的App定义了所有的依赖包。
在配置完项目仓库后,我们可以声明其中的依赖,如果我们想要声明一个新的依赖,可以采用如下步骤:
- 指定依赖的配置。
- 声明所需的依赖。
有些时候,你可能需要和SDK协调工作。为了能顺利编译你的代码,你需要添加SDK到你的编译环境。你不需要将SDK包含在你的APK中,因为它早已经存在于设备中,所以配置来啦,我们会有5个不同的配置:
- compile
- apk
- provided
- testCompile
- androidTestCompile
compile是默认的那个,其含义是包含所有的依赖包,即在APK里,compile的依赖会存在。
apk的意思是apk中存在,但是不会加入编译中,这个貌似用的比较少。
provided的意思是提供编译支持,但是不会写入apk。
testCompile和androidTestCompile会添加额外的library支持针对测试。
这些配置将会被用在测试相关的tasks中,这会对添加测试框架例如JUnit或者Espresso非常有用,因为你只是想让这些框架们能够出现在测试apk中,而不是生产apk中。
除了这些特定的配置外,Android插件还为每个构建变体提供了配置,这让debugCompile或者releaseProvided等配置成为可能。如果你想针对你的debug版本添加一个logging框架,这将很有用。
一个依赖需要定义三个元素:group,name和version。group意味着创建该library的组织名,通常这会是包名,name是该library的唯一标示。version是该library的版本号,我们来看看如何申明依赖:
dependencies {
compile 'com.google.code.gson:gson:2.3'
compile 'com.squareup.retrofit:retrofit:1.9.0'
}
上述的代码是基于Groovy语法的,所以其完整的表述应该是这样的:
dependencies {
compile group: 'com.google.code.gson', name: 'gson', version:'2.3'
compile group: 'com.squareup.retrofit', name: 'retrofit', version: '1.9.0'
}
Gradle脚本的执行时序
Gradle脚本的执行分为三个过程:
- 初始化
分析有哪些Module将要被构建,为每个Module创建对应的 Project实例。这个时候settings.gradle
文件会被解析,setting.gradle是针对多模块操作的,定义了哪一个模块将会别构建。 - 配置:处理所有的模块的 build.gradle 脚本,处理依赖、属性等。这个时候每个模块的build.gradle文件会被解析并配置,这个时候会构建整个Task的链表(这里的链表仅仅指存在依赖关系的Task的集合,不是数据结构的链表)。
- 执行:根据Task链表来执行某一个特定的Task,这个Task所依赖的其他Task都将会被提前执行。
Project和tasks
在Grade中的两大重要的概念,分别是Project和Tasks。每一次构建都是有至少一个Project来完成,所以Android Studio中的Project和Gradle中的Project不是一个概念。每个Project有至少一个Tasks。每一个build.grade文件代表着一个Project。Tasks在build.gradle中定义。当初始化构建进程,Gradle会基于build文件,集合所有的Project和Tasks,一个Tasks包含了一系列动作,然后它们将会按照顺序执行,一个动作就是一段被执行的代码,很像Java中的方法。