Gradle 之 Android 中的应用

在上一篇文章中 Gradle 之语言基础 Groovy 主要介绍了 Groovy 的基础语法(如果没有 Groovy 的基础,建议先看看上篇文章,如果可以动手敲一下里面的示例代码就更好不过了),也是为本篇文章打基础的。

本篇文章主要介绍 Gradle 在 Android 中的应用(Android DSL 和 Gradle DSL),也是通过一些示例来介绍和理解,主要分为以下一些内容,示例代码都在 GradleForAndroid

一. Gradle 构建生命周期

一个 Gradle 的构建通常有如下三个阶段

  • 初始化:项目 Project 实例会在该阶段被创建。如果一个项目中包含有多个模块,并且每一个模块都有其对应的 build.gradle 文件,就会为每一个模块都创建一个对应的 Project 实例
  • 配置:执行各个模块下的 build.gradle 脚本,为 Project实例创建和配置 Task,构造 Task 任务依赖关系图以便在执行阶段按照依赖关系执行 Task
  • 执行:在这个阶段将会决定执行哪个 Task,哪个 Task 被执行取决于开始该次构建的参数配置和该 Gradle 文件的当前目录

在创建完成一个新的 Android 应用项目之后,一般情况下, .gradle 文件的目录结构如下所示:

1
2
3
4
5
GradleForAndroid
|---- build.gradle
|---- setting.gradle
\---- app
\---- build.gradle

其中,两个文件 build.gradlesetting.gradle 位于项目的根目录下,还有一个 build.gradle 位于 \app\ 目录下。\build.gradle 是顶层构建文件,\app\build.gradle 是模块构建文件。
我们以上面这个新创建的项目来学习 Gradle 的构建生命周期

1.1 初始化

  1. 在初始化阶段,会创建一个 Setting 对象,对应着 setting.gradle 文件,
    Setting 对象的一个主要作用就是声明哪些模块将会参与到构建中去,Setting 文档(Gradle API 5.0)

  2. 在新建的项目中,setting.gradle 文件一般会默认包含一行内容,如下所示

    1
    include ':app'

    上面这一行,其实是一行 groovy 代码的简写,对应的是 Setting#include(String[] projectPaths) 方法,表示 :app 模块将会参与到构建中去。
    如果我们创建一个 library 库,setting.gradle 将会变为如下所示,表示 :app:library 两个模块将会参与到构建中

    1
    include ':app', ':library'
  3. setting.gradle 脚本文件可以中读取一些只可读的配置信息,这些配置信息的来源可以有如下三个:

    • 可以在本工程的 gradle.properties 文件中定义配置信息
    • 也可以在系统的 gradle.properties 文件中定义配置信息,系统的 gradle.properties 位于 user's .gradle 目录下
    • 还可以通过 -P 命令参数指定配置信息,比如 ./gradlew clean -P cmd='Hello from commandLine' 便在执行 clean task 的时候,指定了 cmd='Hello from commandLine' 配置信息
  4. 上面讲到,一个 setting.gradle 文件对应着一个 Setting 对象,Setting 对象包含的方法如下图所示



例如有如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
include ':app', ':library'
println propertiesFile
println DEFAULT_SETTINGS_FILE
println getRootProject().name
println getRootProject().path
getGradle().addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
println 'buildStarted'
}
@Override
void settingsEvaluated(Settings settings) {
println "settingsEvaluated"
}
@Override
void projectsLoaded(Gradle gradle) {
println 'projectsLoaded'
}
@Override
void projectsEvaluated(Gradle gradle) {
println 'projectsEvaluated'
}
@Override
void buildFinished(BuildResult result) {
println 'buildFinished'
}
})

其输出是:

1
2
3
4
5
6
7
8
9
10
11
Hello from gradle.properties
settings.gradle
GradleForAndroid
:
settingsEvaluated
projectsLoaded
projectsEvaluated
BUILD SUCCESSFUL in 0s
3 actionable tasks: 3 executed
buildFinished

