1.Summary

  从Android团队开始宣布放弃Eclipse转投Android Studio时,构建工具Gradle进入了Android开发者的视野。而随着热修复、插件化、编译时注解的流行,深入了解Gradle就变得很有必要了。那么什么是Gradle ?

2.About

  Gradle是一个基于Ant构建工具,用Groovy DSL描述依赖关系的jar包。我们都知道早期的Android开发使用的是Eclipse,而Eclipse的构建工具使用的是Ant,用XML描述依赖关系,而XML存在太多的弊端,不如动态语言。所以动态语言Groovy代替了XML,最后集成为Gradle。而Groovy的诞生正是由于Java在后端某些地方不足,对于配置信息处理比较差,所以Apache开发了这门语言并且开源了代码。各家公司也对其进行了大量使用,其中LinkedIn公司开源了许多的Gradle插件,有兴趣的可以下载源码看看。Gradle的使用场景也很多,单元测试,自动化集成,依赖库管理等。既然说到了Java在后端的应用,必然要说道Android端的Java,与之搭配的就是最近很火的Kotlin,Kotlin也是一门动态语言,而且Kotlin和Groovy一样也可以写build.gradle文件,它们都是基于JVM的动态语言,都可以使用DSL去描述项目依赖关系。讲到这里我不禁佩服JVM生态,除了Kotlin、Groovy,还有Scala、 Clojure等,通过这些不同的语言可以去写不同层级的代码,而最后都是字节码。

3.Intoduction

  我们会介绍DSL、Gradle相关知识。

Groovy DSL

  首先Groovy语言的基本知识我们不进行探讨,网上与之相关的资料有很多。我们来讲讲它的DSL,因为Gradle提供的build.gradle配置文件就是用DSL来写的。那么什么是DSL?维基百科里面描述的很清楚,但是具体到代码有哪些呢?就像Android里面的AIDL(Java DSL)、HIDL,前端的JQUERY(JavaScript DSL)。由于DSL是一种为解决某种问题的领域指定语言,而不像Java这种通用性计算机语言有语法解析器,所以Android团队写了解析AIDL的语法解析器,Gradle团队写了解析Groovy DSL的语法解析器。如果想要开发针对自己公司业务的DSL,那么可以自行到网上查找相关的学习资料。不过对于中小公司都是使用成熟的DSL框架,而不是重零开始,我们只要学会使用某个DSL框架就可以了,就比如Gradle框架,只要理解框架中插件的创建,任务的定义就可以了。

  说了这么多不如来个代码感受一下。

apply plugin: 'com.android.application'

