构建工具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