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框架,只要理解框架中插件的创建,任务的定义就可以了。

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

  1. apply plugin: 'com.android.application'
  2. android {
  3. compileSdkVersion 23
  4. buildToolsVersion "27.0.0"
  5. defaultConfig {
  6. applicationId "com.hawksjamesf.myapplication"
  7. minSdkVersion 14
  8. targetSdkVersion 26
  9. versionCode 1
  10. versionName "1.0"
  11. testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  12. }
  13. buildTypes {
  14. release {
  15. minifyEnabled false
  16. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  17. }
  18. }
  19. }
  20. dependencies {
  21. implementation fileTree(include: ['*.jar'], dir: 'libs')
  22. implementation 'com.android.support:appcompat-v7:23.0.0'
  23. implementation 'com.android.support.constraint:constraint-layout:1.0.2'
  24. // testImplementation 'junit:junit:4.12'
  25. androidTestImplementation 'com.android.support.test:runner:1.0.1'
  26. androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
  27. }

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

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

  1. buildTypes {
  2. debug{
  3. println 'haha'
  4. }
  5. release {
  6. minifyEnabled false
  7. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  8. }
  9. }

  一般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

  1. println("settings start")
  2. include ':app', ':location'
  3. println("settings end")
  4. //include ':viewpagerindicator_library'

SimpleWeather/build.gradle

  1. // Top-level build file where you can add configuration options common to all sub-projects/modules.
  2. println("root project start")
  3. buildscript {
  4. repositories {
  5. jcenter()
  6. google()
  7. }
  8. dependencies {
  9. classpath 'com.android.tools.build:gradle:3.0.1'
  10. //
  11. // NOTE: Do not place your application dependencies here; they belong
  12. // in the individual module build.gradle files
  13. }
  14. }
  15. allprojects {
  16. repositories {
  17. jcenter()
  18. google()
  19. }
  20. }
  21. task clean(type: Delete) {
  22. delete rootProject.buildDir
  23. }
  24. ext{
  25. compileSdkVersion=26
  26. buildToolsVersion ='27.0.0'
  27. minSdkVersion =17
  28. targetSdkVersion =26
  29. versionCode=2
  30. versionName="2.0"
  31. }
  32. println("root project end")

SimpleWeather/app/build.gradle

  1. println("app start")
  2. apply plugin: 'com.android.application'
  3. android {
  4. compileSdkVersion rootProject.ext.compileSdkVersion
  5. buildToolsVersion rootProject.ext.buildToolsVersion
  6. defaultConfig {
  7. minSdkVersion rootProject.ext.minSdkVersion
  8. targetSdkVersion rootProject.ext.targetSdkVersion
  9. versionCode rootProject.ext.versionCode
  10. versionName rootProject.ext.versionName
  11. multiDexEnabled = true
  12. applicationId "com.hawksjamesf.simpleweather"
  13. testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  14. ndk {
  15. // 设置支持的SO库架构
  16. abiFilters 'x86_64'
  17. abiFilters 'x86'
  18. abiFilters 'armeabi-v7a'
  19. abiFilters 'arm64-v8a'
  20. }
  21. }
  22. buildTypes {
  23. release {
  24. minifyEnabled false
  25. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  26. buildConfigField("String", "BASE_URL", '"https://weatherapi.market.xiaomi.com"')
  27. }
  28. debug {
  29. // buildConfigField ("String","BASE_URL",'"https://api.caiyunapp.com/"')
  30. buildConfigField("String", "BASE_URL", '"https://weatherapi.market.xiaomi.com"')
  31. }
  32. }
  33. lintOptions {
  34. // abortOnError false
  35. }
  36. productFlavors {
  37. }
  38. }
  39. dependencies {
  40. implementation fileTree(include: ['*.jar'], dir: 'libs')
  41. // implementation project(':viewpagerindicator_library')
  42. implementation 'com.jakewharton:butterknife:8.8.1'
  43. annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
  44. implementation 'com.google.dagger:dagger:2.11'
  45. annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
  46. implementation 'com.squareup.retrofit2:retrofit:2.3.0'
  47. implementation 'com.squareup.retrofit2:retrofit-converters:2.3.0'
  48. implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
  49. implementation 'com.squareup.retrofit2:adapter-rxjava2:2.+'
  50. implementation 'com.squareup.okhttp3:okhttp:3.8.1'
  51. implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
  52. // implementation 'com.jakewharton.timber:timber:4.5.1'
  53. implementation 'com.orhanobut:logger:2.1.1'
  54. implementation 'com.google.code.gson:gson:2.8.2'
  55. implementation 'org.greenrobot:eventbus:3.0.0'
  56. implementation 'io.reactivex.rxjava2:rxjava:2.+'
  57. implementation 'com.tencent.bugly:crashreport:2.6.6.1'
  58. implementation 'com.tencent.bugly:nativecrashreport:3.3.1'
  59. // Test helpers for Room
  60. testImplementation 'android.arch.persistence.room:testing:1.0.0'
  61. // Room (use 1.1.0-alpha1 for latest alpha)
  62. implementation 'android.arch.persistence.room:runtime:1.0.0'
  63. annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
  64. // RxJava support for Room
  65. implementation 'android.arch.persistence.room:rxjava2:1.+'
  66. // implementation "com.android.support:multidex:1.0.1"
  67. //noinspection GradleCompatible
  68. implementation 'com.android.support:appcompat-v7:26.1.0'
  69. implementation 'com.android.support:design:26.1.0'
  70. implementation 'com.android.support:recyclerview-v7:26.1.0'
  71. compile project(path: ':location')
  72. }
  73. println("app end")