1.2 配置

  1. 在配置阶段,会执行所有的 build.gradle,包括项目根目录下的 build.gradle 和各个 module 下的 build.gradle
  2. 在执行 build.gradle 的时候,会为每个 build.gradle 创建一个对应的 Project 对象,Project 文档(Gradle API 5.0)
  3. 配置阶段会执行 build.gradle 里面的所有代码和 Task 里面的配置代码,比如下面的 printProperties Task,只执行了 doLast{} 之外的代码,doLast{} 之外的代码是 Task 的配置代码
  4. 配置执行完成之后,会根据各个 Task 的依赖关系生成一个有向无环图,可以通过Gradle对象的getTaskGraph方法访问,对应的类为TaskExecutionGraph
  5. 在执行所有的 Gradle Task 之前,都会执行 初始化阶段配置阶段 的代码
  6. 比如有如下代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    // Top-level build file where you can add configuration options common to all sub-projects/modules.
    println "/build.gradle 开始配置"
    buildscript {
    println "/build.gradle buildscript 开始配置"
    ext.kotlin_version = '1.2.71'
    repositories {
    google()
    jcenter()
    }
    dependencies {
    classpath 'com.android.tools.build:gradle:3.0.1'
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
    }
    println "/build.gradle buildscript 结束配置"
    }
    allprojects {
    println "/build.gradle allprojects 开始配置"
    repositories {
    google()
    jcenter()
    }
    println "/build.gradle allprojects 结束配置"
    }
    task clean(type: Delete) {
    delete rootProject.buildDir
    }
    ext {
    local = 'Hello from build.gradle'
    }
    task printProperties {
    println "/build.gradle task printProperties 开始配置"
    println '/build.gradle task printProperties'
    println "/build.gradle task printProperties 结束配置"
    doLast {
    println local
    println propertiesFile
    if (project.hasProperty('cmd')) {
    println cmd
    }
    }
    }
    println "/build.gradle 结束配置"

    在 Terminal 里面执行 ./gradlew clean 会有如下输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    Hello from gradle.properties
    settings.gradle
    GradleForAndroid
    :
    settingsEvaluated
    projectsLoaded
    // 配置阶段,执行 /build.gradle 里面的代码
    > Configure project :
    /build.gradle buildscript 开始配置
    /build.gradle buildscript 结束配置
    /build.gradle 开始配置
    /build.gradle allprojects 开始配置
    /build.gradle allprojects 结束配置
    /build.gradle allprojects 开始配置
    /build.gradle allprojects 结束配置
    /build.gradle allprojects 开始配置
    /build.gradle allprojects 结束配置
    /build.gradle task printProperties 开始配置
    /build.gradle task printProperties
    /build.gradle task printProperties 结束配置
    /build.gradle 结束配置
    projectsEvaluated
    BUILD SUCCESSFUL in 0s
    3 actionable tasks: 3 executed
    buildFinished

1.3 执行

执行阶段就是指执行某个具体的任务 Task。说道 Task,我想大家应该比较熟悉,在 Android 项目中依赖了 Android Gradle 插件以后,会有许多自带的 Task,比如常用的 cleanassemble 等,而且大家更应该掌握的是如何自定义 Task,关于 Task 会单独抽一节来讲述。

