Gradle 之 Android 中的应用

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

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

原文在此:Gradle 之 Android 中的应用

一. Gradle 构建生命周期

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

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

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

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 文件一般会默认包含一行内容,如下所示

    include ':app'
    复制代码

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

    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 对象包含的方法如下图所示

    例如有如下代码

    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'
        }
    })
    复制代码

    其输出是:

    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. 比如有如下代码
    // 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 会有如下输出
    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 对象的方法有以下几种
    // 通过 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
      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 实例对象
      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 之后运行
    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
    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

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 的源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值