android {
compileSdkVersion 23
buildToolsVersion "27.0.0"
defaultConfig {
applicationId "com.hawksjamesf.myapplication"
minSdkVersion 14
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
} dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:23.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
// testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

  如果你是第一次接触Gradle的话,你一定表示看不懂这门语言,但是如果把它看成配置文件,是不是就能理解了。Gradle使用了大量的闭包和lamda这样简洁的语法来表示配置信息,而且Gradle中很多地方做了省略。比如去掉分号,去掉方法括号等等,力求做到精简。

  如果你想要配置一些简单的属性,可以通过API查看,比如添加debug的配置。

buildTypes {
debug{
println 'haha'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

  一般DSL形成的框架都要有足够完整的API,因为其专业性太强了,就是所谓的行话,一般人看不懂,要通过查百科才能明白。

  如果你想要根据公司的业务添加一些代码的话,那么就需要我们写任务或者插件,而这需要我们熟悉Gradle框架和Groovy语言了。

Gradle框架

  我们都知道Gradle的生命流程要经历三个部分:初始化、配置、执行。

  • 初始化阶段:settings.gradle

    在初始化阶段,Gradle会为每个项目创建Project对象(每个项目项目都会有一个build.gradle),那么系统是如何知道有哪些项目的,通过settings.gradle。
  • 配置阶段:build.gradle

    在配置阶段,Gradle会解析已经创建Project对象的项目的build.gradle文件,Project包含多个task,这些task被串在一起,存在相互依赖的关系。
  • 执行阶段:task

    最后就是执行这些task了。

  基于这个流程Android团队提供了自己的插件给Android开发者写Android项目,并且有丰富的DSL文档Android Plugin DSL Reference。如果想要看看Gradle官方提供的插件可以看看这个文档Gradle Build Language Reference

  为了验证流程,我在自己的项目SimpleWeather中添加如下log。

SimpleWeather/settings.gradle

println("settings start")
include ':app', ':location'
println("settings end") //include ':viewpagerindicator_library'

SimpleWeather/build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
println("root project start")
buildscript {
repositories {
jcenter()
google() }
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
//
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
} allprojects {
repositories {
jcenter()
google()
}
} task clean(type: Delete) {
delete rootProject.buildDir
} ext{
compileSdkVersion=26
buildToolsVersion ='27.0.0'
minSdkVersion =17
targetSdkVersion =26
versionCode=2
versionName="2.0"
} println("root project end")

SimpleWeather/app/build.gradle

println("app start")
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName multiDexEnabled = true
applicationId "com.hawksjamesf.simpleweather"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
// 设置支持的SO库架构
abiFilters 'x86_64'
abiFilters 'x86'
abiFilters 'armeabi-v7a'
abiFilters 'arm64-v8a'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField("String", "BASE_URL", '"https://weatherapi.market.xiaomi.com"')
}
debug {
// buildConfigField ("String","BASE_URL",'"https://api.caiyunapp.com/"')
buildConfigField("String", "BASE_URL", '"https://weatherapi.market.xiaomi.com"')
}
}
lintOptions {
// abortOnError false
} productFlavors { }
} dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
// implementation project(':viewpagerindicator_library')
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
implementation 'com.google.dagger:dagger:2.11'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:retrofit-converters:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.+'
implementation 'com.squareup.okhttp3:okhttp:3.8.1'
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
// implementation 'com.jakewharton.timber:timber:4.5.1'
implementation 'com.orhanobut:logger:2.1.1'
implementation 'com.google.code.gson:gson:2.8.2'
implementation 'org.greenrobot:eventbus:3.0.0'
implementation 'io.reactivex.rxjava2:rxjava:2.+'
implementation 'com.tencent.bugly:crashreport:2.6.6.1'
implementation 'com.tencent.bugly:nativecrashreport:3.3.1'
// Test helpers for Room
testImplementation 'android.arch.persistence.room:testing:1.0.0'
// Room (use 1.1.0-alpha1 for latest alpha)
implementation 'android.arch.persistence.room:runtime:1.0.0'
annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
// RxJava support for Room
implementation 'android.arch.persistence.room:rxjava2:1.+'
// implementation "com.android.support:multidex:1.0.1"
//noinspection GradleCompatible
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support:design:26.1.0'
implementation 'com.android.support:recyclerview-v7:26.1.0'
compile project(path: ':location')
}
println("app end")

SimpleWeather/location/build.gradle

println("lib location start")
apply plugin: 'com.android.library' android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
} } dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
println("lib location end")

我们可以清醒的看到初始化阶段和配置阶段。

[0] % ./gradlew clean
Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details
settings start
settings end > Configure project :
root project start
root project end > Configure project :app
app start
Configuration 'compile' in project ':app' is deprecated. Use 'implementation' instead.
app end > Configure project :location
lib location start
lib location end

Gradle任务

配置阶段调用


  在前面我们粗糙地讲了task,这里我们在细讲一下。在Android DSL中默认的任务有编译、打包、签名、安装,它们按照顺序被一一执行。而我们也可以写一些hook它们流程的任务.

下面是定义task的三种方式

task(hello) {
println "config hello"
} task('hello') {
println "config hello"
} tasks.create(name: 'hello') {
println "config hello"
}

  在Gradle中为了使得配置文件看起来更加精简,精简版之后的代码如下

task hello {
println "config hello"
} tasks.create('hello') {
println "config hello"
}

  精简版是开发者最为常用的。当我们定义了任务内容,通过./gradle hello就可以执行task,但是我们都知道Gradle的流程中会先配置task在执行task,而上面的代码会在配置阶段就被调用。那么问题来了,如果我们想要代码在执行阶段被调用要怎么办呢 ?

执行阶段调用


看代码。

task hello {
println "config hello" doLast {
println "excute hello"
}
} hello.doLast {
println "excute hello"
} hello.leftShift {
println "excute hello"
} hello << {
println "excute hello"
}

  <<符号的出现就是为了精简代码,所以和leftShift、doLast一样没什么可说的。所以在执行阶段执行代码的方式也就两种。

task hello {
println "config hello" doLast {
println "excute hello"
}
} hello << {
println "excute hello"
}

  对于第一种可以将配置阶段的代码和执行阶段的代码写在一个闭包里面,对于第二种只能写执行阶段的代码,其中各种利弊相比已经很清晰了。

  除了简单的定义task,还可以进阶的定义task。

来看个代码。

