Gradle 是一款基于Apache Ant 和Apache Maven概念的功能强大的项目自动化构建工具,它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各类繁琐配置。Gradle 对多工程的构建支持很出色,还能够根据特定需求开发自定义插件来解决特定问题,所以掌握Gradle 核心技术能够提升咱们的开发效率,本文是对Gradle 核心之一Task的相关属性、执行流程、自定义任务等进行介绍。html
咱们的全部Gradle的构建工做都是由Task组合完成的,它能够帮助咱们处理不少工做。每次构建(build)至少由一个project构成,一个project由一个至多个task构成。每一个task表明了构建过程中的一个原子性操做,好比编译、打包、发布等等这些操做。在Gradle环境下能够经过命令./gradlew tasks 查看当前工程全部的task。java
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组。
TaskContainer为咱们提供了两个方法,findByPath() 和 getByPath () 。经过查看官方文档能够知道,这两个方法均可以接收task 的名字,相对路径,绝对路径做为参数来查找对应的具体的Task。区别仅在于方法的返回值,findByPath()若是查找不到会直接放回null,而getByPath ()查找不到则会抛出UnknownTaskException异常,因此使用getByPath ()时要用try catch配合使用。注:在gradle低版本还提供过findByName() 、getByName() 方法。不过如今已经不提供这两个方法了,都经过findByPath() 和 getByPath ()来实现。
当执行一个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.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 方法会自动打破闭环,不会报错。
在此以前先介绍下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的核心代码。
Task中有个enabled属性,用于启用和禁用任务,默认是true,表示启用,设置为false则禁止该任务,在执行阶段该任务会被跳过。在实际项目中是很使用的一个技巧,如提高build编译速度,禁止一些测试相关的Task,从而缩短执行时间。
断言就是一个条件表达式。Task有一个onlyIf方法,它接受一个闭包做为参数,若是该闭包返回为true则该任务执行,不然跳过。在实际项目中有不少用途,好比控制程序在哪些状况下打包,何时执行单元测试等。
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!
焦俊楠,民生科技有限公司用户体验技术部开发工程师。