Gradle 从入门到放弃

1,初识

Android 工程结构图

Gradle中,每一个待编译的工程都叫一个Project。每一个Project在构建的时候都包含一系列的Task。比如一个Android APK的编译可能包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。

2,工作流程

工作流程图

Gradle工作包含三个阶段:

  1. 首先是初始化阶段。对我们前面的multi-project build而言,就是执行settings.gradle
  2. Configration阶段的目标是解析每个project中的build.gradle。
    解析完成后,其内部的Task会被添加到一个有向图中,用于解决Task的依赖关系。
    这个阶段完成以后,整个build中的Project与内部Task的关系也映射完毕。
  3. 最后一个阶段就是执行任务了。根据所有配置完毕的Task来执行构建过程。

3,Gradle对象

Gradle主要有三种对象,这三种对象对应三种不同的脚本文件,在gradle执行的时候,会将脚本转换成对应的对象:

  • Gradle对象:当我们执行gradle xxx或者什么的时候,gradle会从默认的配置脚本中构造出一个Gradle对象。在整个执行过程中,只有这么一个对象。Gradle对象的数据类型就是Gradle。我们一般很少去定制这个默认的配置脚本。
  • Project对象:每一个build.gradle会转换成一个Project对象。
  • Settings对象:每一个settings.gradle都会转换成一个Settings对象。

注意,对于其他 gradle 文件,除非定义了 class,否则会转换成一个实现了 Script 接口的对象。
当我们执行 gradle 的时候,gradle 首先是按顺序解析各个 gradle 文件。这里边就有所谓的生命周期的问题,即先解析谁,后解析谁。

构建的生命周期, ,然后根据Settings中的配置,创建Project对象,,根据文件内容来对project对象进行配置,

3.1 gradle 的用法

3.1.1 定义依赖

group、name、version
compile group:'com.android.support.constraint',name:'constraint-layout',version:'1.0.2'

3.1.2 扩展属性

setting.gradle

ext {

android = [compileSdkVersion: 25,
           buildToolsVersion: "25.0.1",
           applicationId    : "com.dotc.trendyvip",
           minSdkVersion    : 15,
           targetSdkVersion : 21,
           versionCode      : 3]

dependencies = [
        appcompatv7        : 'com.android.support:appcompat-v7:25.0.1',
        supportv4          : 'com.android.support:support-v4:25.0.1',
        cardview           : 'com.android.support:cardview-v7:25.0.1',
        design             : 'com.android.support:design:25.0.1',
        recyclerview       : 'com.android.support:recyclerview-v7:25.0.1',
        eventbus           : 'org.greenrobot:eventbus:3.1.1',
        gson               : 'com.google.code.gson:gson:2.7',
        firebase           : 'com.google.firebase:firebase-messaging:10.0.1',

        butterknife        : 'com.jakewharton:butterknife:8.8.1',
        annotationProcessor: 'com.jakewharton:butterknife-compiler:8.8.1',
        volley             : 'com.mcxiaoke.volley:library:1.0.19',
        bugly              : 'com.tencent.bugly:crashreport:latest.release',

        analyticssdk       : 'com.dotc.sdk:analytics-lite:1.0.2.0110.1502',
        greendao           : 'org.greenrobot:greendao:3.2.2',
        glide              : 'com.github.bumptech.glide:glide:3.7.0',
        glidevolley        : 'com.github.bumptech.glide:volley-integration:1.4.0@aar'
]
}

build.gradle

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')

    compile rootProject.ext.dependencies.appcompatv7
    compile rootProject.ext.dependencies.supportv4
    compile rootProject.ext.dependencies.design
    compile rootProject.ext.dependencies.recyclerview
    compile rootProject.ext.dependencies.cardview

    compile rootProject.ext.dependencies.eventbus

    compile rootProject.ext.dependencies.gson

//    compile rootProject.ext.dependencies.butterknife
//    annotationProcessor rootProject.ext.dependencies.annotationProcessor

    compile rootProject.ext.dependencies.bugly
    compile rootProject.ext.dependencies.analyticssdk
    compile rootProject.ext.dependencies.greendao
    // glide
    compile rootProject.ext.dependencies.glide
    compile rootProject.ext.dependencies.glidevolley

    compile rootProject.ext.dependencies.volley

    compile rootProject.ext.dependencies.firebase
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
}

3.1.3 更改打包名

          applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    if (output.zipAlign) {
                        def file = output.outputFile
                        def flavor = variant.productFlavors[0].name;
                        def fileName = file.name.replace("java-" + flavor + "-release.apk", "ime" + "-" + flavor + "-v" + "${variant.mergedFlavor.versionName}" + "-r" + svnRevision() + ".apk")
                        output.outputFile = new File(file.parent, fileName)
                    }

                    def file = output.packageApplication.outputFile
                    def flavor = variant.productFlavors[0].name;
                    def fileName = file.name.replace("java-" + flavor + "-release.apk", "ime" + "-" + flavor + "-v" + "${variant.mergedFlavor.versionName}" + "-r" + svnRevision() + ".apk")
                    output.packageApplication.outputFile = new File(file.parent, fileName)
                }
            }

