写给 Android 开发者的 Gradle 系列(一)基本姿势
本文基于 Android Gradle plugin 3.0.1
建议前往掘金阅读
Gradle 介绍
笔者认为能够戳进这篇文章的读者十之八九也是知道 Gradle 可以用来干什么,所以没必要介绍什么了,毕竟说一堆术语搞得大家都不懂很难堪(手动滑稽)。简单来说,对于大部分的 Android 开发者来说 Gradle 是一个强大的工具,它提供便捷的方式帮助开发者构建 app。如果想看一下比较丰富的介绍的话可以查看如何通俗地理解 Gradle?
如何学习 Gradle
接下来笔者会出一系列关于 Gradle 文章,但是授人鱼不如授人以渔 ——
-
Gradle 基于 groovy 语言,groovy 官方文档链接戳我。当然,对于类似笔者这种比较懒的程序员来说一般会选择搜一些中文文章来看,如附录中的Gradle从入门到实战 - Groovy基础。好消息是 groovy 与 java 相同是基于 jvm,所以理解起来并不是那么困难,且对于日常开发来说,真的不需要学习多少内容。
-
Gradle DSL 学习。新建一个 Android 项目,可以看到
project/build.gradle
文件中的内容类似如下:buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.0.1' } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir }
如果你想知道
buildscript
和allprojects
的含义就应该戳开上面的链接了。例如文档中说到allprojects
是用于配置当前 project 和所有子 project 的,该方法将会在这些 project 中执行给定的闭包,那么上述代码闭包中的repositories
的意义同样是可以在文档中找到的。 -
Android Plugin DSL 学习。新建一个 Android 项目,可以看到
project/app/build.gradle
文件中的内容类似如下:apply plugin: 'com.android.application' android { compileSdkVersion 27 defaultConfig { applicationId "com.joker.cliptest" minSdkVersion 21 // ... } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:27.1.1' // ... }
那么像
buildTypes
的意义是什么就可以在上述文档中找到了。 -
Gradle task 学习。在前文提到的
project/build.gradle
文件中最后有一个task clean(type: Delete) { delete rootProject.buildDir }
其实就是一个 task,关于 task 的更多姿势在后一节中阐述。
前面共有四点,而且看起来每一点都需要花一定的时间去学习,其实不然,等到问题遇到了再去查文档就好了,尤其是前面提到的两点 DSL 学习,其实日常开发中就已经能够理解其中的含义了,说白了它们其实就是一些闭包、一些固定格式,正是因为它们的格式是固定的,task 才能够读取到相应的数据完成相应的事情,所以各位读者大不必害怕 Gradle 学习成本高。
Gradle task
日常开发中,上一节提到的四点中,Gradle task 与大部分的开发者开发是最为紧密的。日常开发中开发者难免会进行 build/clean project、build apk 等操作 ——
实际上这些按钮的底层实现都是通过 Gradle task 来完成的,只不过 IDE 使用 GUI 降低开发者们的使用门槛。当然,如果各位读者足够细心观察的话,是能够看到点击相应按钮后在 Build 输出栏中会输出相应的信息 ——
其实说到这里各位读者理应对 task 有一个基本的概念,一个 task 就是一个函数,没有什么神秘的地方。想要知道当前 Android 项目中共有哪些 task,可以在项目目录下输入:
./gradlew tasks(Mac、Linux)
gradlew tasks(Windows,后文同)
将会输出类似以下信息:
这仅是部分 task,如果想要查看全部的 task 可以添加 --all
参数:
./gradlew tasks --all
拉到最后的 Other task 部分:
可以看到所有的 task,可能有经验的读者更是感觉到亲切,提取部分 task 说明下:
compileDebugJavaWithJavac
:编译 java 文件processDebugManifest
:生成最终 AndroidManifest 文件compileDebugAidl
:编译 AIDL 文件packageDebug
:打包成 apk
而如果为 release 包开启了混淆,可看到还有 transformClassesAndResourcesWithProguardForRelease
task,即为 release 包混淆。
这些 task 实际上贯穿了开发者们的日常开发流程,只是 IDE 在上层封装了一层,开发者们点点按钮就完成了这些操作。当然也有读者会疑惑,这些 task 从哪里来的?为什么没有在 build.gradle
中看到蛛丝马迹?这些 task 都是 plugin 中的,在 build.gradle
顶部 apply 的 plugin 中的(com.android.application/com.android.library
)。在后续的文章中笔者将会介绍如何找到这些 task 的源码、如何阅读它们、如何写 task、如何写 plugin 以及解析较为知名的 gradle 插件源码,这些也正是 Android 开发者学习 gradle 的目的所在。
Gradle 构建周期
根据Build phases 文档 和 Settings file 文档可以知道,Gradle 构建周期分为:
-
Initialization
:Gradle支持单个和多项目构建。在 Initialization 阶段,Gradle 将会确定哪些项目将参与构建,并为每个项目创建一个 Project 对象实例。对于 Android 项目来说即为执行setting.gradle
文件。那么日常开发中setting.gradle
文件是如何书写的呢,假设当前应用中除 app 以外还有一个 a module 和 b module,那么setting.gradle
文件应类似如下:include ':app', ':a', ':b'
所以 Gradle 将会为它们三个分别创建一个 Project 对象实例。
-
Configuration
:在这一阶段项目配置对象,所有项目的构建脚本将会被「执行」,这样才能够知道各个 task 之间的依赖关系。需要说的一点是,这里提到的「执行」可能会稍有一些歧义:task a { } task testBoth { // 依赖 a task 先执行 dependsOn("a") println '我会在 Configuration 和 Execution 阶段都会执行' doFirst { println '我仅会在 testBoth 的 Execution 阶段执行' } doLast { println '我仅会在 testBoth 的 Execution 阶段执行' } }
写在 task 闭包中的内容是会在
Configuration
中就执行的,例如上面的dependsOn("a")
和println
内容;而 doFirst {}/doLast {} 闭包中的内容是在Execution
阶段才会执行到(doFirst {}/ doLast {}
实际上就是给当前 task 添加 Listener,这些 Listeners 只会在当前 taskExecution
阶段才会执行)。建议各位读者将这个 task 复制黏贴到 app 的
build.gradle
中,然后观察点击 Android Studio 的 Sync 按钮(含Configuration
阶段)和在命令行输入./gradlew app:testBoth
时命令行(testBoth
的Execution
阶段)输出的结果。便可理解上述闭包内容的执行过程。当然你也可以查看 Settings file 中的案例来理解。对于 Android 项目来说即为执行各个 module 下的
build.gradle
文件,这样各个build.gradle
文件中的 task 的依赖关系就被确认下来了(例如assembleDebug
task 的执行依赖于其他 tasks 先执行,而这个依赖关系的确定就是在Configuration
阶段)。 -
Execution
:task 的执行阶段。首先执行doFirst {}
闭包中的内容,最后执行doLast {}
闭包中的内容。
在命令行中执行某一个 task,是可以清晰地看见每一个执行流程:
hook Gradle 构建过程
在命令行输入 ./gradlew xxx
并按下回车之后的执行过程中,就会执行上面的流程,再通过一张深入理解Android之Gradle中的图加深印象——
然而在笔者目前为止碰到的 Gradle 开发中,很少提及到上述的 hook 系列。用的比较多的一个是前文提到的 task 中的 doFirst {}/ doLast {}
,再一个就是 Project 在 Configuration
阶段结束的 hook,前文也提到**Configuration
阶段将会执行每一个 Project 的 build.gradle
文件**,那么如何监听这每一个 Project 的引入后的点呢?通过 Project.afterEvaluate ——
//afterEvaluate { Project project ->
// println 'hook afterEvaluate'
//}
task hook { Project project ->
afterEvaluate {
println 'hook afterEvaluate'
}
}
那么为什么要 hook Project 的 afterEvaluate
阶段呢?因为在 afterEvaluate
阶段的时候,当前 Project 内的 task 信息才能被掌握,例如想在 assembleDebug
task 前输出一段信息,那么在 app/build.gradle
中撰写以下代码:
// task badHook {
// tasks.findByName("assembleDebug").doFirst {
// println 'hook afterEvaluate from BadHook'
// }
// }
task assDHook {
afterEvaluate {
tasks.findByName("assembleDebug").doFirst {
println 'hook afterEvaluate from assHook'
}
}
}
此时如果调用 assembleDebug
task 的话,首先是 Initialization
阶段,再是 Configuration
阶段,该阶段中当 appProject 到达 afterEvaluate
阶段的时候 appProject 中的 tasks 信息将会全部被获取,当然这其中也包括 assembleDebug
task,所以此时再给它添加一个 doFirst {}
闭包便可以达到目的。而像 badHook
闭包的内容将会在 Initialization
阶段执行,此时 appProject 并没有获取全部的 task 信息,将会导致压根找不到 assembleDebug
的错误。
后文
本文中并没有总结太多的基础知识,基本都以外链的形势写在了文中,因为笔者认为现在已经有大量的文章写的很不错了,所以不想大费笔墨撰写,笔者挑了一些自认为较重要的点为后文做铺垫,另外,文中还有大量的外链是外链到官方文档的,所以建议各位读者在遇见问题的时候多多查阅文档。各位读者可以在下文的附录中阅读更为基础的 Gradle 知识:
附录
- 强推深入理解Android之Gradle
- 关于Android Gradle你需要知道这些(1)
- Gradle从入门到实战 - Groovy基础
- Gradle Android插件用户指南中文版
- ASOP Gradle 源码
我建了一个群,如果你对文章有什么疑问,或者想和我讨论 Android 技术,欢迎入群哈。