如何通俗易懂地理解gradle

1.什么是Gradle

从gradle.com的userguide可以看到这样一段话:

Gradle is an open-source build automation tool focused on flexibility and performance. Gradle build scripts are written using a Groovy or Kotlin DSL. Read about Gradle features to learn what is possible with Gradle.

翻译过来就是Gradle是一个专注于灵活性和性能的开源构建自动化工具。Gradle构建脚本是用Groovy或Kotlin语言写的。Gradle是出现个人觉得还是非常革命性的,以它出现以前出存在构建工具很久了,常用的是ant,maven,做过服务端的或是早期android开发的一定对这两个不陌生,最早的时候是用ant的,它用xml文件来配置构建过程,比如:

<project name="application" default="all">
    //......
    <target name="package" depends="compile" 
        description="package the java classes into a .jar">
        <jar jarfile="${app_jar}" 
            manifest="./META-INF/MANIFEST.MF"
            basedir="${build}"/>
    </target>
    //......
</project>

ant使用一些xml标记来指一定一些构建命令或者依赖及参数,上面这个是指定了一个package任务它依赖于compile任务,它执行时用jar命令对编译生成物进行打包。而maven也是基于xml来描述依赖等,但它ant更进一步的是它使用了公共仓库依赖管理,比如一些公共库它放到maven center上进行共享,引用时只要在xml配置一下不用将它放在本地,方便了库的更新管理等。这两个工具有个共同的缺陷就是它们都是使用xml进行描述,因为xml并不是编程语言,在一些需要逻辑的构建任务中使用会非常吃力,为了能达到有if、while这样的表达能力,它们必须用xml一些特殊的标记来表示,而且使用xml会让配置文件可读性很差,没法很好地维护,为了解决这些问题(当然也为了解决其他问题),于是gradle出现了,gradle使用编程语言groovy来描述构建过程,这一举措极大地提高了编写和维护构建脚本,而且groovy语言本身非常简单易用,另外还支持了DSL,这让gradle一下得到了极大的发展,特别是在Android Studio集成了之后,因为从此做Android开发构建配置非常的简单高效。

2.Gradle原理

其实不论是什么构建工具,都有构建脚本,ant和maven用xml,gradle用groovy,运行的过程也都会有解析脚本,生成对应的执行任务然后执行。gradle有个project的概念,这个跟ant的project有点像,一次构建过程至少会包含一个project,而gradle是用task作为最小的执行单位来实现的,一个project里会有多个task,task之间形成了依赖关系,gradle在运行一次构建过程时会有三个步骤:

1.初始化:初始化阶段会建立运行构建环境和决定哪个project会参与构建,并且会为每个project创建实例
2.配置阶段:配置阶段会根据gradle脚本定义的task创建他们的依赖关系,形成一个task依赖图
3.执行阶段:执行阶段会根据task的依赖关系按顺序执行

以上是gradle的大概原理,那实际上是怎样的呢,比如脚本要怎么写,task怎么定义?要理解这些,可以先来看一个Android的构建实例,用Android Studio创建一个空的工程,可以看到几个关键的文件:

// setting.gradle
include ':app'

setting.gradle描述了构建过程包含什么project,上面的例子中是包含app这个project,在android studio中其实就是名为app的module。其中":"号是指当前目录,指定后gradle会去这个目录找对应的build.gradle文件。

// 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:3.1.2'
        

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

// 这个跟上面的不一样,是指project里的引用库,不是对脚本本身生效,是全局的
allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

上面的脚本指定了编译过程用到的依赖库,这个需要说明一下,因为gradle非常灵活自由,所以可以被用来实现很多自定义的构建,一些公共的构建逻辑放在了jcenter或maven库中,引用了才能使用相关的api,比如classpath 'com.android.tools.build:gradle:3.1.2’这一行就是指定要引用android的gradle编译的脚本库, 和全局的project的依赖

// app/buid.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "cmdmac.org.myapplication"
        minSdkVersion 22
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    packagingOptions {
        exclude 'META-INF/DEPENDENCIES'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/license.txt'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/NOTICE.txt'
        exclude 'META-INF/notice.txt'
        exclude 'META-INF/ASL2.0'
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    // AS默认配置,如果如果没有记得加上
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {exclude group: 'com.android.support', module: 'support-annotations'})
    androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
    implementation 'org.jooq:joor:0.9.6'
}

