[转]Android中Gradle入门详解
1.什么是gradle
Gradle 是新一代的自动化构建工具,它是一个独立的项目,跟 AS、Android 无关,官方网站:https://gradle.org/ , 类似 Ant、Maven这类构建工具都是基于 xml 来进行描述的,很臃肿,而 Gradle 采用的是一种叫做 Groovy 的语言,语法跟 Java 语法很像,但是是一种动态语言,而且在 Java 基础上做了不少改进,用起来更加简洁、灵活,而且 Gradle 完全兼容 Maven、Ivy,这点基本上宣布了 Maven、Ivy 可以被抛弃了,Gradle 的推出主要以 Java 应用为主,当然目前还支持 Android、C、C++。
2. Gradle 与 Android Studio 的关系
上面也提到,Gradle 跟 Android Studio 其实没有关系,但是 Gradle 官方还是很看重 Android 开发的,Google 在推出 AS 的时候选中了 Gradle 作为构建工具,为了支持 Gradle 能在 AS 上使用,Google 做了个 AS 的插件叫 Android Gradle Plugin ,所以我们能在 AS 上使用 Gradle 完全是因为这个插件的原因。在项目的根目录有个 build.gradle 文件,里面有这么一句代码:
classpath 'com.android.tools.build:gradle:2.1.2'
这个就是依赖 gradle 插件的代码,后面的版本号代表的是 android gradle plugin 的版本,而不是 Gradle 的版本,这个是 Google 定的,跟 Gradle 官方没关系。
3. Gradle Wrapper
现在默认新建一个项目,然后点击 AS 上的运行,默认就会直接帮你安装 Gradle ,我们不需要额外的安装 Gradle 了,但是其实这个 Gradle 不是真正的 Gradle ,他叫 Gradle Wrapper ,意为 Gradle 的包装,什么意思呢?假设我们本地有多个项目,一个是比较老的项目,还用着 Gradle 1.0 的版本,一个是比较新的项目用了 Gradle 2.0 的版本,但是你两个项目肯定都想要同时运行的,如果你只装了 Gradle 1.0 的话那肯定不行,所以为了解决这个问题,Google 推出了 Gradle Wrapper 的概念,就是他在你每个项目都配置了一个指定版本的 Gradle ,你可以理解为每个 Android 项目本地都有一个小型的 Gradle ,通过这个每个项目你可以支持用不同的 Gradle 版本来构建项目。
4. Gradle两个基本概念:项目和任务
每个 build.gradle 构建脚本文件代表一个项目 project:
任务 task 定义在构建脚本里:
每次构建至少包括一个项目,每个项目里又至少包括一个任务。
在编译过程中, Gradle 会根据 build 相关文件,聚合所有的project和task,执行task 中的 action。因为 build.gradle文件中的task非常多,先执行哪个后执行那个需要一种逻辑来保证。这种逻辑就是依赖逻辑,几乎所有的Task 都需要依赖其他 task 来执行,没有被依赖的task 会首先被执行。所以到最后所有的 Task 会构成一个 有向无环图(DAG Directed Acyclic Graph)的数据结构。
5. 构建生命周期
一个 Gradle 构建通常包括下面三个阶段:
- 初始化
项目实例会在这时被创建,如果这个项目里有多个 module,或者依赖多个 library,并且它们都有对应的 build.gradle 文件,就会创建多个项目实例 - 配置
在这个阶段构建脚本被执行,并且为每个项目实例创建和配置任务 - 执行
在这个阶段 Gradle 将根据构建脚本的配置决定哪些任务会被执行
6. 初识Gradle文件
我们用 Android Studio 新创建一个项目时,会自动生成 3 个 Gradle 文件:
setting.gradle
setting.gradle 文件在 初始化过程中被执行,构建器通过 setting.gradle 文件中的内容了解哪些模块将被 build,下面的内容表明当前项目中除了 app 模块还有另外一个叫做 “shixinlibrary” 的依赖模块:
include ‘:app’, ‘:shixinlibrary’
注意:单模块项目不一定需要有 setting 文件,但一旦有多个模块,必须要有 setting 文件,同时也要写明所有要构建的模块,否则 gradle 不会 build 不包括的模块。
主目录下的 build.gradle
看 gradle 文件中的注释:
Top-level build file where you can configuration options common to all sub-projects/modules.
主目录下的 build.gradle 文件是最顶层的构建文件,这里配置所有模块通用的配置信息。
默认的顶层 build.gradle 文件中包括两个代码块 (buildscript 和 allprojects):
buildscript
从名字就可以看出来,buildscript 是所有项目的构建脚本配置,主要包括依赖的仓库和依赖的 gradle 版本。
下图中 repositories 代码块将 jcenter 配置为一个仓库,JCenter 是一个很有名的 Maven 仓库。确定了依赖的仓库后,我们就可以在 dependencies 代码块中添加依赖的、在 jcenter 仓库中的包了。
如果仓库有密码,也可以同时传入用户名和密码
repositories {
jcenter()
mavenCentral()
maven {
url "https://dl.bintray.com/thelasterstar/maven/"
credentials {
username 'user'
password 'secretpassword'
}
}
}
dependencies 代码块用于配置构建过程中的依赖包,注意,这里是用于构建过程,因此你不能将你的应用模块中需要依赖的库添加到这里。
默认情况下唯一被用于构建过程中的依赖包是 Gradle for Android 的插件。我们还可以添加一些其他用于构建的插件,比如 retrolambda, apt, freeline 等等。
allprojects
allprojects 代码块用来声明将被用于所有模块的属性,注意是所有模块。常见的就是配置仓库地址(jcenter, 自定义 maven 仓库等),你还可以在 allprojects 中创建 tasks,这些 tasks 最终会运用到所有模块中,
官方建议尽量少添加用于所有模块的属性,因为这意味着强耦合,一旦没有构建主项目,你的子模块很有可能因为缺少所有模块的属性导致构建失败。
模块下的 build.gradle
模块下的 build.gradle 文件只应用于当前模块,你可以覆盖主目录下的 build.gradle 的内容。
上图中主要分三个模块:apply plugin , android, dependencies。
apply plugin
apply plugin 声明了接下来要用到哪些插件的内容,上图表明使用了 androd 插件,这里之所以能用 android 插件,是因为主目录中声明了 Gradle for Android 的依赖,这里才能使用。
因此当我们需要使用其他插件,比如 retrolambda 时,首先需要在主目录 build.gradle 文件中添加依赖,然后在模块 build.gradle 中声明使用 retrolambda 插件。
备注:默认的 android 插件是由 Google 官方维护的,为我们提供了构建、测试、打包 Android 应用的能力。除此之外我们还可以自定义插件。在逐渐加深对 Gradle 的了解后,我们将尝试自己写个 Gradle 插件。
android
在声明了 android 插件后,我们就可以使用 android 插件提供的内容进行构建配置。
android 构建配置中必须要有的是两个版本:
- compileSdkVersion : 编译应用的 Android API 版本
- buildToolsVersion : 构建工具版本 (android studio3.0上已经不需要了)
defaultConfig 代码块用于配置应用的默认属性,可以覆盖 AndroidManifest.xml 中的属性,比如:
- applicationId : 覆盖了 AndroidManifest 中的 package name
- minSdkVersion : 覆盖了 AndroidManifest 中的属性,配置运行应用的最小 API
- targetSdkVersion : 一样,用于通知系统当前应用已经被这个版本测试过,和之前的 compileSdkVersion 没有关系
- versionCode : 一样,应用的版本号
- versionName : 版本名称
- defaultConfig 还可以添加签名,占位符等等,这里只列这些。
- buildTypes 用来定义如何构建和打包不同类型的应用,常见的就是测试和生产。
- android 中还可以配置其他信息,比如 签名、渠道等,你可以在 Project Structure 面板中直观的查看,添加,也可以使用代码添加
dependencies
上图中可以看到 依赖配置 在 android 代码块的外边,事实上依赖配置是 Gradle 配置的基础功能,也就是说除了 Android,其他类型的项目(比如 JavaEE )也可以这么用。
我们可以在依赖配置中,添加要使用的库,当然也可以添加本地的 jar 包。
7.Android项目中的Gradle实践
版本的统一管理
当我们的工程中有许多module的时候,分开管理编译版本,minsdk将会是一件很麻烦的事,因为一个library的改动,可能会影响到其他module。这时我们就需要对所有的版本进行统一的管理:
我们可以把一些需要用的字段都放在project的build.gradle(注意是project的不是module的)中:
ext {
compileSdk = 21
minSdk = 11
targetSdk = 23
support = "23.1.1"
buildTools = "21.0.1"
buildstyle ="debug"
}
这样,在module的build.gradle中可以进行读取:
defaultConfig {
applicationId "android.com.testgradle"
minSdkVersion rootProject.ext.minSdk
targetSdkVersion rootProject.ext.targetSdk
versionCode 1
versionName "1.0"
}
多版本打包( Build Variant)
BuildType
默认情况下,Android plugin会自动的构建release和debug两个版本
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
}
}
// release版本中设置了开启混淆,并且定义了混淆文件的位置
Android plugin允许自定义这两个示例,并且可以创建其他的buildType,如下:
buildTypes {
debug {
minifyEnabled false
applicationIdSuffix ".debug"
}
custom.initWith(buildTypes.debug)
custom {
applicationIdSuffix ".custom"
versionNameSuffix "-customs"
}
}
上述配置进行了一下设置:
- 对默认的debug构建类型进行了修改,关闭了混淆配置,添加applicationId后缀
- 以debug为基础创建一个叫custom的构建类型(相当于继承了debug版本),在custom的构建类型中修改applicationId后缀,并添加了versionName的后缀。
Source sets
gradle通过一个叫SourceSets的概念来寻找和关联要编译的源码文件,每当创建一个新的build type 的时候,gradle 默认都会创建一个新的source set。我们可以建立与main文件夹同级的文件夹,根据编译类型的不同我们可以选择对某些源码和资源文件直接进行替换。
对于每一个BuildType,Android plugin都会创建一个对应的sourceSet,默认位置为:src/BuildTypeName
所以新建BuildType的名字不能是main、androidTest和test这三个已经被用的名字
BuildType的代码/资源会以以下方式进行合并
- manifest会被合并到app的manifest文件中
- res目录下的资源文件会替换main里的资源文件
- java目录下的文件会被添加到main里的java目录中,所以不能和main里的类重名(含包名)
除此之外,不同编译类型的项目,我们的依赖都可以不同,比如,如果我需要在staging和debug两个版本中使用不同的log框架,我们这样配置:
Product flavors
Product flavors 默认不引用,我们可以手动添加,同Build type一样,每一个Product flavors 版本也都有属于自己的Source sets,也可以差异化设置构建属性。
当同时设置了BuildType和Product flavors后,二者会组合构建,最多可以生成m×n个版本的apk包。对于二者SourceSets,重复的res文件覆盖原则buildType>Product flavors
在一些情况下,一个应用可能需要基于多个标准来创建多个版本。
例如,有个 app 需要一个免费版本和一个付费的版本,并且需要在不同的 app 发布平台发布。这个 app 需要 2 个付费版和 2 个特定发布平台,因此就需要生成 4 个APK(不算 Build Types 生成的 Variant 版本)。
然而,这款 app 中,为 2 个发布平台构建的付费版本源代码都是相同,因此创建 4 个 flavor 来实现不是一个好办法。 如果使用两个 flavor 维度,两两组合,构建所有可能的 Variant 组合才是最好的。
这个功能的实现就是使用 Flavor Dimensions 。每一个 Dimensions 代表一个维度,并且 flavor 都被分配到一个指定的 Dimensions 中。
android {
...
flavorDimensions 'price', 'store'
productFlavors {
google {
dimension 'store'
}
amazon {
dimension 'store'
}
free {
dimension 'price'
}
paid {
dimension 'price'
}
}
}
/* 注:
dimension参数在gradle2.0之后替换掉了flavorDimension参数,所以很多文章里依然使用的类似于如下写法:
google {
flavorDimension 'store'
}
flavorDimension参数现已失效
*/
andorid.flavorDimensions 数组按照先后排序定义了可能使用的 Dimensions 。每一个 Product Flavor 都被分配到一个 Dimensions 中。
上面的例子中将 Product Flavor 分为两组(即两个维度),分别为 price 维度 [free, paid] 和 store 维度 [google, amazon] ,再加上默认的 Build Type 有 [debug, release] ,这将会组合生成以下的 Build Variant:
- free-google-debug
- free-google-release
- free-amazon-debug
- free-amazon-release
- paid-google-debug
- paid-google-release
- paid-amazon-debug
- paid-amazon-release
每一个 Variant 版本的配置由几个 Product Flavor 对象决定:
- 一个来自 price 组中的对象
- 一个来自 store 组中的对象
android.flavorDimensions 中定义的 Dimensions 排序非常重要(Variant 命名和优先级等)。
flavorDimensions 中的排序决定了哪一个 flavor 覆盖哪一个,这对于资源来说非常重要,因为一个 flavor 中的值会替换定义在低优先级的 flavor 中的值。
flavorDimensions 使用最高的优先级定义,因此在上面例子中的优先级为:
price > store > defaultConfig
android studio 3.0的一些变化
不需要再指定buildToolsVersion了,会根据compileSdkVersion自动匹配合适的版本。
compile标签依然可用,但是已经过时,替代标签为api和implementation
api标签==compile
在java文件中,选中你要转换的代码,然后在顶部选择Code——>Convert Java File to Kotlin File进行转换就好了,转换之后,这就是一个Kotlin文件了。
Android Monitor分离为logcat和Android Profiler
支持java1.8,支持lamda表达式,并提示转换
新的文件资源管理器