前言

成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。

从明面上看,Gradle 是一款强大的构建工具,而且许多文章也仅仅都把 Gradle 当做一款工具对待。但是,Gradle 不仅仅是一j U y款强大的构建工具,它看起来更像是一个编程框架。Gradle 的组成可以细分为如下三个方面

  • 1)、groovy 核心语法包括 groovy 基本语法、闭包、数据结构、面c c n { , { 8 a I向对象等等
  • 2)、Android DSL(build scrpit block)Android 插件在 Gradle 所特有的东西,我们可以在不同的 build scrpit bloc& ) – v Fk 中去做不同的事情
  • 3)3 * O 6 a # aGradle API包含 Prx / S qoject、Task、Setting 等等(本文重点)

可以看到,Gradle 的语法是以 groK E ! # fovy 为基础的,而且,它还有自己独有的 API,所以我们可以把 Gradle 认作是一款编程框架,利用 Gradle 我们可以q A r H在编8 0 %程中去实现项目构建过程中的所有需求。需要注意的是,想要随心所W S P c ? I i : :欲地使用 Gradle,我们必须提前掌握好 groovy,如果对 groovy 还不是很熟悉的建议看看 《深入探索Gradle自动化构建e : a x技术(二、Groovy 筑基篇)》 一文。

需要注意的是,Groovy 是一门语言,而 DSL 一种特定领域的配置文件,Gradle 是基于 Groovy 的一种框架工具,而 gradls I . t Gew 则是 gradle 的一g Z o ; P a f V个兼容包装工具。

一、p H P )Gradle 优势

1、更好的灵活性

在灵活性t : $ 3 8上,Gradle 相对于 Maven、Ant 等构建工具, 其 提供了一系列的 API 让我们有能力去修改或定制项目的构建过程。例如我们可以 利用 Gradle{ 6 s a 去动态修改生成的 APK 包名,但是如果是使用的 Mavend K a ^ T、Ant 等工具,我们就必须等生成 APK 后,再手动去修改 APK 的名称。

2、更细的粒度

在粒度性上,使用 Maven、Ant 等构建工Y a具时,我们的源代码和构建脚本是独立的,而且我们也不知道其内部的处理是怎样的。但是,我们的 Gradle 则不同,它 从源代码的编译、资源的编译、再到生成 APK 的过程中都是一个F x , : u D S接一个来执行的

此外,Gradle 构建的粒度细化到了每一个 task 之中。并且它所有的 Task 源码都是开源的,~ f q D #在我们掌握了这一整套打包流程后,我们就可以通过去修改它的 Task 去动态% N Y Y改变其执行流程。例如 Tinker 框架的实现过程中,F Q _ . =它通过动态地修改 Gradle 的打包过程生成 APK 的同时,也生成了各种补丁文件。

3、更好的扩展性

在扩展性上,Gradle 支持插V L s件机制,所以9 2 x K我们可以复用这些插件,就如同复用库一样简单方便

4、更强的兼容性

Gradle 不仅自身功能强大,而且它还能 兼容所有的 Maven、Ant 功能` j g * Z B S U,也就是说,Grad# J D 0 * z / )le 吸取了所有构建工具的长处

可以看到,Gradle 相比于其它构建工具,其好处不言而喻,而其 最核心的原因就是因为 Ga V Q e {radle 是一套编程框架

二、Gradle 构建生命周期

Gradle 的构建过程分为 三部分:初始化阶段、配置阶段和P ! z执行阶段。其构建流程如下图所示:

深度探索 Gradle 自动化构建技术(三、Gradle 核心解密)

下面分别来详细了解下它们。

1、初始化阶段

首先,在这个阶段中,^ k 4 M r会读G * S 0 & 9取根工程中的 setting, ! O.gradle 中的 include 信息,确定有多少工程加入构建,然后,会为每一个项目(build.gradle 脚本文件)创建一个个与之对应的 Project 实例,最终形成一个项目的层次结构
与初始化阶段相关的脚本文件是 settk Y ?ings.gradle,而一个 settings.gradle 脚本对应一个 Settings 对象,我们最常用来声明项目的层次结构的 include 就是 Settings 对象下的一个方法,在 Gradle 初? J : I ` S J %始化的时候会构造一个 Setting8 B n 1 a 3 es 实例对象,以执行各个 Project 的初始化配置

settings.gradle

settings.gradle 文件中,我们可以 在 GradlG B – ` G } g N Ze 的构建过程中添加各个生命t H U Z周期节点监听,其代码如下所示:

includew  p Z $ ? I':app'
gradle.addBuildListener(newBuildListener(){
voidbuildStarted(Gradlevar1){
println'开始构建'
}
voidsettingsEvaluated(Settingsvar1){
/I % A ,/var1.F ` 3 { C - } Ngradle.rootProject这里访问Project对象时会报错,
//- Y | Q ~ k l * C因为还未完成Project的初始化。
println'settings评估完成(settings.gradle中代码执行完毕)'
}
voiS N ; 2 E c ^ g adprojectsLoaded(Gradlevar1){
println'项目结构加载f G b p v W完成(初始化阶段结束)= d W'
priny g l v 4 i ` %tln'初始化结束,$ @ w A @ C J可访问根项目:'+var1.gradl1 b . / f c De.rootProject
}
voidpri 2 J ( 1 Y TojectsEvaluated(Gradlevar1){
println'所有项目评估完成(配置阶段结束)'
}
voidbuildFinish% J [ r : 3ed(BuildReV # B ; l L E 0 lsultvar1){
println'构建结束'
}
})

编写完相应的 Gradle 生命周期监听代码之后h G p s T 4 @ I,我们就可以在 Build 输出界面看到如下信息:

Executingtasks:[clean,:app:assembleSpeedDebug]inproject
/Users/quchao/Docume6 O E f N | c u Snts/main-open-project/A} a O 3 Iweb h z Dsome-We P G ,anAndroid
settings评估完成(settins.gradle中代码执行完毕)
项目结构加载完成(初始化阶段结束)
初始化结束,可访问根项C K 4 J H r目:rootproject'Awesome-WanAndroid'
ConfigA 2 [ ? = 7 purationondemandisanincubatingfea. D 4 = | 4 4ture.
>Con0 ( - # x @ { $ figur) d r B {eE T ! i r Oproject:app
gradlewversion>4.0
WARNING:API'variant.getJavaCompiZ n b ` F , L X .ler()'isobsoleteandhasbeen
replacedwith'variant.getJavaCompileProvider()'.
Itwillberemovedattheendof2019t C %.
Formoreinformation,see
https://d.anW Y 3 m ] gdro : 1 a ` t x /id.com/r/tool- 0 ? T P @s/V V M H / 9 Itask-configuration-avoidance.
Todeterminewhatiscallingvariant.getJavaCompiler(),use
-Pandroid.debug.obsoG l M I Z 4leteApi=trueonthecy n w H / ( + -ommandlinetodi+ L E x &splaymore
informatione 0 i : g M.
skiptinyPicPluginTask!!!!!!
skiptinyPicPly { p SuginTask!!!!!!
所有项目评估完成(配置阶段结束)
&[ % s , q F X .gt;Task:cleanUP-TO-DATE
:cleanspend1ms
...F y B - C q :
>Task:app:clean
:app:cleanspend2ms
>Task:app:packageSpeedDebug
:aw 7 % Q d + Yp: b m : h +p:packagM 2 a 0 , R j / eeSpeedDebugspend825ms
>Task:app:assembleSpeedDebug
:app:assembleSpeedDebugspend1ms
构建结束
Tasksspendtime>50ms:
...

此外,在 sd | S Kettings.gradle 文件中,我们可以指定其它 project 的Z O b 6 b位置,这样就E x ^ Q可以l ! ?将其它外部工程中的 moudlM ; W N le 导入到当前的工程之中了。示例代码如下所示:c g [ T # 1 1

if(useSpeechMoudle){
//导入其它App的speech语音模块r Z Q {
include"spe( i %ech"
project(":speech").projectDir=newFile("../OtherApp/speech")
}

2、配置阶段

配置阶段的任务是 执行各项目下的 build.gradle 脚本,完成 Project 的配置,与此同时,会构造 Task 任务依赖关系图以便在执行j G f Y C ?阶段按照依赖关系执行 Task。而在配置阶段执行的代码通常来说都会包括以下三个部分的内容,如下所示:

  • 1)、build.gralde 中的各种语句
  • 2)、闭包
  • 3)、Task 中的配置段语句

需要注意的是,执行任何 Gradle 命令,在初始化阶段和配置阶段的x B E 4代码都会被执行

3、执行阶段

在配置阶段结束后,Gradle7 S 4 , / T J 会根据各个任务 Task 的依赖关系来创建一个有向无环图,我们可以通过 GradlX ^ : 4 je 对象的 getTaskGraph 方法来得到该有向无环图 => TaskExecutionGraph,并且,当有向无环图构建完成之后,所有 Task 执行之前,我们可以通过 whenReady(groovy.lang.Closure) 或者 addTaskExecu5 C D 7 v _ :tionGraphListener(TaskE] / V h M f 9xecutionGraphListener) 来接收相应的通知,其代码如Y ? 6 P 1 x K ~下所示:

gradle.getTaskGraph().addTaskExecutionGraphLi0 z Y 5 U g (stener(new
TaskExecutionGraphListener(){
@Override
voidgraph( : q nPopulated(TaskExecutionGraphgraph){
}
})

然后,Gradle 构建系统会通过调用 graE # @ f D 9 wdle <任U 8 O c {务名> 来执行相应的各个任务。

4、Hook Gradle 各个生命周期节点

这里借用 Goe_H 的 Gradle 生命周期时序图来讲解一下 Gradle 生命周期的整个流程,如下图所示:

深度探索 Gradle 自动化构建技术(三、Gradle 核心解密)

可以看到,整个 Gradle 生命周期的流程包含如下 四个部分

  • 1)、首先,解析 settings.gradle 来获取模+ 4 V u d a W块信息,这是初始化阶段
  • 2)、然后,配置每个模块,配置的时候并不会执行 task
  • 3)、接着,配置完1 . Y X d了以后,有一个重要的回调 project.afterE# m @ O i vvaluate,它表示所r R . W P有的模块都已经配置完了,可以准备执行 task 了5 v _ @ D 7
  • 4)、最后,执行指定的 task 及其依赖的 task