上面这个脚本是在module里面的,它定义了这个module应该怎么编译,大概意思是这个project即这个gradle文件使用com.android.application插件,这里要理解一下什么是gradle插件,gradle插件可以理解为也是编译的类库,比如里面的android包起来的东西,如果没有引用这个库是编译这个脚本时会报错,gradle已经有很多常用的插件库,比如java,groovy,android等,你也可以自己实现一个插件库来定制自己的构建逻辑。
在刚接触gradle脚本时,肯定会有点一头雾水,比如android里面用大括号包起来里面又有key-value的写法,这些其实都是groovy语言的特性,可以简单这么理解这个文件,这个文件被编译和配置后,会创建一个project的隐藏实例(有前端经验的可以理解它是document内置对象),里面这些写法都用调用这个实例的方法,比如:

android {
    compileSdkVersion 27
    defaultConfig {
        // ......
        applicationId "cmdmac.org.myapplication"
    }
}

可以理解为project.android.setCompileSdkVersion(27)和project.android.defaultConfig.setApplicationId(“cmdmac.org.myapplication”),这些都是groovy语言的DSL的写法,key-value的形式就是用DSL来表达set,这样理解之后你就会知道其实gradle脚本就是用DSL的写法调用了对应的API,而API的实现是放在gradle插件中的即脚本中看到的apply plugin: ‘com.android.application’,这类似java的import,插件的引用地址能过前面的buildscript里的repositories和dependencies指定了路径。jcenter是一个远程的仓库,可以访问http://jcenter.bintray.com/看到发布的插件库。

3.理解task和Groovy

在第一节中提到了gradle是用task作为最小执行单位的,有人可能会有点疑问,为什么只看到一个task:

task clean(type: Delete) {
    delete rootProject.buildDir
}

其实task都封装在gradle插件中了,在脚本中看不到而矣,想看到有什么task可以在命令行执行./gradlew tasks --all看到或者在Android Studio中的gradle面板中看到。上面能看到的task没有其他相关代码,说明它是一个单独的task,只有在指定执行这个task时才会被执行到。关于更多具体的task知识可以google一下,下面简单的介绍一下

Groovy语言,因为gradle脚本是用groovy语言写的的,那么就必须得再理解一下groovy,groovy是一门基于JVM的语言,怎么理解呢,因为JVM规范是公开的,因此只要实现了JVM规范就可以把语言跑在JVM上,大家知道Java语言代码是跑在JVM上的,groovy就相当于java语言,但groovy语言更强大和自由,也可以支持在groovy语言是直接使用java语言,这里再多介绍一下gradle脚本中的例子:

android {
    compileSdkVersion 27
    defaultConfig {
        // ......
        applicationId "cmdmac.org.myapplication"
    }
}

gradle脚本即是脚本也是groovy代码,上面{}括起来的其实是groovy的闭包,怎么理解呢,可以简单地理解闭包就是一段函数,在groovy中,函数是可以作为参数传进去的,上面的代码用java代码翻译一遍的话可能是这样:

project.android(new android() {
    delegate_android.compileSdkVersion(27);
    delegate_android.defaultConfig(new defaultConfig() {
    	delegate_defaultConfig.applicationId("cmdmac.org.myapplication")
    });
})

可以看到,每个{}前面可以理解为是调用一个函数,参数就是{}包起来的代码块,代码块里可以执行继续调用函数和执行代码,代码块里调用函数的对象就是{}前面定义的,我在上面的脚本上再加几行代码可能会更容易理解一点:

android {
    compileSdkVersion 27
    int a = 1
    int b = 2
    int c =  a + b
    if (c == 3) {
    	println(c)
    }
    defaultConfig {
        // ......
        applicationId "cmdmac.org.myapplication"
    }
}

在执行脚本的时候会打印出3,这样明白了吗{}里面可以就简单理解是一个函数的代码段!

总结

理解gradle最主要是理解这几个:
1.gradle脚本是groovy代码,gradle使用task作为最基本的执行单位
2.groovy代码可以兼容java代码,闭包是一段groovy函数代码段,闭包可以作为函数参数传进另一个函数
3.compileSdkVersion等各个配置项是gradle插件里的实现,可以查看相应的插件配置文档
理解了上面几个之后,只要查一下groovy语法和gradle帮助文件基本就没太大困难,如有兴趣可以查看插件的源码实现会更加深刻〜

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值