task clean(type: Delete) {
delete rootProject.buildDir
} task copy(type: Copy) {
from 'resources'
into 'target'
include('**/*.txt', '**/*.xml', '**/*.properties')
}

  Delete是Gradle提供的,我们可以让自己的task拥有其特性,比如delete那个文件/文件夹。不是很明白的话,我们可以看第二个例子。想要复制一个文件可以使用Copy,from表示源文件,into 表示目标文件,include 表示要复制的文件。当然了这种书写方式还有另外一种,来看个代码。

task myCopy(type: Copy)

myCopy {
from 'resources'
into 'target'
include('**/*.txt', '**/*.xml', '**/*.properties')
}

  Gradle并不管给我们提供了这两个task type,还有很多具体查看Project API,页面左侧栏。当然了我们还可以写一个类继承Copy,然后重写一些属性、方法。

任务相关性


  有的时候我们需要增加任务的相关性,比如一个任务的执行需要另外一个任务执行完才能执行。

用代码说话

1.
project('projectA') {
task taskX(dependsOn: ':projectB:taskY') {
doLast {
println 'taskX'
}
}
} project('projectB') {
task taskY {
doLast {
println 'taskY'
}
}
} 2.
task taskX {
doLast {
println 'taskX'
}
} task taskY {
doLast {
println 'taskY'
}
} taskX.dependsOn taskY

  有两种写法,一种是写在定义时,另外一种是写在调用时。

对于第二种还有它的变种版本。

task taskX {
doLast {
println 'taskX'
}
} taskX.dependsOn {
tasks.findAll { task -> task.name.startsWith('lib') }
} task lib1 {
doLast {
println 'lib1'
}
} task lib2 {
doLast {
println 'lib2'
}
} task notALib {
doLast {
println 'notALib'
}
}

除了dependsOn,你还可以使用mustRunAfter、shouldRunAfter来进行排序。

Gradle插件

编写Gradle插件


  Gradle插件分为两种:script plugins 和 binary plugins。script plugins通过apply from: 'other.gradle'来引用;而binary plugins通过apply plugin: 'com.android.application'引用。这里我们将会讲解第二种,在讲解第二种的过程中可能会涉及到第二种。既然我们已经知道如何使用插件,那么接下来就要知道怎么定义插件。

写插件的三种方式:

  • Build script
  • buildSrc project
  • Standalone project

第一种我们的代码应该这么写

class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('hello') {
doLast {
println 'Hello from the GreetingPlugin'
}
}
}
} // Apply the plugin
apply plugin: GreetingPlugin

如果代码量较多我们就不能写在build.gradle文件,需要像编写一个库一样来写插件。而这个时候我们就可以创建一个模块,也就是第二种。



项目的地址SimpleWeather*

这里有几点需要注意的

  • 模块的名字必须是buildSrc。
  • versionplugin.properties文件名的versionplugin对应的就是apply plugin: versionplugin的versionplugin,文件中通过“implementation-class=com.hawksjamesf.plugin.VersionPlugin”表明Plugin子类的位置。
  • 哦对了,该模块groovy目录下的所有的文件都是用groovy写的,当然你也可以在与groovy目录同级的位置创建java目录,来放置Java代码。

  那么对于第三种,想必我已经不用多说了吧,就是创建一个插件项目。

  如果你想要研究Android团队写的Gradle插件源码,可以通过这里Build Overview获取到源码。

最最后在说一句,由于Kotlin的特性,我们可以用它来替代Groovy写Gradle脚本,这样就可以减少学习Groovy的成本,而且Kotlin自从被google扶正之后,也受到了很多开发者的喜爱,很多项目也在开始用它来做开发。不过千外不要说Java的地位又不行了,身为Java程序员又在自我恐慌,戒骄戒躁,以其浪费时间在恐慌还不如多学几门不同类型语言提升自己。

4.Reference

Groovy官网

使用 Groovy 构建 DSL

DSL编程技术的介绍

CREATING YOUR OWN DSL IN KOTLIN

Gradle的官网

Kotlin Meets Gradle

深入理解Android之Gradle

全面理解Gradle - 定义Task