在 Gradle 构建命令中,最为复杂的命令可以说是 gradle build 这个命令了,因为项目的构建过程中需要依赖很多其它的 task。这里,我们以 Java 项目的构建过程看看它所依赖的 tasks 及其组成的有向无环图,如下所示:

深度探索 Gradle 自动化构建技术(三、Gradle 核心解密)

注意事项

  • 1)、每一个 Hook 点对应的监听器一定要在回调的生命周期之前添加
  • 2)、如果注册了多个 projz v E f N Eect.afterEvaluate 回调,那么执行顺序将与注册顺序保持一致

5、获取构建各个阶段、任务的耗时情况

o W {解了 Gradle 生命周期中的各个 Hook 方法之后,我们就可以 利用它们来获取项目构建各个阶段、任务的耗时情况A p # M +,在 settings.gradle 中加入如下代码即可:

loD : 6 R N n KngbeginOfSetting=System.currentTimeMillis()
defbeginOfConfig
dn { CefconfigHasBegin=false
defbeginOfProjectConfig=newHashMap()
defbeginOfProjectExcute
gradle.projectsLoaded{
println'初始化阶段,耗时:'+(System.curren$ = ]tTimeMillis(Q S W H [ H O)-
be` I { b v B L ] !ginOX Q J 7 % S * E UfSetting)+'ms'
}
gradle.beforeProject{project-&g- u b 7 L 4 8 6t;
if(!configHasBegin){
configHasBegin=true
beginOfConfig=S( D @ M G _ eyst| } r + * $em.currentTimeMil[ : 2 : ` d c *lis()
}
beginOfProjectConfig.put(prn m J toject,Sy5 N Y r e ^ w a Fstem.c6 ^ z N X currentTimeMillis()h s W I)
}
gradle.afterProject{project->
defbegin=beginOfProjectConfig.get(project)
println'配置阶段,'+project+'耗时:'+
(SystL e kem.currentTimeMillis()-J L n Xbegin)+'ms'
}
gradle.taskGraph.whenRs 2 4 &eady{
println'配置阶段,总共耗时:'+(System.currentTimeMilli5 S 2 5 ^ % u _s()-
beginOfConfig)+'ms'
beginOfProjectExcute. b i - b F t=System.currentTimeMilli$ M p i a & 2s()
}
gradle.taskGraph.beforeTas i r 6 t Sk{task->
task.doFirst{
task.ext.beginOfTask=System.currentTimeMillis()
}
task.doLast{
println'执$ $ . Y O j k U行阶段,'+task+'耗时:'+
(System.currentTimeMillis()-task.beginOfTask)+'ms'
}r q ~
}
gradle.buildFinished{
println'执行阶V : B O Z # H段,耗时:'+(System.currentTimeMillis()-
beginOfProjectExcute)+'ms'
}

在 Gradle 中,执行每一种类型的配置脚本就会创建与之对应的实例,而在 Gradle 中如 三种类型D @ J ~ # 7 f x _的配置脚本,如下所示:

  • 1)、Build Scrpit对应一个 Project 实例,V M 3 I e E _ = ,即每个 build.g6 Y p [ t ` yradle 都会转换成一个 Project 实例
  • 2)、Init Scrpit对应一个 Gr` 9 n # Tadle 实例,它在构建初始化时创建,Q J = 9 0整个构建执行过程中以单例形式存在
  • 3)、Settings Scrpit对应一个 Settings 实例,即每个 settings.gradle 都会转换成一个 Settings 实例

可以看到,一个 Gradle 构建流程中会由一至多个 project 实例构成,而每一个 project 实例又是由一至多个 task 构成。下面,我们就来认识下 Project。

三、Project

Project 是 Gradle 构建整个应用程序的入口,所以它非常重要,我们必须对其有深刻地了解。不幸的是,网上几乎没有关于 projec : % * kct 讲) d H o ? ~解的比较好的文章,不过没关系,下面,我们将会一! = r p起来深入学习 projec[ o A gt ap~ a s a g G 6 ii 这部分。

由前可知,每一个 build.gradle 都有一个与之对应的 Project 实例,而在 build.gradle 中,我们通常都 5 & o S 0会配置一系列的项 2 2 + u K [ –目依赖,如下面这个依赖:[ ^ + W

implementation'com.github.bumptech.glide:glide:4.` W s v `  3 `8.Q D t ] Q =0'

类似于 implementation、api 这种依赖关键字,在本质上它就是一个方法~ 5 ] H U a – [ 2调用,在上面,我们使用 implementation() 方法传入了/ } 3 q . m . Y一个 map 参数,参数里面有三对 keB m b ? w & ` &y-valuC s , re,完整写法如下所示:

implementationgroup:'com.github.bumptech.glide'nJ O ! - uame:'glide'vF  Q ; E ^ ersion:'4.8.! 9 0'

当我们使用 implementation、api 依赖对应的 aar 文件时,Gradle; D Z o 6 ~ w J – 会在 repository 仓库 里面找到与之对应的依赖文件,你的仓库中可能包含 jcenter、maveF D M v :n 等一系列仓库,而每一7 6 d个仓库其实就是很多依赖文件的集j L Q e合服务器, 而他们就是通过上述U @ ? ) x * B [ Z的 group、name、version 来进行归类存储的

1、Proju . – } d O 0 N Dect 核心 API 分解

在 Project7 9 R 中有很多的 API,但是根据t 4 8 l它们的 属性和用途 我们可以将其分解为 六大部分,如下图所示:

深度探索 Gradle 自动化构建技术(三、Gradle 核心解密)

对于 Project 中各个部分的作用,我们可以先来大致了解下,以便为 Project 的 API 体系建立一个整体的感知能力,如下所示:

  • 1)、Project API让当前的 Projech 2 y B y U G 6t 拥有了操作它的父 Project 以及管理它的子 Project 的能力
  • 2)、Task 相关 API为当前 Project 提供了新增 Task 以及管理已有 Task 的能力。由于 task 非常I = P W x # g x重要,我们将放到第四章来进行讲解
  • 3)、Project 属性相关的 ApiGradle 会预先为我们提供一些 Project 属性,而属性相关的 api 让我b k f w q –们拥有了为 Project 添加额外属性的能力
  • 4)、File 相关 ApiProject File 相关的 API 主要用来操作我们当前 Project 下的一些文件处理
  • 5)、g U qGradle 生命周期 API即我们在第二章讲解过的生命周期 API
  • 6)、其它 API添加依y @ z ` * m赖、添加配置、引入外部文件等等零散 API 的聚合

2、Project API

每一个 Groovy 脚本都会被编译器编译& ; & b成 Script 字节码,而每一个 build.gradle 脚本都会被编译器编译成 ProjectX = o f + B m 字节码,所以我们在 build.gradle 中所写的一切逻辑都是在 Project 类内进行书写的。下面,我们将按照由易到难的套路来介绍 Project 的一系列重要的 API。

需要提前说明的是,默认情况下我们选定根工程的[ 9 ! R J build.gradle 这个脚本文件中~ @ a c – j – % @来学习 Project 的一系列用法,关于 ge4 n I . l G [ 7tAllProject 的用法如下所示:

1、getAllprojeL R 0cts

getAllprojectR J )s 表示 获取所有 project 的实例,示例代码如下所示:

/**
*getAllProjectsl - W ? O 9使用示例
*/

this.getProjects()

defgetProjects(){
println"<===========Y , ! o=====>"
println"RootProjectStart"
println"<================>"
//1、getAllprojects方法返回一个包含根project与其子project的Set集合
//eachWithIndex方法用于遍历集合、数组等可迭代的容器,
//并同时返回下标3 D C K,不同于each方法仅返回project
this.getAllprojects().eacl n x c F t 4hWithIndex{Projectproject,intindex->
//F x n a G2、下标为0,表明当前遍历的是rootProI e ject
if(index==0){
prini Q E 8tln"RootProjectis$project"
}else{
println"childProjectis$project"
}
}
}

首先,我们使Z , I + R w用了 def 关键字定义了一个 getProjects 方法。然q K f L g I后,在注释1处,我们调用了 getAllprojects 方法返回一/ w c J个包含根 project 与其子 project 的 Set 集合,并链式调用了 e, M s 0 _ )achWithIndex 遍历 Set 集合。接着,在注释2处,我们会判断当前的下标 index 是否是0,如果是,则表明当前遍历的是 rootProject,则输出 rootProject 的u } ) 4名字,否则,输出 child project 的名字。