SimpleWeather/location/build.gradle

  1. println("lib location start")
  2. apply plugin: 'com.android.library'
  3. android {
  4. compileSdkVersion rootProject.ext.compileSdkVersion
  5. buildToolsVersion rootProject.ext.buildToolsVersion
  6. defaultConfig {
  7. minSdkVersion rootProject.ext.minSdkVersion
  8. targetSdkVersion rootProject.ext.targetSdkVersion
  9. versionCode rootProject.ext.versionCode
  10. versionName rootProject.ext.versionName
  11. testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  12. }
  13. buildTypes {
  14. release {
  15. minifyEnabled false
  16. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  17. }
  18. }
  19. }
  20. dependencies {
  21. implementation fileTree(dir: 'libs', include: ['*.jar'])
  22. implementation 'com.android.support:appcompat-v7:26.1.0'
  23. testImplementation 'junit:junit:4.12'
  24. androidTestImplementation 'com.android.support.test:runner:1.0.1'
  25. androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
  26. }
  27. println("lib location end")

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

  1. [0] % ./gradlew clean
  2. Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details
  3. settings start
  4. settings end
  5. > Configure project :
  6. root project start
  7. root project end
  8. > Configure project :app
  9. app start
  10. Configuration 'compile' in project ':app' is deprecated. Use 'implementation' instead.
  11. app end
  12. > Configure project :location
  13. lib location start
  14. lib location end

Gradle任务

配置阶段调用


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

下面是定义task的三种方式

  1. task(hello) {
  2. println "config hello"
  3. }
  4. task('hello') {
  5. println "config hello"
  6. }
  7. tasks.create(name: 'hello') {
  8. println "config hello"
  9. }

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

  1. task hello {
  2. println "config hello"
  3. }
  4. tasks.create('hello') {
  5. println "config hello"
  6. }

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

执行阶段调用


看代码。

  1. task hello {
  2. println "config hello"
  3. doLast {
  4. println "excute hello"
  5. }
  6. }
  7. hello.doLast {
  8. println "excute hello"
  9. }
  10. hello.leftShift {
  11. println "excute hello"
  12. }
  13. hello << {
  14. println "excute hello"
  15. }

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

  1. task hello {
  2. println "config hello"
  3. doLast {
  4. println "excute hello"
  5. }
  6. }
  7. hello << {
  8. println "excute hello"
  9. }

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

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

来看个代码。

  1. task clean(type: Delete) {
  2. delete rootProject.buildDir
  3. }
  4. task copy(type: Copy) {
  5. from 'resources'
  6. into 'target'
  7. include('**/*.txt', '**/*.xml', '**/*.properties')
  8. }

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

  1. task myCopy(type: Copy)
  2. myCopy {
  3. from 'resources'
  4. into 'target'
  5. include('**/*.txt', '**/*.xml', '**/*.properties')
  6. }

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

