Gradle Task原理与实践


前言:

Gradle 是一款基于Apache Ant 和Apache Maven概念的功能强大的项目自动化构建工具,它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各类繁琐配置。Gradle 对多工程的构建支持很出色,还能够根据特定需求开发自定义插件来解决特定问题,所以掌握Gradle 核心技术能够提升咱们的开发效率,本文是对Gradle 核心之一Task的相关属性、执行流程、自定义任务等进行介绍。html


Task


咱们的全部Gradle的构建工做都是由Task组合完成的,它能够帮助咱们处理不少工做。每次构建(build)至少由一个project构成,一个project由一个至多个task构成。每一个task表明了构建过程中的一个原子性操做,好比编译、打包、发布等等这些操做。在Gradle环境下能够经过命令./gradlew tasks 查看当前工程全部的task。java

1. Task的建立和配置

Task建立:Gradle 有多种建立task的方式,这里只介绍常见的两种方式。android

1. 直接使用task函数建立,实际上是调用Project对象中的task(String name)的方法。 git

2. 经过task容器TaskContainer对象的create()方法进行建立。github

Task 配置:上面两种建立方式其实都有对应的重载方法,能够传入具体的配置参数来进行Task初始化配置。windows

配置项api

描述闭包

默认值函数

type工具

基于一个存在的Task来建立

DefaultTask

overwrite

是否替换存在的task,配合type使用

false

dependsOn

用于配置task的依赖

[ ]

action

添加到task中的一个Action或者一个闭包

null

description

用于配置task的描述

null

group

用于配置task的分组

null

name

用于配置task的名称

null

Task 能够经过重载方法进行参数指定,也能够经过闭包的形式进行配置。

这里的group 配置的分组方便task快速定位以及后期维护,如不指定将默认分配到other组。


2. 获取Task的方式

TaskContainer为咱们提供了两个方法,findByPath() 和 getByPath () 。经过查看官方文档能够知道,这两个方法均可以接收task 的名字,相对路径,绝对路径做为参数来查找对应的具体的Task。区别仅在于方法的返回值,findByPath()若是查找不到会直接放回null,而getByPath ()查找不到则会抛出UnknownTaskException异常,因此使用getByPath ()时要用try catch配合使用。注:在gradle低版本还提供过findByName() 、getByName() 方法。不过如今已经不提供这两个方法了,都经过findByPath() 和 getByPath ()来实现。


3. Task的执行

当执行一个Task的时候,其实就是执行其拥有的actions列表,这个列表保存在Task对象实例中的actions成员变量中,其类型就是一个List。Task本质上又是由一组被顺序执行的Action对象构成,Action实际上是一段代码块,相似与Java中的方法。这里主要介绍Task建立Action的两个方法,doFirst与doLast。doFirst{} 可使代码在Gradle 的执行阶段中Task以前执行,而doLast{}则偏偏相反,是在Task以后执行。

经过查看Task的源码能够知道,doFirst{}就是在Task存放actions的List第一位添加,保证其添加的Action在现有actions List元素的最前面;doLast{}是在actions List末尾添加,从而实现Task中actions顺序执行的目的。

  • Tips:“<<” 操做符在Gradle 的Task上是doLast方法的短标记形式,也就是说“<<” 能够替代doLast。


4. Task执行顺序


4.1 Task的依赖

dependsOn 是task 配置参数之一,主要做用就是为task 添加依赖task ,保证task 之间的执行顺序。经过下面的测试代码来理解一下:

输出结果:

咱们执行了./gradlew taskC ,而在上面咱们给taskC指定了依赖taskB, taskB的执行又依赖于taskA 。因此执行结果为taskA -> taskB -> taskC。


4.2 输入输出

TaskInputs:

Task的输入类,参数能够接收为任意对象以及文件、文件夹。

TaskOutputs:

TaskOutputs files ( );

TaskOutputs file ( );

TaskOutputs dir ( );

Task的输出类,只接收文件类型。


4.3 API指定执行顺序

Task的shouldRunAfter 方法和mustRunAfter方法能够控制一个Task应该或者必定在某个Task以后执行。经过这种方式能够在某些状况下控制任务的执行顺序,而不是经过强依赖的方式。

taskB.shouldRunAfter(taskA)表示taskB应该在taskA执行以后执行,这里并非强制的,因此有可能任务顺序并不会按预设的执行。

taskB.mustRunAfter(taskA)表示taskB必须在taskA执行以后执行,执行任务的顺序是确认的。

  • 注:使用mustRunAfter方法,若是出现Task之间依赖语法矛盾,依赖关系造成闭环,编译器会报错。而shouldRunAfter 方法会自动打破闭环,不会报错。


5. 挂接自定义Task到构建过程当中

在此以前先介绍下Gradle 的生命周期:

A. 初始化阶段(Initialization)

初始化阶段gradle会去解析项目根工程中setting.gradle中的include信息,肯定哪些工程加入构建。

B. 配置阶段(Configuration)

配置阶段将解析全部工程的build.gradle脚本,配置project对象,建立、配置task等相关信息。

C. 执行阶段(Execution)

根据具体的gradle命令,执行对应相关的task以及其依赖的task。

而实际项目中咱们将要挂接的正是gradle的执行阶段,由于只有在配置阶段完成,执行阶段gradle才会去执行系统默认task 以及自定义task 。project为咱们提供了这样的方法project.afterEvaluate{}。