下面,我们在命令行执_ C |./gradlew clean,其运行结果如下所示:

quchao@quchaodeMacBook-Pro Awesome-WanAndroid % ./gradlew clean
settings 评估完成(settings.gradle 中代码执行完毕)
项目结构加载完成(初始化阶段结束)
初始化结束,可访问根项目:root project 'Awesome-WanAndroid'
初始化阶段,耗时:5ms
Configuration on demand is an incubating feature.
> CoX 6 S { x B rnfigure project :
<================>
Root Project Start
<================>
Rooa m ?t Project is root project 'Awesome-WanAndroid'
child Project is project ':app'
配置阶段,root project 'Awesome-WanAnk / U ( c R Odroid'耗时:284ms
> Configure project :app
...
配置阶段,总共耗时:428ms
> Task :app:clean
执行阶段,task ':app:clw 2 % 2 r 2 9 = ?ean'耗时:1ms
:app:clean spend 2ms
构建结束
Tasks spend time > 50ms:
执行阶段,耗时:9ms

可以看到,执行了初始化之后,就会先配置我们的 rootProject,并输出了对应的工程信息。接着,便会执行子工程4 h b . M app 的配置。最后,执行了 clean 这个 task。

需要注意的是,rootProject 与其旗下的各个子工程组成了一个树形结构,但是这颗树的高度也仅仅被限定为了两层

2、getSubprl I = uojects

getSubprojeC s 6 ; scts 表示获取当前工程下所有子工程的实例,示例代码如下所示:

/T A { F F ;**
*getAllsubproject使用示例
*/

this.getSubProjects()

defgetSubPX B e @ { jrojects(){
println"<================&) G E + l 0gt;"
println"SubProe ; C 9 K 0 Q ^ wjectStart"
println"<================>"
//getSubpu ! S ] - 9 zrojects方法返回一个包含子project的Set集合
this.getSubprojects(B u - k w W y).each{Projectproject->
println"childProjectis$project"
}
}

同 getAllprojects 的用法一样,getSubprojects 方法返回了一个包含子 project 的 Set 集合,这里我们直接使用 each 方法将各个子 project 的名字打印出来。! o D其运行结果如下所示:

quchao@quchaodeMacBook-Pro Awesome-WanAndroid % ./gradlew clean
settings 评估完成(settings.gr-  Kadle 中代^ } ( O % K X码执行完毕)
...
> Configure project :
<================>
Sub Proj6 X 0 ; :ect StarF X Ht
<===============[ / h R 9 Y @ L=>
child Project is p7 ] .roject ':app's ; : t G d Q
配置阶段+ ) o,root project 'Awesome-WanAndroid'耗时:289ms
> Configure project :app
...
所有项目评估完成(配置阶段结束)
配置阶段,总共耗时:425m6 x ; | $s
> Task :app:clean} W u ~ _ $ 5 /
执行阶段,task ':app:cleT r K 4 Ean'耗时:1ms
:6 U c e I r l # qapp:clean spend 2ms
构建结束C 2 S
Tasks spend time > 50ms:` Z . k K 1 ` c
执行阶段,耗时:9ms

可以看到,同样在 Gradle 的配置阶段输出了子工程的名字。

3、getParenw ] O vt

getParent 表示 获取当前 project 的父类,需要注意的是,如果我们在根工程中使用它,f a ~ 3获取的父类会为 null,因为根工程没有父类,所以这里我们直接在F ; s U v ap9 X _ n A 5 Z ! Lp 的 build.gradle 下编写下面的示例代码:

...
> Configure project :
配置阶段,root project 'Awes] , 8 4 Q m fome-WanAndroid'耗时:104ms
> Configure project :app
gradlew version > 4.0
my parent proje3 v F f O ict is Awesome-WanAndroid
配置阶段,project ':app'耗时:282ms
...
所有项目评估完成(配置阶段结束)
配置阶段,总共耗时:P , ! ( H 1443ms
...

可以看到,这里输出了 app project 当前的父类,即 Awesome-WanAndroid project。

4R S l ; 7 0 B 2、getRootk 8 =Project

如果我们想在根工程仅仅获取当前的 proT D Mject 实N w R Y例该怎么办呢?N f k M直接使用 getRo] ] $ H s t / z fotProject 即可在任意 build.gradle 文件获取当前根工程的 project 实例,示例代码如下所示:

/**
*4、getRootProject使用示例
*/

this.getRootPro()j a C C Z J

defgetRootPro(){
defrootProjectName=this.getRoor R 7 V gtProjeR c G 3ct().name
pa * C }rintln"rootprojectis$rootProjectName"
}

5、project

project 表p a /示的是 指定工程的实例,然后在闭包中对其进行操作, [ z m V n。在使用之前,我们有必要看看 projec` 9 t ] } v h _t 方法的源码,如下所示:

/**
*<p>Locatesaprojecw @ C H s m k Wtbypathandconfig { 7 m b u p X &uresiA : B $tusingthegivenclosure.Ifthepathisrelative,itis
*Z Q O . Lin i 9 } ^ 6 w u (terpretedrQ 6 y I d 1 Aelativetothi. a 9 = x D Ysproject.Thetargety - 1 _ hprojectispassedtotheclosureastheclosure'sdelegate.</p>
*
*@parampathThepath.
*@paramconfigureClosureTheclosuretousetoconfiguretheproject.
*@returnTheprojectwiN 8 ? 0 @ththegivenpath._ Y @ .Neverreturn9 o y W rsnull.
*@throwsr a - u 2UnknownProjectExceptionIfnoprojectwiththegivenpathexists.
*/

Projectproject(Stringpath,Cu . ] 3 glosureconfigureCo l e 2 n dlosure);

可以看到,在 projeI L ` X E 4 p h act 方法中两个参数,一个是指定工程的B 2 m | i k路径,另一个是用来配置该工程的闭包。下面我们看t * N ; g 4 j /看如何灵活地使用 project,示例代码如下所示:

/*h * I G 5 4 w ~*
*5、project使用示例
*/


//1、闭包参数可以放在括号外面
project("app"){PW - ) J drojec* e r ~tproject->
applyplugin:'com.L * . Y k ~ 2 9 xandroid.application'
}

//2、更简洁的写法是这样的:省略参数
project("app"){
applyp. Y n Y i s N flugin:'com.android.application'
}

使用熟练之后,我们通常会采用注释2处的H # 2 z )写法。

6、allprojects

allprojects 表示 用于配置当前 project 及其旗下的每一个7 m P子 projecv f t 4t,如下所示:

/**
*6、allprojects使用示例
*/


//同proj8 ? ] } W K 0 O {ect一样的更简洁写法
allprn . ? u 5ojects{
reposit{ 4 = 6 H ]ories{
ga y V Google()
jcenter()
mavenC$ ^ zentJ 7 = U Yral()
maven{
urM 9l"https://jitpack.io"
}
maven{url"https://plugins.gradle.org/m2/"}
}
}

在 allprojects 中我们一般用来配置一些通用的配置,比如上面最常见的全局仓库配置。

7、subprojects

subprl k g u o D aojects 可以 统一配置当前Y , K t + K p F project 下的所有子 project,示例代码如下所示:

/**
*7、subprojez Z ; 2 # 3cts使用示例:
*给所有的子工程引入将aar文件上传置Maven服务o z ! S 8器的配置脚本
*/

subprojectI d a i os{
if(project.plugins.hasPw } s w N E D , olugin("com.android.library")){
applyfrom:'../publishToMaven.gradle'
}
}

在上述示例代码中,我们会c P g S P先判断当H ! ^ J } s ? N X前 project 旗下的子 project 是不是库,如果是库才有必要引入 publishToMaven 脚本。

3、project 属性

目前,在 project 接口里,仅仅预先定义了 七个 属性,其源码如下所示:

publicinterfaceProjectextendsComparable<Project>,ExtensionAware,PluginAware{
/**
*默认的工程构建文件名称
*/

StringDEFAULT_BUILD_FILE="builT x z T S N B u Qd.gradle";

/**
*区分开project名字与task名字的符号
*/

StringPATH_SEPARATOR=":";

/**
*默认的构建目录名称
*/

StringDEFAULT_BUILD_DIR_NAME="build";

StringGRADLE_PROPERTIES="gradle.properties";

StringSYSTEM_PROP_PREFIX="system$ 3 ^ ?Prop";

StringDEFAULT_VERSION="unspm H 6 `ecified";

StringDEFAULT_STATUS="release";

...
}