3.1.4.Excluding Dependencies

compile ('com.dotc.sdk:allinonesdk:3.0.0.0602.578') {
    exclude group:'com.dotc.sdk', module: 'adsdk'
    exclude group:'com.dotc.sdk', module: 'innersdk'
}

3.1.5.productFlavors

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
        }
    } 
}

3.1.6.apk签名

    signingConfigs {

        releaseSignConfig {
            storeFile file('./trendyvip.key')
            storePassword 'trendyvip'
            keyAlias 'trendyvip'
            keyPassword 'trendyvip'
        }
    }

3.1.7.sourceSets

sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src', 'java-inputmethodcommon/src', 'java-overridable/src', 'gen-javame', 'thrift']
            resources.srcDirs = ['src', 'java-inputmethodcommon/src', 'java-overridable/src']
            aidl.srcDirs = ['aidl']
            renderscript.srcDirs = ['src', 'java-inputmethodcommon/src', 'java-overridable/src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
            jniLibs.srcDirs = ['libs']  //自动编译lib下面的jar包
        }

        dev.res.srcDirs = ['flavors/flash/res']
        dev.assets.srcDirs = ['flavors/flash/assets']

        play.res.srcDirs = ['flavors/flash/res']
        play.assets.srcDirs = ['flavors/flash/assets']

        tap_dev.res.srcDirs = ['flavors/tap/res']
        tap_dev.assets.srcDirs = ['flavors/tap/assets']

        tap_play.res.srcDirs = ['flavors/tap/res']
        tap_play.assets.srcDirs = ['flavors/tap/assets']

    }
}

3.1.8.引用aar

repositories {  
    flatDir {  
        dirs 'libs'  
    }  
} 

// 具体引用:
compile(name: 'facebook-android-sdk-4.5.0', ext: 'aar')

4,Task简介

一个project中Task的数量,是由编译脚本制定的插件决定。插件是什么呢?插件就是用来定义 Task,并具体执行这些 Task 的东西。

Gradle 是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译 Java 有 Java 插件,编译 Groovy 有 Groovy 插件,编译 Android APP 有 Android APP 插件,编译 Android Library 有 Android Library 插件

apply plugin: 'com.android.library'    <== 如果是编译 Library,则加载此插件 
apply plugin: 'com.android.application'  <== 如果是编译 Android APP,则加载此插件

除了加载二进制的插件(jar 包),还可以加载 gradle 文件,如

apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"  

5,Groovy入门

一个Task包含若干Action。Task有doFirst和doLast两个函数,用于添加需要最先执行的Action和需要最后执行的Action。Action就是一个闭包。对于原有的Task,我们可以在其执行之前或者执行之后,进行一系列的Hook操作,在其执行之前和执行之后,添加一些操作。例:

tasks.getByName("task"){  
   it.doLast{  
       println "do the task"
     }  
}  

Java程序员可以无缝切换到使用Groovy开发程序。Groovy内部会将其编译成Java class然后启动虚拟机来执行。

Groovy调用Java

groovy 调用 Java class 十分方便,只需要在类前导入该 Java 类,在 Groovy 代码中就可以无缝使用该 Java 类

Java调用Groovy

1)方法1:直接调用

IDEA/Eclipse 开发环境下,通过安装相应的 groovy 解释器插件,既可以在 Java 代码中直接调用 groovy,在这种方式下,编译器会自动将groovy类编译为class后,再由该Java类调用

public class Test {
    public static void main(String[] args){
        GroovyDemo demo = new GroovyDemo("assad");
        System.out.println(demo.sayHello());
    }
}
2)方法二:反射动态调用

通过反射的方式调用groovy类,当groovy脚本修改后,无需重新编译,自动执行,实现groovy脚本的动态调用

 public class Test {
    public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException {
       
        //获取 groovy 类的反射对象
        ClassLoader parent = getClass().getClassLoader();  
        GroovyClassLoader loader = new GroovyClassLoader(parent);  
        Class groovyClass = loader.parseClass(new File("demo/GroovyDemo.groovy"));  
        
        //实例化 GroovyDemo 类
        GroovyObject groovyObj = (GroovyObject) groovyClass.newInstance("assad");  
        //调用 groovyDemo 成员方法
        System.out.println( groovyObj.invokeMethod("sayHello",null));
    }
}

6,Groovy闭包

闭包,是一种数据类型,它代表了一段可执行的代码。其外形如下:

