如何通俗易懂地理解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語言,關於更多groovy語言的可以google一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章