欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何通俗易懂地理解gradle

程序员文章站 2022-04-29 15:15:31
...

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语言,关于更多groovy语言的可以google一下。

相关标签: gradle