def aClosure = {// 闭包是一段代码,所以需要用花括号括起来..  
    Stringparam1, int param2 ->  // 这个箭头很关键。箭头前面是参数定义,箭头后面是代码  
    println"this is code" // 这是代码,最后一句是返回值,  
   // 也可以使用 return,和 Groovy 中普通函数一样  
}  

简而言之,Closure 的定义格式是:

def xxx = {paramters -> code}  // 或者  
def xxx = {无参数,纯 code}  这种 case 不需要 -> 符号

说实话,从 C/C++ 语言的角度看,闭包和函数指针很像。闭包定义好后,要调用它的方法就是:

闭包对象.call(参数)  

或者更像函数指针调用的方法:

闭包对象 (参数)  

比如:

aClosure.call("this is string",100)  
// 或者  
aClosure("this is string", 100)  

如果闭包没定义参数的话,则隐含有一个参数,这个参数名字叫 it,和 this 的作用类似。it 代表闭包的参数。比如:

def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
// 等同于
def greeting = { it -> "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'

注意:

  • Groovy 中,当函数的最后一个参数是闭包的话,可以省略圆括号。
  • Closure 的参数需要查文档

7,Gradle 配置定义

配置属性

Gradle 提供了一种名为extra property的方法。extra property是额外属性的意思,在第一次定义该属性的时候需要通过 ext 前缀来标示它是一个额外的属性。定义好之后,后面的存取就不需要 ext 前缀了。ext 属性支持 Project 和 Gradle 对象。即 Project 和 Gradle 对象都可以设置 ext 属性,如:

def initMinshengGradleEnvironment(){  
    // 属性值从 local.properites 中读取  
    Properties properties = new Properties()  
    File propertyFile = new File(rootDir.getAbsolutePath() +"/local.properties")  
    properties.load(propertyFile.new DataInputStream())  
    //gradle 就是 gradle 对象。它默认是 Settings 和 Project 的成员变量。可直接获取  
    //ext 前缀,表明操作的是外置属性。api 是一个新的属性名。前面说过,只在  
    // 第一次定义或者设置它的时候需要 ext 前缀  
    gradle.ext.api =properties.getProperty('sdk.api')  
     
    println gradle.api  // 再次存取 api 的时候,就不需要 ext 前缀了  
    ......  
    } 

Project 和 自定义.gradle 对应的 Script 的关系是

  • 当一个 Project apply 一个 gradle 文件的时候,这个 gradle 文件会转换成一个 Script 对象。
  • Script 中有一个 delegate 对象,这个 delegate 默认是加载(即调用 apply)它的 Project 对象。但是,在 apply 函数中,有一个 from 参数,还有一个 to 参数(参考图 31)。通过 to 参数,你可以把 delegate 对象指定为别的东西。
  • 在 Script 中操作一些不是 Script 自己定义的变量,或者函数时候,gradle 会到 Script 的 delegate 对象去找,看看有没有定义这些变量或函数。
    所以,对于加载 自定义.gradle 的project。 有几个project,就会加载到几个project中去。其中缺省的扩展属性是添加到相应的project中了,这样可以直接拿到里面的方法, 如
tasks.getByName("assemble"){  
   it.doLast{  
       println "$project.name: After assemble, jar libs are copied tolocal repository"  
        copyOutput(true)  //copyOutput 是 utils.gradle 输出的 closure  
     }  
}  
settings.gradle
include ':app'

用于指示 Gradle 在构建应用时应将哪些模块包括在内。
settings.gradle 除了可以 include 外,还可以设置一些函数。这些函数会在 gradle 构建整个工程任务的时候执行,可以在 settings 做一些初始化的工作。如:

// 定义一个名为 initMinshengGradleEnvironment 的函数。该函数内部完成一些初始化操作 
// 比如创建特定的目录,设置特定的参数等 
def initMinshengGradleEnvironment(){  
    println"initialize Minsheng Gradle Environment ....."  
    ......// 干一些 special 的私活....  
    println"initialize Minsheng Gradle Environment completes..."  
}  
//settings.gradle 加载的时候,会执行 initMinshengGradleEnvironment  
initMinshengGradleEnvironment()  
//include 也是一个函数:  
include 'CPosSystemSdk' , 'CPosDeviceSdk' ,  
      'CPosSdkDemo','CPosDeviceServerApk','CPosSystemSdkWizarPosImpl' 
build.gralde

实例:

// 下面这个 subprojects{}就是一个 Script Block  
subprojects {  
  println"Configure for $project.name" // 遍历子 Project,project 变量对应每个子 Project  
  buildscript {  // 这也是一个 SB  
    repositories {//repositories 是一个 SB  
       ///jcenter 是一个函数,表示编译过程中依赖的库,所需的插件可以在 jcenter 仓库中  
       // 下载。  
       jcenter()  
    }  
    dependencies { //SB  
        //dependencies 表示我们编译的时候,依赖 android 开发的 gradle 插件。插件对应的  
       //class path 是 com.android.tools.build。版本是 1.2.3  
        classpath'com.android.tools.build:gradle:1.2.3'  
    }  
   // 为每个子 Project 加载 utils.gradle 。当然,这句话可以放到 buildscript 花括号之后  
   applyfrom: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"  
 }//buildscript 结束  
}  

起始 subprojects 是一个函数,然后其参数是一个 Closure。
下面来解释以下各个Script Block :

  • subprojects:它会遍历 posdevice 中的每个子 Project。在它的 Closure 中,默认参数是子 Project 对应的 Project 对象。由于其他 SB 都在 subprojects 花括号中,所以相当于对每个 Project 都配置了一些信息。
  • buildscript:它的 closure 是在一个类型为 ScriptHandler 的对象上执行的。主意用来所依赖的 classpath 等信息。在 buildscript SB 中,你可以调用 ScriptHandler 提供的 repositories(Closure )、dependencies(Closure) 函数。
(Library)build.gradle

Android Library 编译出来的应该是一个 AAR 文件。但是由于有的项目有些特殊,需要发布 jar 包给其他人使用。jar 包在编译过程中会生成,但是它不属于 Android Library 的标准输出。在这种情况下,就需要在编译完成后,主动 copy jar 包到目标目录中。

//Library 工程必须加载此插件。注意,加载了 Android 插件就不要加载 Java 插件了。因为 Android  
// 插件本身就是拓展了 Java 插件  
apply plugin: 'com.android.library'   
//android 的编译,增加了一种新类型的 ScriptBlock-->android  
android {  
       // 你看,我在 local.properties 中设置的 API 版本号,就可以一次设置,多个 Project 使用了  
      // 借助我特意设计的 gradle.ext.api 属性  
       compileSdkVersion =gradle.api  // 这两个红色的参数必须设置  
       buildToolsVersion  = "22.0.1"  
       sourceSets{ // 配置源码路径。这个 sourceSets 是 Java 插件引入的  
       main{ //main:Android 也用了  
           manifest.srcFile 'AndroidManifest.xml' // 这是一个函数,设置 manifest.srcFile  
           aidl.srcDirs=['src'] // 设置 aidl 文件的目录  
           java.srcDirs=['src'] // 设置 java 文件的目录  
        }  
     }  
   dependencies {  // 配置依赖关系  
      //compile 表示编译和运行时候需要的 jar 包,fileTree 是一个函数,  
     //dir:'libs',表示搜索目录的名称是 libs。include:['*.jar'],表示搜索目录下满足 *.jar 名字的 jar  
     // 包都作为依赖 jar 文件  
       compile fileTree(dir: 'libs', include: ['*.jar'])  
   }  
}  //android SB 配置完了  
//clean 是一个 Task 的名字,这个 Task 好像是 Java 插件(这里是 Android 插件)引入的。  
//dependsOn 是一个函数,下面这句话的意思是 clean 任务依赖 cposCleanTask 任务。所以  
// 当你 gradle clean 以执行 clean Task 的时候,cposCleanTask 也会执行  
clean.dependsOn 'cposCleanTask'  
// 创建一个 Task,  
task cposCleanTask() <<{  
    cleanOutput(true)  //cleanOutput 是 utils.gradle 中通过 extra 属性设置的 Closure  
}  
// 前面说了,我要把 jar 包拷贝到指定的目录。对于 Android 编译,我一般指定 gradle assemble  
// 它默认编译 debug 和 release 两种输出。所以,下面这个段代码表示:  
//tasks 代表一个 Projects 中的所有 Task,是一个容器。getByName 表示找到指定名称的任务。  
// 我这里要找的 assemble 任务,然后我通过 doLast 添加了一个 Action。这个 Action 就是 copy  
// 产出物到我设置的目标目录中去  
tasks.getByName("assemble"){  
   it.doLast{  
       println "$project.name: After assemble, jar libs are copied tolocal repository"  
        copyOutput(true)  
     }  
}  
/* 
  因为我的项目只提供最终的 release 编译出来的 Jar 包给其他人,所以不需要编译 debug 版的东西 
  当 Project 创建完所有任务的有向图后,我通过 afterEvaluate 函数设置一个回调 Closure。在这个回调 
  Closure 里,我 disable 了所有 Debug 的 Task 
*/  
project.afterEvaluate{  
    disableDebugBuild()  
}  

小Tip:

task myTask  <<  {
   println ' I am myTask'
}

如果代码没有加 <<,则这个任务在脚本initialization(也就是你无论执行什么任务,这个任务都会被执行,I am myTask都会被输出)的时候执行,如果加了<<,则在 gradle myTask 后才执行。

你可能感兴趣的:(Gradle 从入门到放弃)