project.afterEvaluate{} 在配置完成后,能够保证获取到全部的task,包括系统默认执行的task,这样就能够将咱们自定义的task 经过顺序执行指定,挂接到构建过程当中。实际开发中,通常咱们都会对系统的assembleRelease任务进行挂载,先来了解一下assembleRelease的内部执行顺序:

:preBuild

:preReleaseBuild

:checkReleaseManifest

:prepareReleaseDependencies

:compileReleaseAidl

:compileReleaseNdk

:compileReleaseRenderscript

:generateReleaseBuildConfig

:generateReleaseResValues

:generateReleaseResources

:mergeReleaseResources

:processReleaseManifest

:processReleaseResources

:generateReleaseSources

:incrementalReleaseJavaCompilationSafeguard

:javaPreCompileRelease

:compileReleaseJavaWithJavac

:extractReleaseAnnotations

:mergeReleaseShaders

:compileReleaseShaders

:generateReleaseAssets

:mergeReleaseAssets

:mergeReleaseProguardFiles

:packageReleaseRenderscript

:packageReleaseResources

:processReleaseJavaRes

:transformResourcesWithMergeJavaResForRelease

:transformClassesAndResourcesWithSyncLibJarsForRelease

:mergeReleaseJniLibFolders

:transformNativeLibsWithMergeJniLibsForRelease

:transformNativeLibsWithSyncJniLibsForRelease

:bundleRelease

:compileReleaseSources

:assembleRelease

  • 注:上面代码基本上列举了Gradle构建Android项目时执行assembleRelease的全部Task,Lint、Test等非必需Task除外。

下面咱们将结合腾讯开源的热修复方案Tinker的源码来分析将自定义task挂接构建过程的原理。

Tinker GitHub连接 https://github.com/Tencent/tinker

上图为tinker的工程目录,与构建相关的代码都在tinker-build 这个module里,咱们主要分析的是里面的TinkerPatchPlugin.groovy文件的代码。


TinkerPatchPlugin是一个自定义插件类,主要是来实现对热修复目标工程的配置相关文件的操做。

咱们主要分析的代码片断为下图,在project.afterEvaluate{} 方法中遍历获取到全部的Android变体,而后建立了一个自定义任务TinkerManifestTask ,经过当前的变体拿到AndroidManifest.xml文件的路径,并传给了TinkerManifestTask ,接着经过mustRunAfter 指定TinkerManifestTask 在构建过程当中assembleRelease当中:processReleaseManifest任务以后执行,从而达到在AndroidManifest.xml追写tinker相关的配置信息。

TinkerManifestTask 中具体将tinker配置信息写入AndroidManifest.xml的核心代码。


6. Task的启用与禁用

Task中有个enabled属性,用于启用和禁用任务,默认是true,表示启用,设置为false则禁止该任务,在执行阶段该任务会被跳过。在实际项目中是很使用的一个技巧,如提高build编译速度,禁止一些测试相关的Task,从而缩短执行时间。


7. Task的onlyIf断言

断言就是一个条件表达式。Task有一个onlyIf方法,它接受一个闭包做为参数,若是该闭包返回为true则该任务执行,不然跳过。在实际项目中有不少用途,好比控制程序在哪些状况下打包,何时执行单元测试等。


8. 默认Task

Gradle 为咱们提供了不少默认的task来进行使用,下面是Gradle 官网文档上对copy Task 的使用提供的例子。除了copy,还有delete、upload、zip等许多使用的task。更多具体用法能够去阅读Gradle 的官方文档: https://docs.gradle.org/current/dsl/org.gradle.api.Task.html



实践

ndk编译,在Android中编译生成动态库有两种方式:

第一种是经过Android.mk脚本执行ndk-build命令来进行手动编译。这种状况下,能够经过自定义task来实现自动化编译ndk。

这里咱们自定义的task要指定type 为Exec类型,Exec task 能够经过commandLine方法执行windows、Linux环境下的命令,经过预先设置好ndk 编译的命令,经过task自动执行来达到目的。(上图为windows 环境下的执行代码)

第二种经过cmake 进行编译。

在android studio 2.2及以上,构建原生库的默认工具是CMake。CMake是一个跨平台的构建工具,能够用简单的语句来描述全部平台的安装(编译过程)。可以输出各类各样的makefile或者project文件。Cmake 并不直接建构出最终的软件,而是产生其余工具的脚本(如Makefile ),而后再依这个工具的构建方式使用。它能够根据不一样平台、不一样的编译器,生成相应的Makefile或者vcproj项目。从而达到跨平台的目的。Android Studio利用CMake生成的是ninja,ninja是一个小型的关注速度的构建系统。咱们不须要关心ninja的脚本,知道怎么配置cmake就能够了。从而能够看出cmake实际上是一个跨平台的支持产出各类不一样的构建脚本的一个工具。

如今的ndk开发中基本上都是用CMake,而以前经过Android.mk脚本方式基本已经废弃,这里简单介绍下CMake相关的配置。在使用Android Studio 建立工程时,选择Native C++ ,AS便会自动为咱们生成ndk 开发相关的文件以及配置。

而生成的这些文件中须要咱们关注主要文件即是CMakeLists.txt。

在配置好CMakeLists.txt,不须要单独执行编译命令,由于gradle 已经默认在build task 中预置了NDK编译相关的操做。因此只要咱们正常对工程编译,即可在/build/intermediates/cmake/目录下找到生成的动态库。


Thanks!


做者介绍

焦俊楠,民生科技有限公司用户体验技术部开发工程师。

相关文章
相关标签/搜索