任务相关性


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

用代码说话

  1. 1.
  2. project('projectA') {
  3. task taskX(dependsOn: ':projectB:taskY') {
  4. doLast {
  5. println 'taskX'
  6. }
  7. }
  8. }
  9. project('projectB') {
  10. task taskY {
  11. doLast {
  12. println 'taskY'
  13. }
  14. }
  15. }
  16. 2.
  17. task taskX {
  18. doLast {
  19. println 'taskX'
  20. }
  21. }
  22. task taskY {
  23. doLast {
  24. println 'taskY'
  25. }
  26. }
  27. taskX.dependsOn taskY

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

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

  1. task taskX {
  2. doLast {
  3. println 'taskX'
  4. }
  5. }
  6. taskX.dependsOn {
  7. tasks.findAll { task -> task.name.startsWith('lib') }
  8. }
  9. task lib1 {
  10. doLast {
  11. println 'lib1'
  12. }
  13. }
  14. task lib2 {
  15. doLast {
  16. println 'lib2'
  17. }
  18. }
  19. task notALib {
  20. doLast {
  21. println 'notALib'
  22. }
  23. }

除了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

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

  1. class GreetingPlugin implements Plugin<Project> {
  2. void apply(Project project) {
  3. project.task('hello') {
  4. doLast {
  5. println 'Hello from the GreetingPlugin'
  6. }
  7. }
  8. }
  9. }
  10. // Apply the plugin
  11. 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. [转]MySQLHelper类

    本文转自:http://de.cel.blog.163.com/blog/static/5145123620110181003903/ 类似于SQLHelper,只是这里引用的是MySql.Data类 ...

  2. 2013腾讯编程马拉松初赛第〇场(HDU 4504)威威猫系列故事——篮球梦

    http://acm.hdu.edu.cn/showproblem.php?pid=4504 题目大意: 篮球赛假如我们现在已经知道当前比分 A:B,A代表我方的比分,B代表对方的比分,现在比赛还剩下 ...

  3. Playing with coroutines and Qt

    你好!我最近想知道C ++中的协程的状态,我发现了几个实现.我决定选择一个用于我的实验.它简单易用,适用于Linux和Windows. 我的目标是试图找到一种方法来让代码异步运行,而不必等待信号触发插 ...

  4. Web安全之Cookie劫持

    1. Cookie是什么? 2. 窃取的原理是什么? 3. 系统如何防Cookie劫持呢? 看完这三个回答, 你就明白哪位传奇大侠是如何成功的!!! Cookie: HTTP天然是无状态的协议, 为了 ...

  5. HDU 1996汉诺塔VI

    题目: n个盘子的汉诺塔问题的最少移动次数是2^n-1,即在移动过程中会产生2^n个系列.由于 发生错移产生的系列就增加了,这种错误是放错了柱子,并不会把大盘放到小盘上,即各柱 子从下往上的大小仍保持 ...

  6. 10.10 android输入系统_APP获得并处理输入事件流程

    APP对fd/InputChannel的注册过程: new WindowInputEventReceiver extends InputEventReceiver//InputEventReceive ...

  7. ECMAScript5和ECMAScript6_浏览器支持情况

    ECMAScript5浏览器支持情况: Opera 11.60 Internet Explorer 9* Firefox 4 Safari 5.1** Chrome 13 * IE9不支持严格模式 - ...

  8. trident原理及编程指南

    目录 trident原理及编程指南 一.理论介绍 1.trident是什么? 2.trident处理单位 3.事务类型 二.编程指南 1.定义输入流 2.统计单词数量 3.输出统计结果 4.split ...

  9. position:absolute和margin:auto 连用实现元素水平垂直居中

    有时候,要实现一些元素水平垂直都居中,这部分元素呢 可能大小未知,例如一些图片或者是一些未知大小的块元素. 利用绝对定位可以将要居中的元素脱离文档流. position: absolute; left ...

  10. Unreal Enginer4特性介绍-牛B闪闪的UE4

    声明:转载说明出处! unreal4特性介 原文地址:   https://www.unrealengine.com/products/unreal-engine-4     unreal engin ...