二. 自定义 Task

  1. 任务 Task 代表了在构建过程中的一个单原子性的动作,比如:编程生成 .class 文件或者生成 javadoc 等.
  2. 每一个 task 都是属于某一个 Project 对象的,每一个 task 都有自己的名字,在 projecttask 的名字是唯一的,如果在整个项目 projects 范围内需要指定某个 task 的话,也需要指定 project 的名字,projecttask 的名字中间使用 : 相连接,比如:./gradlew :app:clean
  3. 创建 Task 对象的方法有以下几种

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 通过 TaskContainer.create(String) 创建 `Task`
    getTasks().create('helloTask') {
    doLast {
    println 'create Task by TaskContainer'
    }
    }
    task helloTask1 {
    doLast {
    println 'create Task by task(String name)'
    }
    }
    class HelloTask extends DefaultTask {
    def message = 'create Task by extends DefaultTask'
    @TaskAction
    def hello() {
    println message
    }
    }
    // 通过 type 参数可以指定该 task 的父类,默认的父类是 DefaultTask
    task helloTask2(type: HelloTask)
  4. 一个 Task 是由一系列的 Action 组成的,当一个 Task 执行的时候就是按照一定的顺序执行这些 Action,可以有以下两种方式向 Task 中添加 Action

    • 通过闭包 Closure 的方式添加 Action

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      class HelloTask extends DefaultTask {
      def message = 'create Task by extends DefaultTask'
      @TaskAction
      def hello() {
      println message
      }
      }
      task helloTask2(type: HelloTask) {
      doFirst {
      println 'helloTask2 doFirst'
      }
      doLast {
      println 'helloTask2 doLast'
      }
      }
    • 直接添加 Action 实例对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      def taskDef = task helloTask3(type: HelloTask)
      taskDef.doFirst(new Action<Task>() {
      @Override
      void execute(Task task) {
      println 'helloTask3 Action execute doFirst'
      }
      })
      taskDef.doLast(new Action<Task>() {
      @Override
      void execute(Task task) {
      println 'helloTask3 Action execute doLast'
      }
      })
  5. Task 依赖关系 & Task 执行顺序
    Task 中有两个很重要的概念 dependsOnmustRunAfterdependsOn 用于声明两个 Task 对象之间的依赖关系,mustRunAfter 用于声明一个 Task 必须在另一个 Task 之后运行,虽然感觉差不多,但是实际上还是有区别的

    • helloTaskB.dependsOn(helloTaskA) 中,可以单独运行 helloTaskA,但是运行 helloTaskB 的时候会触发 helloTaskA 的执行
    • helloTaskC.mustRunAfter(helloTaskA)中,helloTaskAhelloTaskC 都是可以单独运行的,但是当 helloTaskChelloTaskA 同时运行时,helloTaskC 一定会在 helloTaskA 之后运行
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      task helloTaskA {
      doFirst {
      println 'helloTaskA doFirst'
      }
      }
      task helloTaskB {
      doFirst {
      println 'helloTaskB doFirst'
      }
      }
      helloTaskB.dependsOn(helloTaskA)
      task helloTaskC {
      doFirst {
      println 'helloTaskC doFirst'
      }
      }
      helloTaskC.mustRunAfter(helloTaskA)
  6. Android 中使用自定义 Task
    在 Gradle 构建的时候,需要将自定义的 Task 添加到构建过程中时,需要把握好添加自定义 Task 的时机与位置

    • 下面一幅图清晰地展示了 Gradle 构建过程中一些关键的回调,可以在下面一些回调中添加自定义 Task
    • project 对象中,可以通过 gradle 对象得到 TaskExecutionGraph 的实例对象,也可以通过 TaskExecutionGraph 实例对象一些关键回调添加自定义的 Task。比如下面这个例子,就是在 TaskExecutionGraph 实例对象准备好之后,弹出一个 dialog 用于输入 storePasskeyPass,然后将 storePasskeyPass 设置到 android.signingConfigs
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      apply plugin: 'com.android.application'
      import groovy.swing.SwingBuilder
      //......
      gradle.taskGraph.whenReady { taskGraph ->
      if (taskGraph.hasTask(':app:assembleRelease')) {
      def storePass = ''
      def keyPass = ''
      if (System.console() == null) {
      System.setProperty('java.awt.headless', 'false')
      new SwingBuilder().edt {
      dialog(modal: true, title: 'Enter password', alwaysOnTop: true, resizable: false, locationRelativeTo: null, pack: true, show: true) {
      vbox { // Put everything below each other
      label(text: "Please enter store passphrase:")
      def input1 = passwordField()
      label(text: "Please enter key passphrase:")
      def input2 = passwordField()
      button(defaultButton: true, text: 'OK', actionPerformed: {
      storePass = input1.password;
      keyPass = input2.password;
      dispose();
      })
      }
      }
      }
      } else {
      storePass = System.console().readPassword("\nPlease enter store passphrase: ")
      keyPass = System.console().readPassword("\nPlease enter key passphrase: ")
      }
      if (storePass.size() <= 0 || keyPass.size() <= 0) {
      throw new InvalidUserDataException("You must enter the passwords to proceed.")
      }
      storePass = new String(storePass)
      keyPass = new String(keyPass)
      android.signingConfigs.release.storePassword = storePass
      android.signingConfigs.release.keyPassword = keyPass
      }
      }

如下图所示 TaskExecutionGraph 的方法结构如下图所示,都是非常实用方便的方法



三. Android DSL & Gradle DSL

3.1 Android DSL

Android DSL 是 Gradle 的一个 Android 插件,其实在使用 Android Studio 开发的时候经常会和 Android DSL 打交道,比如下面 android{ } 闭包 里面的内容都是 Android DSL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apply plugin: 'com.android.application'
import groovy.swing.SwingBuilder
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.lijiankun24.gradleforandroid"
minSdkVersion 15
targetSdkVersion 27
// ......
}
buildTypes {
// ......
}
signingConfigs {
// ......
}
}

至于里面都有哪些 API,可以去 Android DSL 文档 查看,也可以去 GitHub 上面搜 android-gradle-dsl

3.2 Gradle DSL

Gradle DSL 在上面介绍 Gradle 生命周期和自定义 Task 的时候已经介绍过了,比如上面介绍的 SettingTaskExecutionGraph 都是 Gradle DSL 中的类,其他的类和方法等 API 可以去 Gradle DSL 文档 查看,或者可以在 Android Studio 中像查看 Android SDK 源码一样去查看 Gradle DSL 的源码