美文网首页
Gradle 从入门到放弃

Gradle 从入门到放弃

作者: 莫库施勒 | 来源:发表于2019-02-19 19:47 被阅读0次

    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 文件。这里边就有所谓的生命周期的问题,即先解析谁,后解析谁。

    构建的生命周期,\color{red}{首先在初始化阶段根据 settings.gradle 文件构建出一个 Settings 对象} ,然后根据Settings中的配置,创建Project对象,\color{red}{然后在配置阶段去找各个 project 下的build.gradle 文件},根据文件内容来对project对象进行配置,\color{red}{同时在执行阶段生成 gradle 对象}

    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 从入门到放弃

          本文链接:https://www.haomeiwen.com/subject/znrfyqtx.html