构建工具Gradle的更多相关文章

  1. 构建工具Gradle安装和简单使用

    1. 安装 到gradle官网下载页 https://gradle.org/gradle-download/ 下载gradle,其中“完全版(Complete distribution)”包含除了运行 ...

  2. 新一代构建工具gradle学习

    简介:Gradle的出现,是技术发展的必然,站在了Ant.maven等构建工具的肩膀上,使用了一种强大且具有表达性的基于Groovy的领域特定语言(DSL),使其拥有易用且灵活的方式去实现定制逻辑.方 ...

  3. 初识构建工具-gradle

    构建工具的作用 依赖管理 测试,打包,发布 主流的构建工具 Ant:提供编译,测试,打包 Maven:在Ant的基础上提供了依赖管理和发布的功能 Gradle:在Maven的基础上使用Groovy管理 ...

  4. 构建工具-----Gradle(二)-----myeclipse 10和myeclipse2015安装gradle插件----其他版本的myeclipse类似

    我们需要给myeclipse安装gradle的插件.这样myeclipse就能识别到gradle项目了,直接加载进去即可. 我们先安装配置系统命令行的gradle,挺简单的,下载后配置环境变量即可,详 ...

  5. 项目构建工具Gradle的使用入门(参考,只表明地址)

    Gradle入门介绍:简介 http://blog.jobbole.com/71999/ Gradle入门介绍:第一个Java项目 http://blog.jobbole.com/72558/ Gra ...

  6. 基于 Groovy 的自动化构建工具 Gradle 入门(转)

    本人工作之初没有使用自动化构建,后来敏捷了,开始使用 Ant - 完全面向过程的定义步骤,不进行依赖管理.再发展到 Maven,面向对象的方式管理工程,有了依赖的管理,JAR 包统一从中央仓库获得,保 ...

  7. java之项目构建工具Gradle

    介绍 Java 作为一门世界级主流编程语言,有一款高效易用的项目管理工具是 java 开发者共同追求的心愿和目标.显示 2000 年的 Ant,后有 2004 年的 Maven 两个工具的诞生,都在 ...

  8. 自动化构建工具gradle安装教程(使用sdkman安装)

    gradle是什么?(wiki解释) Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具.它使用一种基于Groovy的特定领域语言来声明项目设置,而不是传统的 ...

  9. 项目构建工具gradle

    1.安装 https://gradle.org/install 2.构建一个项目 https://guides.gradle.org/creating-new-gradle-builds/ 3.bui ...

随机推荐

  1. springboot入门(三)-- springboot集成mybatis及mybatis generator工具使用

    前言 mybatis是一个半自动化的orm框架,所谓半自动化就是mybaitis只支持数据库查出的数据映射到pojo类上,而实体到数据库的映射需要自己编写sql语句实现,相较于hibernate这种完 ...

  2. C#学习笔记——常量、字段以及事件

    一 常量与字段 (一) 常量 常量总是被视为静态成员,而不是实例成员.定义常量将导致创建元数据.代码引用一个常量时,编译器会在定义常量的程序集的元数据中查找该符号,提取常量的值,并将值嵌入IL中.由于 ...

  3. screenX, clientX, pageX

    screenX:鼠标相对屏幕左上角的水平偏移量. clientX:鼠标相对于浏览器左上角的水平偏移量,会随着滚动条的移动而移动. pageX:鼠标相对浏览器左上角的水平偏移量.不会随着滚动条的移动而移 ...

  4. OC学习篇之---协议的概念和用法

    这一篇文章我们在来看一下OC中协议的概念以及用法,协议也是OC中的一个重点,Foundation框架以及我们后面在写代码都会用到. OC中的协议就是相当于Java中的接口(抽象类),只不过OC中的名字 ...

  5. php 面试题一(看视频的学习量比网上瞎转悠要清晰和明了很多)(看视频做好笔记)(注重复习)

    php 面试题一(看视频的学习量比网上瞎转悠要清晰和明了很多)(看视频做好笔记)(注重复习) 一.总结 1.无线分类的本质是树(数据结构)(数的话有多种储存结构可以实现,所以对应的算法也有很多),想到 ...

  6. C#判断操作系统类型

    操作系统  PlatformID  主版本号  副版本号  Windows95  1  4  0  Windows98  1  4  10  WindowsMe  1  4  90  WindowsN ...

  7. redis学习笔记之虚拟内存

    首先说明下redis的虚拟内存与os的虚拟内存不是一码事,但是思路和目的都是相同的.就是暂时把不经常访问的数据从内存交换到磁盘中,从而腾出宝贵的 内存空间用于其他需要访问的数据.尤其是对于redis这 ...

  8. spring-如何在项目启动的情况下获取Bean实例

    十年阿里,就只剩下这套Java开发体系了 >>>   大家都知道,项目启动的时候,spring读取xml文件,将配置的bean 或者 注解下的controller service d ...

  9. iOS开发RunnLoop学习二:GCD中的定时器

    #import "ViewController.h" @interface ViewController () /** 注释 */ @property (nonatomic, st ...

  10. iOS开发之Quartz2D 六 绘制UIImageView

    #import <UIKit/UIKit.h> @interface XMGImageView : UIView /** <#注释#> */ @property (nonato ...