提到 Gradle,熟悉 Android 的人都不会陌生,在咱们开始把 Android Studio 这个 IDE 扶正的时候,gradle 就完全进入了咱们的视野。可是大多数人对于 gradle 执行构建和构建流程都比较陌生,本文从编写 Gradle Plugin 的角度,但愿把 Gradle 体系的一些基础结构能讲明白。html
首先咱们明白,gradle 的工做是把全部的构建动做管理起来 —— 任务是否应该执行,何时执行,执行某个任务前先作一些什么事情,某几个动做是否能够并行执行。
对于 gradle plugin 的编写就是为了帮咱们完成这些事情。若是你单单从任务的纬度去看这个问题的话,又会想到若是 B 须要 A 的产物的话,是否须要把 A 和 B 进行一些耦合。显然,对于任务间的解耦,Gradle 也作了。java
上面咱们提到了「任务」这个词,任务是什么呢?一个任务咱们能够理解为把一次指定的输入,转换成想要的输出。好比「编译」这个动做,就是把 .java 文件编译成 .class,或者执行 aapt,把资源文件编译成一个resource.ap_
文件等。任务的基础就是这么简单,而后为了加快执行速度,gradle 增长了 UP-TO-DATE 检查(只要输入和输出的文件不发生变化,那么这个任务就再也不执行),也增长了 incremental build 的特性(下一次的编译,并不仅是把.class
所有删除,从新编译一次这样粗暴,而是只编译变化了几个文件)并发
在这种细颗粒度的状况下,咱们对于任务执行的正确性和效率都有了保障。ide
关于 Gradle 的构建任务,其实网上有不少文章介绍了,无非是介绍任务的定义方式,任务的doFirst
和doLast
,可是不多介绍其余的元素,咱们从 gradle plugin 的视角介绍一下这些概念。在一切开始以前,咱们要了解下 gradle 这个容器的一些最最基础的流程 —— gradle 构建生命周期。函数
官方文档: https://docs.gradle.org/curre...
如文档所示,gradle 在执行的时候,会经历三个过程 —— 初始化,配置,执行。初始化过程对于咱们来讲,体感比较弱;配置阶段是一个重要阶段,咱们须要告诉每个 Task,它的输入文件是什么(好比源码文件,资源文件),输出文件或者文件夹是什么(好比编译后的 .class 文件,ap_ 等资源包放在哪一个文件夹下)等等。那么执行阶段,就是真正执行任务的时候了,咱们这时候须要在执行的函数中,拿到在配置阶段定义的 Input,而后生产出 Output,放到规定的目录下,或者写入指定的文件便可。gradle
对于咱们来讲,理解生命周期尤其重要,若是你在configuration
阶段去获取一个 task 的结果,从逻辑上来讲是很愚蠢的。因此你很须要知道你的代码是在“什么状态下”执行这一步操做。ui
咱们知道了生命周期之后,就要开始思考一个问题,好比 B 任务的一些输入依赖于 A 任务的一些输出,这时候就须要配置 B 任务依赖 A 任务,那么我如何保证这一点呢?this
有一个办法,那就是对 B 任务调用显式依赖B.dependsOn(A)
这样 B 必定在 A 以后执行的,B 任务中对于某个由 A 产生的文件的读取是必定能读到的。不错,它是个好办法,但问题就在于,这样的指定方式耦合度很是高,若是你须要加入一些对A
产物的一些修改,而后再传给B
的时候,就没有任何办法了。B
同时知道了A
的存在,若是咱们这时候不但愿由A
任务提供这个文件,而是由A'
来提供这个输出,在这里也作不到,因此须要换一个思路。spa
Gradle 提供了并使用了很是多像 Provider,Property,FileCollection 之类这样的类。看名字咱们大概能知道,这些方法都提供了一个 get() 方法,获取到里面保存的实例。可是 Gradle 对于这个 get() 方法赋予了更多的意义,它能够把依赖关系放进去,当你调用get()
的时候,能够检查它的依赖的任务是否已经执行完成,若是已经完成,那么再返回这个值。code
@NonExtensible public interface Provider<T> { /** * Returns the value of this provider if it has a value present, otherwise throws {@code java.lang.IllegalStateException}. * * @return the current value of this provider. * @throws IllegalStateException if there is no value present */ T get(); //..... }
有了上面这个特性,咱们定义起依赖关系就简单多了,咱们把一个任务的输出文件用 Provider 包裹起来,也就是Provider<File>
这样的类型提供,由 Gradle 或者自行为这些 Provider 设置dependsOn
,而后再把这些 Provider 分发给其余 Task。
另外的 Task 只要保证它只在执行阶段去调用这些 Provider 的 get 方法便可。Provider 只是一种意图,所以他们能够先把 Provider 存到 Task 实例的成员变量里,同时使用 Gradle 提供的@Input/@InputFile/@OutputFile
等注解为这些 Provider 的 getter 进行标注,这样能让 Gradle 把这些值管理起来。
这样咱们解决了第一个问题 —— Task 之间不在显式依赖。若是咱们想实如今 Task A 和 Task B 之间作一些 Hook 的话,咱们这时候要对 Provider 作一个管理,咱们能够作一个全局管理器,为每个产物集合作一个名字或者枚举的标记,而后对对应的标记定义一系列的动做,好比替换这个标记的产物,或者追加产物等,以便于后续的任务能更好的处理这里产生的产物。
这张图是原来的显式依赖方式
解耦后的方式是
这样任务和任务之间就这么联系在了一块儿,当咱们执行一条熟悉的命令:
./gradlew assembleDebug
它会把依赖产物的全部 task 所有执行一遍,事实上,assembleDebug
这个任务根本不知道本身依赖了哪些具体的任务,它只知道本身“须要”什么,产出什么(apk)。
上面讲了任务依赖相关的理论知识,咱们来举一个具体的例子,就以assembleDebug
为例。
咱们把事情说的简单点,好比assembleDebug
的任务是把全部已经处理好的 dex,resources,assets 打包成一个 apk,那么这个 input 就是前面提到的三个,output 是 apk。咱们在assembleDebug
这个 Task 里面会看到以下的东西(伪代码):
class AssembleDebugTask { private Provider<File> dexInput; private Provider<File> resourcesInput; private Provider<File> assetsInput; private Provider<File> outputAPK; @InputFile public Provider<File> getDexInput() { return dexInput; } @InputFile public Provider<File> getResourcesInput() { return resourcesInput; } @InputFile public Provider<File> getAssetsInput() { return assetsInput; } @OutputFile public Provider<File> getOutputAPK() { return outputAPK; } }
以上是对产物的定义,那么在执行任务的过程当中,会有这样的逻辑:
public void doTaskAction() { File dexInput = this.dexInput.get(); }
在这一步的过程当中,Gradle 会去检查这个 Provider 的来源,有没有builtBy
属性,若是有的话,会先执行buildBy
的 Task,好比咱们知道Dex
的文件必定来源于产生 Dex 的任务,那么若是咱们定义这个任务叫DexTask
的话,就会先执行DexTask
这个任务,才会继续执行assembleDebug
了。
事实上为了加快效率,标记了@Input
之类的注解的属性,gradle 在检查任务的时候,会提早去执行相关的依赖,由于在这个过程当中,它能够动用并发的方式,并行执行几个任务,好比咱们这依赖了三个输入,那么能够并行执行这三个任务,等到都执行完了,再去执行assemble
的任务,这时候调用get
就能直接返回值了。
欢迎关注个人公众号「TalkWithMobile」