掌控 Android Gradle

写在前面

目前国内对Android领域的探索已经越来越深,不少技术领域如插件化、热修复、构建系统等都对Gradle有迫切的需求,不懂Gradle将无法完成上述事情。所以Gradle必须要学习。



对本文有任何问题,可加我的个人微信:kymjs123



Gradle 里的几乎任何东西都是基于这两个基础概念:

  • task

  • project

  • 掌握了这两个,你就掌握了一大半的 Gradle 知识了。

    首先讲 Task

    字面理解为任务,Gradle 中所有执行的事件都是借由 Task 执行的。

    例如我们新建一个 Android 工程,在其根目录中输入:

    gradle

    tasks -q

    可以看到如下输出(你可能需要事先配置gradle的环境变量,或也可使用./gradlew替代):

    掌控 Android Gradle

    根据上图可以看到当前工程中的每条task都已罗列出,并且有黄色的输出表示当前task的描述。

    其中-q表示忽略gradle本身的log信息,加上这个参数可以屏蔽很多无关的输出,不加也不会影响执行。

    Task声明格式

    声明一个 task 只需要在任务名前面加上task就可以了,例如下面声明了一个hello的Task。

    task

    hello

    通常我们会给task附带一些执行动作,称之为Action,例如

    hello.doFirst{println

    "hello first"

    }hello.doLast{println

    "hello last"

    }

    也可以附带一个闭包配置,称之为Configuration,闭包中不仅可用做赋值操作,也可以执行一些自动执行的配置。

    hello

    {

    println

    "hello"

    }Task依赖

    单独声明一个task在实际开发中几乎不会有任何的意义,更多的时候是让多个task组合起来,一个依赖另一个,形成一连串的任务集。

    task

    hellohello.doFirst{

    println

    "hello "

    }task world(dependsOn:

    "hello"

    ) << {

    println

    "world"

    }

    上面这段代码定义了两个task,当我们执行hello任务的时候,会输出 hello,而执行world任务的时候,由于声明了dependsOn: "hello",表示world依赖hello,会先执行hello,再执行world。

    task

    xxx

    <<

    {

    }

    这样的语法等价于

    task

    xxx

    xxx

    .dolast

    {

    }

    你可以在任意位置新建一个名为build.gradle的文本,来练习上面讲述的task定义与依赖。

    掌控 Android Gradle

    接着讲 Project

    Android

      │   ├──app   │   └──build.gradle   │   ├──library   │   └──build.gradle   │   ├──

    *.properties

      │   ├──build.gradle   │   └──setting.gradle

    一个 Android 工程,通常是由上述结构构成,其中有着许多不为人知的巧妙用法。

    setting.gradle文件

    关于setting.gradle中也可以写代码,是很多人不知道的。如下代码是我在上一篇文章【企业级 Android 模块化平台设计建议】中讲到的一个例子,在setting.gradle文件中,可以指定一个project位置,这里就可以将一个外部工程中的模块导入到APP工程中了。

    getLocalProperties().entrySet().

    each

    { entry ->    def moduleName = entry.key    

    if

    (Boolean.valueOf(entry.value)) {        def file = new File(rootProject.projectDir.parent,

    "/

    ${moduleName.replace(

    "\\W"

    ,

    ""

    )}

    /

    ${moduleName.toLowerCase()}

    "

    )        

    if

    (file.

    exists

    ()) {            include

    ":

    ${moduleName.toLowerCase()}

    "

               project(

    ":

    ${moduleName.toLowerCase()}

    "

    ).projectDir = file        }    }}build.gradle

    一个项目的根gradle文件,用于描述这个项目的统一资源,其中包括各子资源的使用方式、插件的依赖环境等等。

    subprojects{    apply plugin:

    "com.android.library"

       dependencies {      compile

    "com.xxx.xxx:xxx:1.0.0"

      }}

    通常我们在每个模块都会引用的 aar 的时候,都会在每个模块里面都去手动的compile一遍,例如support包。 但实际上有一个非常简单的办法,写一遍就可以了,就是在项目的根gradle文件中的subprojects闭包中声明这个dependencies。

    通常在写compile依赖的时候,我们都会写成这样:

    compile

    "com.android.support:appcompat-v7:25.0.0"

    其实在gradle中,这是一个方法调用,它的本质是compile()方法传入了一个map参数,因此完整的写法实际上是这样的:

    compile

    group

    :

    "com.android.support"

    name:

    "appcompat-v7"

    version:

    "25.0.0"

    同时,map 的可使用 key 不只是有常用的group、name、version,还包括不常用的configuration、classifier等等。

    再看Task

    Groovy 是基于 Java 的,只不过在这基础上加了一大堆的闭包,来帮助更方便的开发构建脚本。如果你不会 Groovy,没关系,当成 Java 写就行了,其实当成 Kotlin 写是最恰当的。如果你还不会 Kotlin,我强烈推荐你查看我的 【 Kotlin Primer 】系列文章

    每个Task都可以配置其输入与输出,如果一个Task的输出与上一次的输出一致,则不会重复执行。此刻,会在命令行中输出UP-TO-DATE表示已经是最新的结果。

    例如如下Task:

    task transform {    ext.srcFile = file(

    "hello.txt"

    )    ext.destDir =

    new

    File(buildDir,

    "generated"

    )    inputs.file srcFile    outputs.dir destDir    doLast {        destDir.mkdirs()        def ins =

    new

    BufferedReader(

    new

    FileReader(srcFile))        def stringBuilder =

    new

    StringBuilder()        def temp        

    while

    ((temp = ins.readLine()) !=

    null

    ) {            stringBuilder.append(temp)        }        def destFile =

    new

    File(destDir,

    "world.txt"

    )        destFile.text = stringBuilder.toString()    }}

    重复执行后会输出UP-TO-DATE

    掌控 Android Gradle

    骚操作的背后

    学习任何一门技术,最快的途径就是看源码,gradle的源码位于src目录中,例如在我本机的路径为: 

    /Users/zhangtao/.gradle/wrapper/dists/gradle-3.3-all/55gk2rcmfc6p2dg9u9ohc3hw9/gradle-3.3/src

    本地新建一个普通Java工程,导入源码查看代码与注释,这是最好的学习资料。

    task

    hello

    在 Groovy 中,方法括号可以省略,如果字符串的类型是可以被推断的,那么引号也可以省略

    public

    interface

    org

    .

    gradle

    .

    api

    .

    Project

    {

    Task

    task

    (String name)

    ;

    Task

    task

    (String name, Closure configureClosure)

    ;}

    // TaskFactory

    public

    TaskInternal

    createTask

    (Map<String, ?> args)

    {}

    闭包的存在,目的是为了更好的为对象初始化。同 Kotlin 一样,当闭包做为最后一个参数的时候,可以省略括号。

    Copy a = task(myCopy, type: Copy)a.

    from

    "resources"

    a.

    into

    "target"

    a.include(

    "**/*.txt"

    ,

    "**/*.xml"

    ,

    "**/*.properties"

    )

    等价于

    task

    myCopy

    (type: Copy)

    myCopy

    {  

    from

    "resources"

     

    into

    "target"

      include(

    "**/*.txt"

    ,

    "**/*.xml"

    ,

    "**/*.properties"

    )}

    本章就讲到这里,下一篇讲如何创建一个Gradle插件,完成编译时向指定类或新生成类中动态添加代码(包括jar包中)。