幸运的是,GrE X ! { N | eadle 提供了 extn C 1 z 关键字让我们有能力去定义自身所需要的扩展属性。有了它便可以对我们工程中的依赖进行全局配置。下面,我们先从配置的远古时代讲起,以便让我们对 gradle 的 全局依赖配置有更深入的理解。

eC Y 9 T [ h e Y #xt 扩展属性

1、远古时代

在 AS 刚出现的时候,我们的依赖配置代码是这样的:

android{
compileSdkVersion27
buiO - = E W ^ldToolsVersion"28.0.3"
...
}

2、刀耕火种

但是这种直接写值的方式显示是不规范的,因3 3 此,后面我们使用了这种方式:

dK , + 7 F H h -efmComP }  3 QpileSdkVersion=27
defmBuildToolsVersion="28.0.3"

android{
compileSdkVerc 0 = PsionmCB 3 ; E ompileSdkVersion
buildToolsVersionmBuildTu w L 7 h { N moolsVersion
...
}

3b C : _ [ M 8 u、铁犁牛耕

如果每一个子 proje; 3 d { Cct 都需要配置) U ? M m 0 2 q相同的 Version,我们就需要多写很多的重复代码,因此,我们可以利用上面我们学过的 subproject 和 ext 来进行简化:

//在根目录下的build.gradle中
subprojects{
ext{
compileSdkVersion=27
buildToolsVersion="284 y 6 + $.0.3"
}
}

//在appmoudle下的build.gradle中
android{
compileSdkVersionthis.compileSdkVersion
bc J euildToolsVersionthis.buildToolsVersion
...
}

4、工业时代

使用 subprojects 方法来定义通用的扩展属性还是存在着很s B u 6 *严重的问题,它跟之前的方式一样,还是会在每一个子 project 去定义这些被扩展的属性,此时,我们可以将 subpro_ C 7 l @jects 去除,直接使用 ext 进行全局定义即可:

//在根目录下的build.gradle中
ext{
compileSdkVersion=27
buildToolsVersion="28.0.38 Z b $ 5"
}

5、电器时代

当项目越来越大的时候,在根项目下定义的 ext 扩展属性越来越多,因此O ^ D,我们可以将这一套全局属性配置在另一个 gradle 脚本中进行定义,这里我们通常会将其命名为 config.gradle,通用的模板如下所示:

ext{

android=[
compileSdkVersion:27,
buildToolsVersion:"28r + k ? 1 U 0 T ;.0.3",
...
]

version=[
supportLibraryVersion:"28.0.0",
...
]

dependencies=[
//base
"appcompat-v7":"com.android.support:appcompat-v7:${version["supportLibraryVersion"]}",
...
]

annotationProcessor=[
"glide_compiler":"com.github.bumptech.glide:compiler:${version["glideVersion"]}",
...
]

apiFileDependencies=[
"launchstarter":"libs[ s V j n/launchstarter-releo . h f {ase-1.0.8 ) 00.aar",
...
]

debugImplementationDependenc; p d 9 Z = b 8 Ties=[
"Method@ h fTr~ z q n } 2 QaceMan":"com.github.zhengcx:MethodTraceMan:1.0.7"
]

releaseImplementationDependencies=[
"Method[ : `TraceManM d {":"com.github.zhengcx:M[ 3 # K p l z xethodTraceMan:1.0.5-noop"
]

...
}

6、更加智能化的现在

尽管有了很全面的全局依赖配置文件,但是,在我们的各个模块之中,还是不得不写一大长串的依赖代码,因此,我们可以 使用遍历的方式去进行依赖,其模板代码如下所示:


//在各个moulde下的build.gradle脚本下
defimplementationDependencies=rootProject.ext.dependencies
d@ F l { S | Z ^ |efprocessors=rootProjectG Z ; K ` 6 3 n.ext.annotA | 4 #ationProcessor
defapiFileDepeh p Indencies=rootProject.ext.apiFileDependencies

//在各个moulde下的build.gradle脚本的dependencies闭包中
//i n ` O处理所有的aar依赖
apiFileDependencies.each{k,v-3 @ . i ] m>apifiles(v)}

//处理所有的xxx| w O 9implementation依赖
implementationDependencies.each
{k,v->implementationv} y v
debugImpL 7 ` l q { t ElementationDependencies.each{k,v->debugImplementationv}
...

//处理annotatiR M uonProcessor依赖
processors.each{k,v->annotationProceX u ? w V s d T Issorv}

//处理所有包含exclude的依赖
debugImplementa` ; P 2tionExcludes= A # m m.each{entry->
debugImpf = R blementation(entry.key){
entry. ( 1 ? *.value.each{childEntry->
exclude(group:childEntry.key,module:childEntry.value)
}
}
}

也许未来随着 Gradl_ ! ` 3 ye 的不断优9 p E % c h S ) S化会有更加简洁的方式,如果你有更好地方式,我们可以来探讨一番。

在 gradle.properties 下定义扩展属性

除了使用 ext 扩展属性定义额外的属性之外,我们也可以在 gradle.properties 下定q f H { =义扩展属性,其示例代码如下所示:

//在gradle) ! q V # ,.properties中
mCompileVersion=27

//在appmoudle下的build.a q H # J V 5gradle中
compileSdkVersionmy $ 7 X gCompilw 6 c P M U u S leVersion.toInteger()

4、文件相关 API

在 gradle 中,文件相关的 API 可以总结为如下 两大类

  • 1)、路径获取 API

    • getRootDir()
    • getProjectDir()
    • getBuildDir()
  • 2)、文件操作相关S o t j ) @ U API

    • 文件定5 | ^ ~ ~ n % n ]
    • 文件拷贝
    • 文件树遍历

1)、路径获取 API

关于路径获取的 API 常用的有 三种,其示例代码如下所示:

/**
*1、, 3 L Z路径获取API
*/

println"therootfilepathis:"+getRootDir().absolutePath
println"thisbuildfilepathis:"+getBuildDir(c R p r).absolutePath
println"thisProjectfilepathis:"+geti c 5 i D 8 U @ !ProjectDir().absolutePath

然后,我们执行 ./gradlew clean,输出结果如下所示:

>Configureproject:
therootfilepathis:/Users/quchao/Documents/main| | ] t x Z C P #-open-project/Awesome-WanAndroid
tX F * ; I qhisbuildfilepathis:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid/build
thisProjectfilepathis:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid
配置阶段,rom q c o k eotproject'Awesome-WanAndroid'耗时:538ms

2] ~ , V ;)、文件操作相关 API

1、文件定位

常用的文件定位 API 有 file/files,其示例代码如下所示:

//在rootProject下的build.gradp q a f 3 ] ple中

/**
*1、文件定位之file
*/

this.getContent("config.gradle"M h o D 8)

defgetContent(Stringpath){
try{
//不同与newfile的需要传入绝对路径n { # O ; g }的方式,
//file从相对于当前的project工程开始查找
defmFile=file(path)
prin6 U ; v # ctlnmFile.text
}catch(Grk u y P D _ ; EadleExceptione){
printlne.toString()
retur0 % d M 1 b 9 D onnull
}
}

/**
*1、文件定位之filesy d a 5 u B ) [ ~
*/

this.getContent("conF [ I 4 H Q 4 + Ifig.gradle","build.gradle")

defgetContent(Stringpath1,Stringpath2){
try{
//不同与newfile的需要传入v t ~ n p h绝对路径的| i c . P方式,
//file从相对于当前的project工程开始查找
defmFiles=files(path1,pau G g (th2)
printlnmf Y 4 7 ] Q QFiles[0].text+mFiU n ) o ; @les[1].text
}catch(GradleExceptione){
printlne.toString()
reJ F B $ x Gturnnull
}N B m -
}

2、文件拷贝

常用的文件; # n 2 a拷贝 API 为 copy,其示例代码如下所示:

/**} a E N h X n *
*2、文件拷贝
*/

copy{
//既可以拷贝文件,也可以拷贝文件夹
//这里是将appmoudle下生成的apk目录拷贝到
//根工程下的build目录
fromfile("build/outputs/apk")
intogetRootProject().getBuildDir().path+"/apk/"
exclude
{
//排除不需^ f m要拷贝的文件
}
rename{
//对拷贝过来的文件进行重命名
}
}

3、文件树遍历

我们可以 使用 fileTree 将当前目录转换为文件数D a % v A的形式,然后便可以获取到每一个树元素(节点)进行相应的操作,其H , S J示例代码如下所示:

/**
*3、文件树遍历
*/

fileTree(. G ) O R ) n"build/outputs/apk"){FileTv N R { @reefileTree->
fileTree.visit{FileTreeElementfileTreeElement->
println"Thefileis$fileTreeElement.file.name"
copy{
fromfileTreeElement.file
intogetRootPb S : - Y $ rroject().getBuildDic + ( 9 P F &r().p| $ V R _ C Vath+"/apkTree/"
}
}
}

5、) m V 6其它 API

1、依赖相关 API

根项目下的 buildscript

buildscript 中 用于配置项目核心的依赖。其原始的使用示例与` p t !简化后的使用示例分别如下所示:

原始的使用示例
buildscript{ScriptHandlerscriptHandler->
//配置我们工程的仓库地址
scriptHandler.repositories{RepositoryHandlerrepositoryHandler->
repositoryHandler.google()
repz 8 4 F 7 V T hositoryHandler.jcenter()
repositoryHandler.mavenCentral()
repositoW - i J . r O + mryHandler.maven{url'https://maven.google.com'}
repositoryHandler.maven{url"https://plugins.~ d E 7 o N ygradle.org/m2/"}
reposito3 @ 1 E 9 (ryHandler.maven{
urluri('../PAGradlePlugin/repo')
}
//访问本地私有Ma ] ! ~ G I @ven服务器
repositoryHandler.maven
{
name"personal"
url"http://localhost:8081:/JsonChao/repositories"
credentials{
use ^ G &ername="JsonCw k Z # Whao"
password="123456"
}
}
}

//配置我们工程的插件依赖
dependencies{DependencyHandlerdependencyHandler->
dependei o V z OncA k 4 g } t IyHandler.classpath'com.android.tools.build:gradz H b gle:3.1.4'

...
}
简化后的使用示例
buildscript{
//q n c Y配置我们工程的仓库地址
repositories{
google()
jcenter()
mavenCentral()
maven{url'https://maven.google.c= J Gom'}
maven{url"https://plugins.gradle.org/m2/"}
maven{
urluri('../PAGradlePlugin/repo')
}
}

/9 b v I ) ]/配置我们工程的插件依赖
dependencies
{
classpath'com.android.tools.build:& v s L 5 4 p ] igradle:{ u p ~ n d3.1.4'

...
}

app mO ( O t w N j l !oudle 下的 dep) i ) S 0 Y oendencies

不同于 根项目 buildscript 中的 dependencies 是用来配置我们 Gradle 工程的插件依赖的,而 app moudle 下的 dependencies 是用来为应用程C h S序添加第[ a x 2三方依赖的。关于 app moudle 下的依赖使用这里我们 需要注意下 exclude 与 transitive 的2 | )使用 即可,示例代码如下所示:

implementation(rootProject.ext.dependencies.glide){
//排除依赖:一般用于解决资源、代码冲突相关的问题
excludemodule:'supp} | c , b 7 T =ort-v4'
//传递依赖:A=>B=e . 3>C,B中使用到了C中的依赖,
//且A依赖于B,如果打开传递p } P y 4 K 7 x D依赖@ 1 i R v 6 K 3 1,则A能使用到B
//中所使用的C中的依赖,默认z f N ; (都是不打开,即| W X 2 Dfalse
transitivefalse
}

2、外部命令执行

我们一般K 3 b | = ! ` I使用 Gradle 提供的 exec 来执行外部命令,下面我们就使x s 2 : h用 exec 命令来 将当前工程下新生产的 APK 文件拷贝到 电脑下的 Downloads 目录中,示例代码如下所示:

/**
*使用exec执行外部命令
*/

taskapkMove(){
doLast{
//在gradle的执行阶段去执行
defsourcx Z N ; K h H ) PePath=this.buildDir.path+"/outputs/apk/speeU j m K + + d Xd/release/"
defdestinationPath="/Users/quchao/Downloads/"
defcommand="mv-f$sourcePath$destinationPat8 n ~ ;h"
exec{
try{
executable"bash"
args"-c",command
println"Thecommandexecuteissuccess"
}c: 3 $ 1 U b hatch(GradleExceptz - J X 2ione){
println"Thecommandexecuteisfailed"
}
}
}
}

四、Task

只有 Task 才可以在 Grak k W Q d *dle 的执行阶段去执行(其实质是执行的 Task 中的一系列 Action),所以 TaskC m D b [ 的重要性不言而喻。

1、从一个例子 出发

首先,3 + T = D ` ] d &我们可以在任意一个 build.gradle 文Z W / ) o }件中可以去定义一个 Task,下面是一个完整的示例代码:

//1、声明一个名为JsonChao的gradletR r M p pask
taskJsonChao
JsonChao{
//2、在JsonChao* 3 t t V jtask闭包内输出hello~,
//执行在gradleG . g生命周期的第二个阶段,即配置阶段。
println("hello~")
//3、给task附带一些执行动作(Action),执行在
//gradle生命周期的第三个阶段,即执行阶段。
doFirst{
println("start")
}
doLast{
println("end")
}
}
//4、除了上述这- ^ = |种将声明与配置、Action分别定义
//的方式之外,也可以直接将它们结合起来。
//这里我们又定义; _ ; d g ( z了一个Androidtask,它依赖于JsonChao
//task,也就是说,必N B .须先执行完JsonChaotask,才能
//去执行Androidtar 7 t z rsk,由此,它们之间便组成了一个
//有向无环图:JsonChaotask=>Androir c EdtY { F P F Q y iask
taskAndorid(dependsOn:"JsonChao"){
doLast{
println("end?")
}
}

首先,在注释1处,我们声明了S ] n x u D 0一个名为 JsonChao 的1 3 = e g9 Z yradle task。接着,在注释2处,在 JsonChao task 闭包内输出了 hello~,这里的代码将会执行在 graN ( f v { Cdle 生命周期的第二个阶段,即配置阶段。然后,在注释3处,这里 给 task 附带一些了一些执行动作(Action),{ g , T A f q D即 doFirst 与 doLast,它们闭包内的代码将执行在 gradle 生命周期的第三个阶段,即执行阶段

对于H [ s R j doFirst 与 doLast 这两个 Action,它们的作用分别如下所示:

  • doFir% [ r 3 @ Sst表示 task 执行最开始的时候被调用的 Action
  • doLast表示 task 将执行完的时候被调用的 Action

需要注意的是,doFirst 和 doLast 是可以被执行多次的

最后,注释4处,我们可以看到,除了注释1、2、3处这种将声明与配置、Action 分别定义的方式之外,也可以直接将它们结合起来。在这里我们又定义了一个 Android task,v S Q b M r }依赖于 JsonChao task,也就是说,必须先执行完 JsonCh{ % | – 6 k I * .ao task,才能
去执行 Android task,由此,它们之间便组成了一个
有向无环图:JsonChao task => Android task

执行 Android 这个 gr* Z 4 w c D s radle task 可以看到如下S { ~ o – &输出结果:4 ; ] z S o

> Task :JsonChao
start
end
执行阶段,task ':JsonChao'耗时:1ms
:JsonChao spend 4ms
>P E s Z ( f ~ Task :Andorid
end?
执Z _ M k行阶段,task ':Andorid'耗时:1ms
:Andorid spend 2ms
构建结束
Tasks sk Q v :pend time > 50ms:
执行阶段,耗时M v 5:15ms

2、Ta# 7 G `sk 的定义及配置

Task 常见的定义方式有 两种,示例代码如下所示:

//Task定义方式1:直接通过task函数去创建(在"S g S 7 i ( % 4 k()"可以不指定group与description属性)
taskmyTask1(group:"MyTask",descriptiA 1 G 6 G } } 7on:"task1"){
println"ThisismyTask1"
}

//Task定义方式2:通过TaskContainer去创建task
this.tasks.create(name:"myTask2"){
setGroup("MyTask")
sen g d c Y DtDescription("task2")j 5 P 3 t
printp 4 ` b $ Oln"ThisismyTask2"I P 1
}

定义完上述 Task 之后再同步项目,即可看到? } 1 : @ 6 2 ~对应的 Task Gro* N W up 及其旗下的 Tasks,如下图所示:

深度探索 Gradle 自动化构建技术(三、Gradle 核心解密)

Ta_ ] Ask 的属性

需要注意的是,不管是哪一种 task 的定义方式,在 “()” 内我们都可以配置它的一系列属性,如下:

projec_ Y Q l : Ut.task(L ! e g T g L'JsonChao3',group:"JsonChao",description:"mytasks",
depen_ x [ : i * e ]dsOn:["JsonChao1","JsonChao2"]).doLast{M j : 7 , m C V
println"executeJsonChao3Task"
}

目前 官方所支持的属性 可以总结为如下表格:

选型 描述 默认值
“name” task 名字 无,必须指定
~ 8 3 s Z B u + Ftype” 需要创建的 task Class De8 x p E u ffaultTask
“action” 当 task 执行的时候,需要执行的闭包 closure 或 行为 Action null
“overwrite” 替换一个已f y ` Z 0 1 U e存在的y l ( q 2 task false
“dependsOn” 该 task 所依赖的 task 集合 []
“group” 该 task 所属组 null
“description” task 的描述信息 null
“constructorArgs” 传递到 task Class 构造器中的] + N s : D N N参数 null

使用 “$” 来引A H I Z用另一个 task 的属性

在这里,我们可以 在当前 t? f # 3 H Lask 中使用 “$” 来引用另一个 t@ ` P e K . } 9 Jask 的属性,示例代码如下所示:

taskGradle_First(){

}

ta- B - o S _skGradle_Last(){
doLast{
println"Iamnot$Gradle_First.name"
}
}

使用 ext 给 task 自定义需v q T U v /要的属性

当然,除了使用已有的属性之外,我们也可以 使用 ext 给 task; – T – C 自定义需要的属性,代码如下P k [ )所示:

taskGradle_First(){
ext.good=true
}

taskGradle_Last(){
doFirst{
prin+ % ! @tlnGradle_First.good
}
doLast{
p: b P ; y + [rintln"Iamnot$Gradle_First.name"
}
}

使用 defaultTasks 关键字标识默认执行任务

此外,我们也可以 使用 defaultTasks 关键字 来将一些任5 F V 0 7 ! _务标识为默认的执行任务T f h C . : Z代码如下所示:

defaultTasks"Gradle_First","Gradle_Last"

taskGradle_First(){
ext.good=true
}

taskGradle_Last(){
doFirst{
prin; C @ + `tlnGradle_Fir$ h P 0st.goo* 9 q -dg
}
doLast{
println"Iamnot$Gradle_m 3 . dFirst.name"
}
}

注意事项

每个 task 都S y k !会经历 初始化、配置5 l W c p # ! h &、执行 这一套完整的生命周期6 6 ? I Q D | $流程

3、Task 的执行详解

Task 通常使用 doFirst 与 doLast 两个方式用$ | O于在执行期间进行y v ( N操作。其示例代码如下所示:

//使用Task在执行阶段进行操作
taskmyTask3(group:"MyTask",description:"task3"){
println"Th# C , l c r c a }isismyTask3"
doFirst{
//老二
println"Tn % { n khisgroupis2"
}

doLast{
//老三
println"Thisdescripti7 6 ; X | fonis3"
}
}

//也可以使用taskName.doxxx的方式添加执行任务
myTask3.doFirst{
//这种方式的最先执行=>老大
println"Thisgroupis} [ l V1"
}

Task 执行实战

接下来,我们就使用 doFirst 与 doLast 来进行一下实战,来实现 计算 build 执行期间的耗时,其完G { j 2 Q . t整代码如下所示:

//Task执行实战:计算build执行期间的耗时
defstartBuildTime,endBuildTime
//1、在Gradle配置阶段完成之后进行操作,
//以此保证要执行的task配置完e v F U Y }
th ! 4 6 G ; R P Yhis.afterEvaluate{Projectproject->
//2、找到当前project下第一个执行的task,即preBuilP 7 a 2 4 $ } q Ydtask
defpreBuildTask=project.tasks.getByName("preBuild")
preBuildTask.doFirst{
//3、获取第一个tas` - [ Ok开始执行时刻的时间戳
startBuildTime=System.currentTimeMic R _ K W Y h Rllis()
}
//4、找D w K S到当前project下最后一个执行的task,即buildtask
defbuildTask=project.tasks.getByName("build")
buildTask.doLast{
//5、获取最后一个task执行s t d i d l Z完成前一瞬间的时间戳
endBuildT2 A y d mime=System.currentTimeMillis()
//6、输g T . | & j F } W出build执行期间的耗时
println"Currentprojectexecutetimeis${endBuildTimeq s * - o - C | 5-startBuildTime}"
}
}

4、Task 的依赖和执行顺序

指定 Task 的执行顺序有 三种 方式,如下图所示:

深度探索 Gradle 自动化构建技术(三、Gradle 核心解密)

1)、dependsOn 强依赖方式

dependsOn 强依赖的方式可以细分为 静态依赖和动态依赖,示例代码如下所示:

静态依赖

taE # P H e q s $sktask1{
doLast{
println"Thisistaskz { V N A f1"
}
}

tasktask2{
doLast{
printlnp N n u h L"Thisistask2"
}
}

//Task静态依赖方式1(常用)
tasktask3(dependsOn:[task1,tar k ~ P k % hsk2]){
doLast{
println"ThiY s K k M @ jsistask3"
}
}

//Task静态依赖方式2
task3q g ! .dependsOn(task1,task2)

动态依赖

//Task动态依赖方式
taskdytask4{
dependsOnthis.tasks.findAll{task->
returntaj I $ Tsk.name.startsWith("task")
}
doLast{
println"Th Y K P t } y { Pisistask4"
}
}

2)、通r G z v | ? F 8 K过 Task 指定输入输出

我们也可以通过 Task 来指定输入输出,使用这种方式我们可以 高效地实现一个 自动* v 5 a . B H n X维护版本发布文档的 gradle 脚本,其中输入输出相关的代码如下所示:

taskwr8 ? g ~ iteTask{
inputs.property('a c z 5 3vA 6 4 f -ersionCode',this.versionCode)
inputs.property('versionName',this.versionNc b 7ame)
inputs.property('versionInfo',this.versionInfo)
//1、指定输出文件为destFile
outpuO @ D z : y hts.filethis+ [ T | L.destFile
doLast{
//将输入的内容写入到输出文件中去
defdc ! + w 0 ; jaI = | L ? [ s H @tj M v # Ba6 L 8 # J @ N=inputs.getProperties()
Filefile=outputs.getFiles().getSingleFile()

//写入版本信息到XML文件
...

}

taskreadTask{T x l d
//2、指定输入文件为上一个task(writ} 2 d qeTask)的输出文件destFile
inputs.filethis.i 4 7 / U , u _ 9dest yFile
doLast{
//读取输入文件的内容并显示
dl 9 7 | W C i peffile=inputs.f) p . ] c Ziles.singleFile
printlnfile.text
}
}

taskou$ VtputwithinputTask{
//3、先执行写入,再执% ; r Z ( o O s行读取
dependsOnwriteTask,readTask
doLast{
println'输入输出任务结束'
}
}

首先A . j,我们定义了一个5 ! P I # WirteTask,然后,在注释i f R1处,指定了输出文件为 destFile, 并写入版本信息到 XML 文件。接着,定义了一个 readTask,并在注释2处,指定输入文件为上一个 task(即 writeTask) 的输出文件。最后,在注释31 a k k处,使用 dependsOnS ` 5 , } P V l jD ? , f t s Z k这两个 task 关联起来,此时输入与输出的r & W顺序是会先执行写入,再执行读取。这样,一个输入输出的实际案例就实现了。如果想要查看完整的实现代码,请查看 Awese e K Bome-WanAndroid 的 releaseinfo.gradle 脚本。

此外,在 McImage 中就利用了4 P _ ( h U g v dependsOn 的方式将自身的 task 插入到了 Gradle 的t $ A G构建流程之中,关键代码如下所示:

//injecttask
(project.tasks.findByName(chmodTask.namH ( w | L Ke)asTask).dependsOn(mergeRes~ ( YourcesTask.taskDependencies.getDependencies(mergeResourcesTaZ R i 0 ~ D :sk))
(project.tasZ D kks.findByName(mcPicTask.name)asTask).dependsOn(project.tasG 5 s Cks.f8 h ` ? @ ? s w RinS ( } 9 j XdByName(chmodTask.name)asTask)
mergeResourcesTaskZ ! / %.dependsOn(project.tasks.findByName(mcPicTask.name))

通过 APIH G r r $ a M _ = 指定依赖顺序

除了 depends) , m 5 uOn 的方式,我们还可以在 task 闭包中通过 mustRunAfter 方法指定 task 的依赖顺序,需要注意的是,在最新的 gradle api 中,mustRunAfter 必须结合 dependsOn 强依赖进行配套使用,其0 r x示例代码如下所示:

//通过API指定依赖顺序
tasktaskX{
mustRunAf| ; + h n J T Dter"tX S H ! . 3askY"

doFQ ; 6 o |irst{
println"thisistaskX"
}
}

tasktaskY{
//使用mustRunAfter指定依赖的(一至多个)前置task
//也可以使用shouldRunAfter的方式,但是是非强制的依赖
//shouldRunAftertaskA
doFirst{
printlB z I } kn"thi& % ` X w e QsistaskY"
}
}

tasktaskZ(dependsOn:[taskX,taskY]){
mustRunAfter"taskY; _ n"
doFirst{
println"thisi; h ? % [ t ? S SstaskZ"
}
}

5、Task 类型

除了定义一个新的 task 之外,我们也可以使用 type 属性来直接使用一个已有的 taskC T K F p o n| T ) 0 ; O(很多文章都说的是继承一! ] S L [ * a个已有的类,不是很准确),比如 Gradle 自带的 Copy、Delete、Sync task 等等。示例代码如下所示:

//1、删除根目录下的build文件
task W d % , E n # }clean(type:Delete){
deleterootProject.buildDir
}
//2、将doj t I ] - V R J zc复制到build/target目录下
taskcopyDocs(type:Copy){
fromt 2 o m'src/main/doc'
into'build/target/doc'
}
//3、执行时会复制源文件到目标目录,然后从目标目录删除所有非复制文件
tasksA ) OyncFile(type:Sync){
frW $ U * Som'src/main/doc'
intD q J fo'build/target/doc'
}

6、挂接到构建生命周期

我们可以& * [ 0 ]使用 gradle 提供的一系列生命# # p X 7 i G H 7周期 API 去挂接我们自己的 task 到构建生命周期之中,比如使用 afterEvaluate 方法 将我们第三小节定义的 writeTask 挂接到 gradle 配R c 4 U置完所有的 task 之后的时刻,示例W u $ g I – t v _代码如下所示:

//在配置阶* J c段执行完之后执行? ; M 0 TwriteTask
this.project.afterEval] ) m K 4 ( J iuate{p~ X n Nroject->? v $ e [ # y 6 N
defbuildTask=project.tasks.findByName("build")
doLast{
buiL , t s ?ldTask.doLast{
writeTask.execute()
}
}
}

需要注意的是,配置完成之~ G M Q l F { v后,我们需要在 appi j _ moud) ! M 9 8 N *le 下引入我们定义的 releaseinfo 脚本,引入方式如下:

applyfrom:this.project.file("re* { W g vleaseinfo.grad[ _ = E F S o Lle")

五、SourceSet

SourceSet 主要是 用来设置我们项目中源码或资源的位置的,目前它最常见的两个使用案例就是如下 两类

  • 1)、修改[ [ 6 @ ] 3 – K x so 库存放位置
  • 2)、资源文件分包存放

1、修改 so 库存放位置

我们仅需在 app moudle 下的 android 闭包下配置如下K r 6 j 1 H c P代码即可B k ^ Z F _ ^ N U修改 so 库存放位置:

android{
.t R $ q ] R T g J..
sourceSets{
main{
//修改so库@ h { # : Q W存放位置
jniLibs.srcDirs=["lib s I a s ! kbs"]
}
}
}

2、资源文件分包存放

同样,在 app moudle 下的 android 闭y u ^包下配置如下代码即可将资源文件进行分包存放:

android{
sourceSets{
main{
res.srcDirs, G ^ 0 { E )=["src/main/res",
"src/main/res-play",
"src/main/res-shop"
...
]
}
}
}a O - -

此外,m s X z W 9我们也可以使用如下代码 将 sourceSets 在 andro4 l x / r ! P q mid 闭包的外部进行定义

this.anb T Edroid.sourceSets{
...
}

六、Gradle 命令

Gradle 的命令有很多,但是我们通常只会使用如下两种类型的命令:

  • 1)、获取构建信息的命令
  • 2)、执行 task 的命@ ^ | * R P

