構建工具Gradle

1.Summary

  從Android團隊開始宣佈放棄Eclipse轉投Android Studio時,構建工具Gradle進入了Android開發者的視野。而隨着熱修復、插件化、編譯時註解的流行,深刻了解Gradle就變得頗有必要了。那麼什麼是Gradle ?html

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相關知識。java

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框架,只要理解框架中插件的建立,任務的定義就能夠了。react

  說了這麼多不如來個代碼感覺一下。android

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的配置。git

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

  通常DSL造成的框架都要有足夠完整的API,由於其專業性太強了,就是所謂的行話,通常人看不懂,要經過查百科才能明白。
  若是你想要根據公司的業務添加一些代碼的話,那麼就須要咱們寫任務或者插件,而這須要咱們熟悉Gradle框架和Groovy語言了。程序員

Gradle框架

  咱們都知道Gradle的生命流程要經歷三個部分:初始化、配置、執行。github

  • 初始化階段: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 Referenceweb

  爲了驗證流程,我在本身的項目SimpleWeather中添加以下log。sql

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

相關文章
相關標籤/搜索