写给 Android 开发者的 Gradle 系列(一)基本姿势

本文基于 Android Gradle plugin 3.0.1

建议前往掘金阅读

Gradle 介绍

笔者认为能够戳进这篇文章的读者十之八九也是知道 Gradle 可以用来干什么,所以没必要介绍什么了,毕竟说一堆术语搞得大家都不懂很难堪(手动滑稽)。简单来说,对于大部分的 Android 开发者来说 Gradle 是一个强大的工具,它提供便捷的方式帮助开发者构建 app。如果想看一下比较丰富的介绍的话可以查看如何通俗地理解 Gradle?

如何学习 Gradle

接下来笔者会出一系列关于 Gradle 文章,但是授人鱼不如授人以渔 ——

  • Gradle 基于 groovy 语言,groovy 官方文档链接戳我。当然,对于类似笔者这种比较懒的程序员来说一般会选择搜一些中文文章来看,如附录中的Gradle从入门到实战 - Groovy基础。好消息是 groovy 与 java 相同是基于 jvm,所以理解起来并不是那么困难,且对于日常开发来说,真的不需要学习多少内容。

  • Gradle DSL 学习。新建一个 Android 项目,可以看到 project/build.gradle 文件中的内容类似如下:

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.0.1'
        }
    }
    allprojects {
        repositories {
            jcenter()
        }
    }
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    

    如果你想知道 buildscriptallprojects 的含义就应该戳开上面的链接了。例如文档中说到 allprojects 是用于配置当前 project 和所有子 project 的,该方法将会在这些 project 中执行给定的闭包,那么上述代码闭包中的 repositories 的意义同样是可以在文档中找到的。

  • Android Plugin DSL 学习。新建一个 Android 项目,可以看到 project/app/build.gradle 文件中的内容类似如下:

    apply plugin: 'com.android.application'
    android {
        compileSdkVersion 27
        defaultConfig {
            applicationId "com.joker.cliptest"
            minSdkVersion 21
            // ...
        }
        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:27.1.1'
        // ...
    }
    

    那么像 buildTypes 的意义是什么就可以在上述文档中找到了。

  • Gradle task 学习。在前文提到的 project/build.gradle 文件中最后有一个

    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    

    其实就是一个 task,关于 task 的更多姿势在后一节中阐述。

前面共有四点,而且看起来每一点都需要花一定的时间去学习,其实不然,等到问题遇到了再去查文档就好了,尤其是前面提到的两点 DSL 学习,其实日常开发中就已经能够理解其中的含义了,说白了它们其实就是一些闭包、一些固定格式,正是因为它们的格式是固定的,task 才能够读取到相应的数据完成相应的事情,所以各位读者大不必害怕 Gradle 学习成本高。

Gradle task

日常开发中,上一节提到的四点中,Gradle task 与大部分的开发者开发是最为紧密的。日常开发中开发者难免会进行 build/clean project、build apk 等操作 ——

这里写图片描述

实际上这些按钮的底层实现都是通过 Gradle task 来完成的,只不过 IDE 使用 GUI 降低开发者们的使用门槛。当然,如果各位读者足够细心观察的话,是能够看到点击相应按钮后在 Build 输出栏中会输出相应的信息 ——

这里写图片描述

其实说到这里各位读者理应对 task 有一个基本的概念,一个 task 就是一个函数,没有什么神秘的地方。想要知道当前 Android 项目中共有哪些 task,可以在项目目录下输入:

./gradlew tasks(Mac、Linux)

gradlew tasks(Windows,后文同)

将会输出类似以下信息:

这里写图片描述

这仅是部分 task,如果想要查看全部的 task 可以添加 --all 参数:

./gradlew tasks --all

拉到最后的 Other task 部分:

这里写图片描述

可以看到所有的 task,可能有经验的读者更是感觉到亲切,提取部分 task 说明下:

  • compileDebugJavaWithJavac:编译 java 文件
  • processDebugManifest:生成最终 AndroidManifest 文件
  • compileDebugAidl:编译 AIDL 文件
  • packageDebug:打包成 apk

而如果为 release 包开启了混淆,可看到还有 transformClassesAndResourcesWithProguardForRelease task,即为 release 包混淆。

这些 task 实际上贯穿了开发者们的日常开发流程,只是 IDE 在上层封装了一层,开发者们点点按钮就完成了这些操作。当然也有读者会疑惑,这些 task 从哪里来的?为什么没有在 build.gradle 中看到蛛丝马迹?这些 task 都是 plugin 中的,在 build.gradle 顶部 apply 的 plugin 中的(com.android.application/com.android.library)。在后续的文章中笔者将会介绍如何找到这些 task 的源码、如何阅读它们、如何写 task、如何写 plugin 以及解析较为知名的 gradle 插件源码,这些也正是 Android 开发者学习 gradle 的目的所在。

Gradle 构建周期