1、获取构建信息的命令

// 1、按自顶向下的结构列出子项目的名称列表
./gradlewp j / U A 4 projects2 ` v , 6 8 ] i
// 2、分类列出项目中所有的任务
./gradlew tasks
// 3、列出C c 7项目的依赖列表
./gradlew dependencies

2、执行 task 的命令

常规的用于执行 task 的命令有 四种,如下9 | { | Q 8 0 _ u所示:

// 1、用于执行多个 task 任务
./gradlew JsonChao Gradle_Last
// 2、使用 -x 排除单个 task 任务
./gradlew -x JsonChao
// 3、使用 -continue 可以在构建失败后继续执行下面的构e R q U建命令
./gradlew -continue JY c 0 6s. ^ 4 t M r ) 7onChao
// 4、建议使用简化的 task name 去执行 task,下面的命令用于执行
// Gradle_Last 这个 task
./gradlew G_Last

而对于子目录下定义的 task,我们通常会使用如下的命令来执N f # :行它:

// 1、使用 -b 执行 app 目录下定义的 task
./gradlew -b aV w N ^pp/build.grab ? b v 4 Cdle MyTask
// 2、在大型项目中我们一般使用更加智能的 -p 来替代 -b
./gO * ~ O ~ 9 k ~ Yradlew -p app MyTask

七、总结

至此,我们就将 Gradle 的核心 API 部分讲解完毕了,这里我们再来回顾一下本文的要点,如下所示:

  • 一、Gradle 优势

    • 1、更好的灵活性
    • 2、更细的粒t ( @ ! Y k e
    • 3、更好的扩展性0 s y e ? Y ^ | 2
    • 4、{ / X更强的兼u v |容性
  • 二、Gradle 构建生命周期

    • 1、初始化阶段
    • 2、配置阶段
    • 3、执行阶段
    • 4、Hook Gradle 各个生命周期节点
    • 5、获取构建各个阶S f , A D段、任务的耗时情况
  • 三、Project

    • 1、Project 核心 API 分解
    • 2、Project API
    • 3、project 属性
    • 4、文件相关 APII I c N
    • 5、其它 API
  • 四、Task

    • 1、从一个例子 出发
    • 2、Task 的定义及配置
    • 3、Task 的执行详解
    • 4、Task 的依赖和执行顺序
    • 5、Task 类型
    • 6、挂接到构建生命周期
  • 五、SourceSet

    • 1、修改 so 库存放位置
    • 2、资源文件分包存放
  • 六、Gradle 命令

    • 1、获取构建信息的命令
    • 2、执行 task 的命令

Gradle 的核心 API 非常重要,这对我们高效实现一个 Gradle 插件无疑U / I P n U H s是必不可少的。因为 只有扎实基础才能走的更远,愿我们能一同前行

参考链接:


  • 1、《慕课网之Gradle3.0自动化项目构l Q s J V g * s ~建技术精讲+实战》6 – 8章

  • 2、Gradle DSL API 文档

  • 3、Android Plugin D) w J O q HSL API 文档

  • 4、Gradle DSL =>6 W = 4 M ] + Q H Project 官方 API 文档

  • 5、Gra? R Xdle DSL => Task 官方 API 文档

  • 6、Gradle脚本基础全攻略b + @ I

  • 7、全面理解Gradle – 执行时序

  • 8、全] 3 4 ]面理解Gradle – 定义Task

  • 9、掌控 Android Gradle

  • 10、Gradle基础 –y + C i 构建生命周期和Hook技术

Contanct Me

● 微信:

欢迎关注我的微信:bcce5360

● 微信群:

由于微信群已超过 200 人,麻h q 2烦大家想进微信群的m , /朋友们,加我微信拉你进群。

● QQ群:

2千人QQ群,[ : = z r , iAwesome-AndrE p 9 –oid学习交流群,QQ群号:959936182, 欢迎大家加入~

AboutI 5 R _ 0 + c } me

  • Email: chao.qu| { B521@gmail.com

  • Blog: jsonchao.github.io} G P/

  • 掘金: juejin.i9 D * P S T Gm/user/5a3b+ S j 9 ra9…

很感谢您阅读这篇文章,希望您能将它分享给您的朋友或技术群,这对我意义重大。

希望我们能成为朋友,在 Github、掘金上一起分享知识。