ext.kotlin_version = "1.4.10"
buildscript {
ext.kotlin_version = "1.4.10"
repositories {
google()
jcenter()
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
allprojects {
repositories {
google()
jcenter()
task clean(type: Delete) {
delete rootProject.buildDir
脚本中每一个 DSL 都对应 Gradle 核心模型对象中的一个方法,buildscript{...}、repositories{...}、dependencies{...}、allprojects{...}、task{...} 这些都能找到对应的方法,大家且随我一起看看源码,不用多深入,单看接口设计就可以了
哈哈,看到这里,很多函数是不是很眼熟,是不是就是我们在 build.gradle
脚本中写的 DSL 代码块呀!遇到没见过的 DSL 大家不妨到 Gradle 项目源码中翻一翻,脚本里面能写什么 DSL 大家也可以过来看看源码,ctrl+F12 一下就清楚了,注释写的还可以,AS 有翻译插件,大家要善用翻译看官方的解释
查看源码的思路其实一开始我也没想到,没想到只要看看接口声明就能搞清楚状况,感谢来自 Gradle 团队的推广视频:来自Gradle开发团队的Gradle入门教程
官方出品,就是不一样,比博客、文档、其他机构的教学视频有深度多了,还没看的小伙伴强烈建议看一看。开眼界,有点体会到使用源码的味道了,不知道大家 get 到没有 ヾ(≧O≦)〃嗷~
Hook 函数
Gradle 的 Hook 函数也有叫生命周期的,就像是我们给 Application 注册监听函数一样,Gradle 允许我们在构建阶段施加自己的影响
这部分内容很繁杂,我也是看了很多文章,仔细推敲之后才决定从这个角度写这块内容
从不同角度对 Hook 函数进行分组
Gradle 提供的 Hook 函数挺多的,有点乱哈,反正我刚看完就是这感觉。这些 Hook 函数之间有不同的设计考虑、角度,基于此我们尝试对这些 Hook 函数进行分组,以方面记忆、理解
这里我们就要结合上面所讲的 Gradle 核心模型了 ヾ(´∀`o)+ 知识点之间从来都是相互关联的
1. 从 Gradle 核心模型的角度看:
Gradle、Settings 都是全局对象,其提供的 hook 函数自然对整个构建过程起作用
Project 对象针对的是单个项目,构建过程中会有多个 Project 对象,自然 Project 对象提供的函数只能对本项目起作用
Root Project 对象有些特殊,既有可以对全局进行设置的 hook 函数,也有只对自身起作用的 hook 函数,大家熟悉的 allProject() 就是其中可以对全局进行设置的函数。一般 hook 全局设置都不在根脚本中写,而是选择在 settings 脚本里写
2. 从函数命名的角度看:
Evaluated
单词代表构建阶段执行的单个 .gradle
脚本
beforeEvaluated
执行该脚本之前做什么
afterEvaluated
执行该脚本之后做什么
Project
和 Evaluated
一个意思,但是 Project 比 Evaluated 含义大一点,Evaluated 专门是指每个 module 中的 .gradle
脚本。而 Project 是指某个 module 的整个构建过程或者可以理解为 Project 对象本身。在 hook 函数看上2者差不多,但还是要理解这2个单词的区别
beforeProject
执行该项目之前做什么
afterProject
执行该项目之后做什么
3. 从脚本执行的角度看:
我们在 .gradle
脚本中写的 hook 函数,肯定只能在该脚本执行后才能生效,要是在脚本中定义诸如 beforeEvaluated
这样的函数大家觉得有意义吗?此时脚本都执行了,你再定义该脚本执行前应该执行什么就太晚了,应该想办法在该脚本执行前写这个函数
4. 从构建流程的角度看:
Initialization
初始化阶段 --> 会执行 init.gradle、settings.gradle 这2个脚本。init.gradle 脚本不推荐使用,因为影响范围太广,谁知道什么时候就会造成未知的困扰。我们都是在 settings.gradle 脚本中写 hook 函数
Configuration
编译阶段 --> 子项目脚本中一般只写影响范围只在本项目的 hook 函数。但是要注意 hook 函数执行的流程,比如你在子项目脚本中写 beforeEvaluated 就是纯扯淡。脚本都运行起来了,还写这个 hook 有个啥用。所以要注意 hook 必须写在正确的位置,要结合整个构建流程来考虑 hook 函数的书写位置
Execution
执行阶段 --> 这个阶段我们写不了 hook 函数的,就算是能写又能怎么样呢,该怎么执行构建任务,已经在上一个环节都已经决定好了,这个阶段我们施加不了任何影响
5. 从函数设计的角度看:hook 函数分2种
一种是单个功能的 hook 函数,比如 beforeEvaluated,这类 hook 函数有很多
一种是 listener 函数,这类函数往往可以同时实现对个功能,addLisenter() 就是这类 hook 函数,可以添加各种监听。大多数单功能的 hook 函数都可以用 listener 来代替。另外 listener 还可以通过遍历的方式实现对所有的 Project 进行设置、操作
6. 从函数作用的目标角度看:
一种专门监听脚本的执行,比如 beforeEvaluated 就是
一种专门监听项目的执行,比如 beforeProject,gradle.allProject() 可以对所有项目对象进行操作
一种专门监听 task 的执行,比如 gradle.taskGraph.whenReady()
Hook 函数中 Gradle 3大对象初始化节点
因为整个构建过程很长,Gradle 3大对象不是一上来就都创建出来了,也是在构建过程中一步步才 new 出来的,在此之前我们就使用该对象是会报错的,所以我们必须明确 Gradle 3大对象 从哪个 hook 函数开始可以使用
换种说法 --> 我们虽然写的是脚本,但是实际代码还是要动态编译成 java 对象执行的,我们必须考虑在脚本中使用对象的时候,对象是不是已经初始化好了
1. Gradle 对象:
init.gradle 脚本执行过后,Gradle 就被创建出来了,所以我们在 settings.gradle 脚本里可以尽情使用 Gradle 对象。这也是 settings.gradle 脚本成为我们进行全局 hook 设置的原因
2. Setting 对象:
gradle.settingsEvaluated {
.... settings 对象可以使用了
settings.gradle 脚本执行过后,Setting 对象才算是可以让我们随便使用
3. Project 对象:
gradle.projectsLoaded {
.... project 对象可以使用了
settings.gradle 脚本执行过程中会把所有子项目的 Project 对象创建出来,projectsLoaded() 这个函数中我们就能拿到所有所有 project 对象了
其实在每个脚本中,都可以使用该脚本对应的核心模型对象,脚本都跑起来了,对象就肯定已经创建出来了。我们要注意的是在脚本中使用超出本脚本范围的对象
Settings.gradle 脚本可以使用的 Hook 函数
方法注释和日志结合起来一起看
include ':libs'
include ':app'
buildscript {
println("settings...")
// Setting 脚本执行前调用
gradle.beforeSettings {
// 在这里写明显无用
println("gradle.beforeSettings...")
// Setting 项目编译前调用
gradle.beforeProject {
// 在这里写明显无用
println("gradle.beforeProject...")
// Setting 脚本执行完成后调用
gradle.settingsEvaluated {
println("gradle.settingsEvaluated...")
// Setting 项目编译完成后调用
gradle.afterProject {
println("gradle.afterProject...")
// 所有项目脚本执行完后调用
gradle.projectsEvaluated {
println("gradle.projectsEvaluated...")
// 开始进入编译阶段时调用
gradle.projectsLoaded {
println("gradle.projectsLoaded...")
// 构建结束时调用
gradle.buildFinished {
println("gradle.buildFinished...")
// 对所有项目脚本进行设置
gradle.allprojects(new Action<Project>() {
@Override
void execute(Project project) {
// 在这里设置 beforeEvaluate 就能能作用了
project.beforeEvaluate {
println("gradle.allprojects.beforeEvaluate...")
project.afterEvaluate {
println("gradle.allprojects.afterEvaluate...")
// 编译阶段 Task 流程图计算出来后调用
gradle.taskGraph.whenReady {
println("gradle.taskGraph.whenReady...")
运行日志:
settings...
gradle.settingsEvaluated...
gradle.projectsLoaded...
> Configure project : --> 执行根目录脚本
gradle.beforeProject...
gradle.allprojects.beforeEvaluate...
root build.gradle...
gradle.afterProject...
gradle.allprojects.afterEvaluate...
root_project.afterEvaluate...
> Configure project :app --> 执行 app 壳工程脚本
gradle.beforeProject...
gradle.allprojects.beforeEvaluate...
app build.gradle...
gradle.afterProject...
gradle.allprojects.afterEvaluate...
> Configure project :libs --> 执行 libs 子项目脚本
gradle.beforeProject...
gradle.allprojects.beforeEvaluate...
libs build.gradle...
gradle.afterProject...
gradle.allprojects.afterEvaluate...
gradle.projectsEvaluated...
gradle.taskGraph.whenReady...
> Task :prepareKotlinBuildScriptModel UP-TO-DATE --> 执行所有 Task 任务,这里省略了
gradle.buildFinished...
BUILD SUCCESSFUL in 2s
子项目 build.gradle 脚本可以使用的 Hook 函数
Settings.gradle 脚本能写的,这里都可以写。但是写在这里又有什么用呢,都开始跑具体的子项目构建脚本了,你再给全局添加 hook 不就晚了,就算设置了,也只能在该脚本之后脚本才能生效
能写的 hook 其实就是这2个了,日志输出看上面的就行
project.beforeEvaluate {
// 写这里没用
println("project.beforeEvaluate...")
project.afterEvaluate {
println("root_project.afterEvaluate...")
Listener 类型的 hook 函数
Gradle Listener 监听也是推荐写在 Settings.gradle 脚本里,这个位置比较合适,root build.gradle 勉勉强强可以写,有几个 hook 函数会不起作用,因为其 hook 点都已经过去了
Gradle 添加 Listener 的方式很灵活,addListener() 函数接收的参数是 Object 对象,可以支持很多种类型的监听,详细看代码提示
1)addBuildListener()
函数可以代替一些全局设置的 hook 函数,里面的方法都是上面一些 hook 函数的重复
gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
println("gradle.addBuildListener.buildStarted...")
@Override
void settingsEvaluated(Settings settings) {
println("gradle.addBuildListener.settingsEvaluated...")
@Override
void projectsLoaded(Gradle gradle) {
println("gradle.addBuildListener.projectsLoaded...")
@Override
void projectsEvaluated(Gradle gradle) {
println("gradle.addBuildListener.projectsEvaluated...")
@Override
void buildFinished(BuildResult result) {
println("gradle.addBuildListener.buildFinished...")
2) TaskExecutionGraphListener
可以监控所有 task 函数的执行
gradle.addListener(new TaskExecutionGraphListener() {
@Override
void graphPopulated(TaskExecutionGraph graph) {
println("gradle.addListener.hasTask..."+graph.hasTask(":test"))
graph.getAllTasks().each {
it.doLast {
// ...
it.doFirst {
// ...
3) TaskExecutionListener
在 Task 执行前后添加钩子
gradle.addListener(object : TaskExecutionListener {
override fun beforeExecute(task: Task) {
override fun afterExecute(task: Task, state: TaskState) {
4) TaskActionListener
在 action 执行前后添加钩子
gradle.addListener(object : TaskActionListener {
override fun beforeActions(task: Task) {
override fun afterActions(task: Task) {
5) addTaskExecutionGraphListener
同 whenReady 的效果
gradle.getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
@Override
void graphPopulated(TaskExecutionGraph graph) {
6) DependencyResolutionListener
Gradle 会在所有子项目脚本执行后,进行依赖的决议。beforeResolve / afterResolve 方法就是 Gradle 为这项工作单独设置的一对 hook
gradle.addListener(object : DependencyResolutionListener {
override fun beforeResolve(dependencies: ResolvableDependencies) {
override fun afterResolve(dependencies: ResolvableDependencies) {
部分函数使用注意点
1. 判断是否包含指定 Task 要加:号
这个挺坑人的,一开始我都想到这里,发现总也找不到指定 Task,还有自定义 Task 要不在构建过程中不调用执行的话,Task 执行流程图里是找不到这个 Task 的
task test {
println("task--test...")
doLast {
println("task--test.doLast...")
gradle.addListener(new TaskExecutionGraphListener() {
@Override
void graphPopulated(TaskExecutionGraph graph) {
println("gradle.addListener.hasTask..."+graph.hasTask(":test"))
graph.getAllTasks().each {
it.doLast {
// ...
it.doFirst {
// ...
所有 hook 流程图
图来自掘金小册,大体都在图里面,大家最后结合这张图再理解下
Hook 函数的意义
是给大家自定义 Task、Plugin 准备的,自定义的任务、插件中的任务你总得给他设置一个执行时机不是,所以 Hook 勾子就成了一个好的选择
Project API
Gradle 脚本中我们使用最多的,最让人头疼的就是 Project 对象的 API 了,感谢:深度探索 Gradle 自动化构建技术(三、Gradle 核心解密),我在这里整理一下
1. getAllprojects()
这个和 gradle.allProjects()
是一样的
// index = 0 是 Root buils.gradle
project.getAllprojects().eachWithIndex { Project project, int index ->
if (index == 0) println("index 0 :root project") else println("index $index : $project.name")
2. getSubprojects()
获取所有子项目
project.getSubprojects().eachWithIndex { Project project, int index ->
println("index $index : $project.name")
3. getParent()
根项目对象获取到的是 null
project.getParent()
4. getRootProject()
project.getRootProject()
5. project()
通过 name 获取到指定子项目,当然 Gradle 对象也有 find 方法,但是这个写法我是第一次看见,有点意思
project("app") { Project project ->
apply plugin: 'com.android.application'
6. allprojects() / subprojects
这个不用说了,大家都熟悉,subprojects 是不会操作跟项目的
7. plugins
if (project.plugins.hasPlugin("com.android.library")) {
apply from: '../publishToMaven.gradle'
ext 扩展属性
ext{...} 这个 DSL 代码段也是 Project 对象提供的方法。ext{...} 大家都不陌生吧,都是用来做全局参数、依赖的配置。ext{...} 是 Gradle 提供的、让我们定义所需全局变量的代码块,简称:扩展属性
1. 定义 ext
一般我们在根项目脚本中写 ext{...}
// root build.gradle
ext {
tag = "BB"
age = 2
2. 使用 ext
大家注意这2种使用方式自动提示的环节,很多人抱怨没有代码提示好难用
println( "name = $tag" )
println( "age = $age" )
println( "name = ${rootProject.ext.tag}" )
println( "age = ${rootProject.ext.age}" )
3. 进阶使用 1 -- 抽象公共配置脚本
这个好理解,ext{...} 写在根目录脚本里有的人说应该拆分、专人专事,于是我们专门写一个 ext{...} 的脚本出来,该脚本一般都叫:config.gradle
,然后 apple from 导入本目录脚本就能用了
ext{...} 编译过后是 Project 对象中的一个成员属性,ext{...} 中一般我们都是写 Map 来配置一些属性
// config.gradle
ext {
// 不同的 DSL 配置块,推荐专门写一个 map 出来,这样方便查找
android = [
compileSdkVersion: 29,
applicationId : "com.bloodcrown.myapplication22",
minSdkVersion : 21,
targetSdkVersion : 29,
versionCode : 1,
versionName : "1.0"
// root builf.gradle
apply from: this.file("config.gradle")
buildscript {
ext.kotlin_version = "1.4.10"
repositories {
google()
jcenter()
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// app build.gradle
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
defaultConfig {
applicationId rootProject.ext.android.applicationId
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
4. 进阶使用 2 -- 抽象子项目脚本基类
这个也不难理解,我们的项目要是有多个 子项目 ,每个子项目中 重复的脚本配置 写起来也是让人讨厌的事,尤其是休休改改的情况下很讨厌的,我们应该延续 java 相面对象中的思路:一处修改,处处使用
这里我们需创建脚本基类,该脚本一般都叫:base_build.gradle
,也是通过 apple from 导入子项目脚本就可以了
// base_build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
defaultConfig {
applicationId rootProject.ext.android.applicationId
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
kotlinOptions {
jvmTarget = '1.8'
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// app build.gradle
apply from: rootProject.file("base_build.gradle")
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.1.0'
5. 进阶使用 3 -- 使用遍历代替一行行手写
可能使有的项目依赖实在是太多了吧,于是出现了一种在 ext{...} 中写依赖,然后在脚本中遍历添加依赖的方式,方便是放便了,但是看着有些不习惯
dependencies = [...]
annotationd_ependencies = [...]
dependencies.each { k, v -> implementation v }
annotationd_ependencies.each { k, v -> implementation v }
嘛~ 这种思路不是太能接受,大家看需求吧
6. ext{...} 中一样可以写代码
出了写一些配置参数,我们一些可以写代码的,看个例子:
ext {
versionName = rootProject.ext.android.versionName
versionCode = rootProject.ext.android.versionCode
versionInfo = 'App的第2个版本,上线了一些最基础核心的功能.'
destFile = file('releases.xml')
if (destFile != null && !destFile.exists()) {
destFile.createNewFile()
this.project.afterEvaluate { project ->
def buildTask = project.tasks.findByName("build")
doLast {
buildTask.doLast {
writeTask.execute()
task writeTask {......}
(project.tasks.findByName(chmodTask.name) as Task).dependsOn(mergeResourcesTask.taskDependencies.getDependencies(mergeResourcesTask))
(project.tasks.findByName(mcPicTask.name) as Task).dependsOn(project.tasks.findByName(chmodTask.name) as Task)
mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))
复制代码