根据Build phases 文档Settings file 文档可以知道,Gradle 构建周期分为:

  • Initialization:Gradle支持单个和多项目构建。在 Initialization 阶段,Gradle 将会确定哪些项目将参与构建,并为每个项目创建一个 Project 对象实例。对于 Android 项目来说即为执行 setting.gradle 文件。那么日常开发中 setting.gradle 文件是如何书写的呢,假设当前应用中除 app 以外还有一个 a module 和 b module,那么 setting.gradle 文件应类似如下:

    include ':app', ':a', ':b'
    

    所以 Gradle 将会为它们三个分别创建一个 Project 对象实例。

  • Configuration:在这一阶段项目配置对象,所有项目的构建脚本将会被「执行」,这样才能够知道各个 task 之间的依赖关系。需要说的一点是,这里提到的「执行」可能会稍有一些歧义:

    task a {
    }
    task testBoth {
        // 依赖 a task 先执行
        dependsOn("a")
        println '我会在 Configuration 和 Execution 阶段都会执行'
        doFirst {
          println '我仅会在 testBoth 的 Execution 阶段执行'
        }
        doLast {
          println '我仅会在 testBoth 的 Execution 阶段执行'
        }
    }
    

    写在 task 闭包中的内容是会在 Configuration 中就执行的,例如上面的 dependsOn("a")println 内容;而 doFirst {}/doLast {} 闭包中的内容是在 Execution 阶段才会执行到(doFirst {}/ doLast {} 实际上就是给当前 task 添加 Listener,这些 Listeners 只会在当前 task Execution 阶段才会执行)。

    建议各位读者将这个 task 复制黏贴到 app 的 build.gradle 中,然后观察点击 Android Studio 的 Sync 按钮(含 Configuration 阶段)和在命令行输入 ./gradlew app:testBoth 时命令行(testBothExecution 阶段)输出的结果。便可理解上述闭包内容的执行过程。当然你也可以查看 Settings file 中的案例来理解。

    对于 Android 项目来说即为执行各个 module 下的 build.gradle 文件,这样各个 build.gradle 文件中的 task 的依赖关系就被确认下来了(例如 assembleDebug task 的执行依赖于其他 tasks 先执行,而这个依赖关系的确定就是在 Configuration 阶段)。

  • Execution:task 的执行阶段。首先执行 doFirst {} 闭包中的内容,最后执行 doLast {} 闭包中的内容。

在命令行中执行某一个 task,是可以清晰地看见每一个执行流程:

这里写图片描述

这里写图片描述

这里写图片描述

hook Gradle 构建过程

在命令行输入 ./gradlew xxx 并按下回车之后的执行过程中,就会执行上面的流程,再通过一张深入理解Android之Gradle中的图加深印象——

这里写图片描述

然而在笔者目前为止碰到的 Gradle 开发中,很少提及到上述的 hook 系列。用的比较多的一个是前文提到的 task 中的 doFirst {}/ doLast {},再一个就是 Project 在 Configuration 阶段结束的 hook,前文也提到**Configuration 阶段将会执行每一个 Project 的 build.gradle 文件**,那么如何监听这每一个 Project 的引入后的点呢?通过 Project.afterEvaluate ——

//afterEvaluate { Project project ->
//  println 'hook afterEvaluate'
//}
task hook { Project project ->
  afterEvaluate {
    println 'hook afterEvaluate'
  }
}

那么为什么要 hook Project 的 afterEvaluate 阶段呢?因为在 afterEvaluate 阶段的时候,当前 Project 内的 task 信息才能被掌握,例如想在 assembleDebug task 前输出一段信息,那么在 app/build.gradle 中撰写以下代码:

// task badHook {
//   tasks.findByName("assembleDebug").doFirst {
//     println 'hook afterEvaluate from BadHook'
//   }
// }
task assDHook {
  afterEvaluate {
    tasks.findByName("assembleDebug").doFirst {
      println 'hook afterEvaluate from assHook'
    }
  }
}

此时如果调用 assembleDebug task 的话,首先是 Initialization 阶段,再是 Configuration 阶段,该阶段中当 appProject 到达 afterEvaluate 阶段的时候 appProject 中的 tasks 信息将会全部被获取,当然这其中也包括 assembleDebug task,所以此时再给它添加一个 doFirst {} 闭包便可以达到目的。而像 badHook 闭包的内容将会在 Initialization 阶段执行,此时 appProject 并没有获取全部的 task 信息,将会导致压根找不到 assembleDebug 的错误。

后文

本文中并没有总结太多的基础知识,基本都以外链的形势写在了文中,因为笔者认为现在已经有大量的文章写的很不错了,所以不想大费笔墨撰写,笔者挑了一些自认为较重要的点为后文做铺垫,另外,文中还有大量的外链是外链到官方文档的,所以建议各位读者在遇见问题的时候多多查阅文档。各位读者可以在下文的附录中阅读更为基础的 Gradle 知识:

附录

我建了一个群,如果你对文章有什么疑问,或者想和我讨论 Android 技术,欢迎入群哈。