作者:renxhui
链接:https://juejin.cn/post/6937208620337610766
理解Gradle
Gradle是一个可以构建工具,他可以app的编译打包工作,但是我们学习Gradle不能把它当做一个工具来学,当我们把他当做工具来学的话,我们的目标就是,会写,会配置脚本就就OK了,但是真实的工作中的需求是复杂且多变的,我们除了会用,还要了解为什么这么用,所以我们需要把他当成编程框架来看,这样对于复杂的需求会更加的得心应手
回忆一下我们在项目中使用Okhttp
的时候是怎么使用的
- 首先Okhttp 使用java语言写的,所以你必须要懂java语法
- 其次Okhttp定义了很多API用于请求网络,所以我要学习Okhttp的各种API,以满足我们不同的需求
一样的思路用到Gradle上
- 首先Gradle是用Groovy写的,所以我们懂Groovy的语法
- 其次Gradle定义了自己的API,所以我们要学习Gradle的API
- 最后Gradle中有插件的概念,不同的插件完成不同的任务,比如我们的Android的打包编译用到的Android的插件,所以我们要学习Android 插件定义的各种API
最终我们需要掌握基本语言Groovy,然后掌握Gradle的API还有他的生命周期,最后掌握Android的插件API,这样就可以去写Gradle了
这里是Gradle API文档,回头想想原来写Gradle脚本原来就是玩转Gradle的API,这样一想好像也还好,并没有想象中那么难,毕竟都是一个个框架学过来的
Gradle基本组件
下面我们就来认识一下Gradle框架的基本组件
先来看下这张图,就是一个普通的Android工程,这个工程包括三个Module
Gradle中每一个带编译的工程都是一个Project(例如上图的app,mylibrary和myLibary2)每一个Project在构建的时候都要包含多个Task,比如一个Android APK的编译可能包括,Java编译的Task,JNI编译的Task,打包生成APK的Task等
而一个Project到底包含多少个Task,是由插件决定的,插件就是来定义Task然后执行Task的,一个插件可以包含多个Task
Gradle是一个框架,他负责定义流程和规则,而具体的工作都是通过插件实现的(类似于淘宝和淘宝商家,淘宝负责制定规则和流程,淘宝商家负责真正的卖东西),比如:编译Java的插件,编译Groovy的插件,编译Android APP的插件
现在我们知道了,一个带编译的工程是一个Project,而一个Protect在构建的时候是由一个个Task定义和执行
那么现在请问上图的图片中有多少个Project?
答案是3个
每一个Libary和每一个App都是单独的Project,每一个Project根目录下都要有一个build.gradle,build.gradle就是Project的编译脚本
所以此gradle工程,包含3个Project,我们可以选择独立编译每一个Project
- cd进入该Project的目录
- 然后执行gradle任务(比如:gradle assemble)
但是如果有100Project 难道就要单独编译100次吗,有没有可以直接在根目录直接全部编译这100个Project?
肯定是有的,只要在gradle工程的根目录添加build.gradle和setting.gradle
- 根目录的build.gradle ,主要是配置其他子Project,比如为其他子project添加属性
- 根目录setting.gradle,他用来告诉Gradle这个工程一共包括多少个Project,如下图
Gradle的命令简介
gradle projects 查看工程信息
(截图中命令为Mac电脑上的命令)
执行这个命令可以查看这个工程到底包含多少个子Project
gradle tasks查看任务信息
gradle project-path:tasks
查看某个project下的Tasks,project-path
是目录名,后面需要跟冒号,在根目录你需要指定你想看那个project的路径
gradle task-name 执行任务
上面我们查看了所有的Task,现在我们看执行其中一个
最后 Task和Task之间是有依赖关系的,比如assemble task
就是依赖其他Task,如果执行这个Task,那就需要他所依赖的其他Task先执行assemble
才能最终输出
由于Task之间是有依赖关系的,所以我们可以自定义Task让他依赖于assemble Task
,那么当assemble Task
执行的时候就会先执行我们自定义的Task
Gradle 的工作流程
Gralde 工作流程包括三个阶段
初始化阶段
Initiliazation
初始化阶段,主要任务是创建项目的层次结构,为每一个项目创建一个Project
对象,对应就是执行setting.gradle
,一个setting.gradle
对应一个setting
对象,在setting.gradle
中可以直接调用其中的方法,Settings API文档
配置阶段
下一个阶段就是Configration
配置阶段,它主要是配置每个Project
中的build.gradle
,在初始化阶段和配置阶段之间,我们可以加入Hook
,这是通过API添加的
Configration
阶段完成之后,整个build的project以及内部的Task关系都确定了,我们可以通过gradle
的getTaskGraph
方法访问,对应的类为TaskExecutionGraph
,TaskExecutionGraph API文档
我们知道每个Project都由多个Task组成,每个Task之间都有依赖关系,Configuration
阶段会建立一个有向图来描述Task之间的依赖关系,这里也可以添加一个Hook,当有向图建立完成之后进行一些操作
每个build.gradle
对应一个Project
对象,在初始化阶段创建,这里是Project API文档
执行阶段
最后一个阶段就是执行阶段,这一阶段的主要是执行Task,这里也可以加Hook,当任务执行完之后做一些事情
添加Gradle构建过程的监听
在setting.gradle中加入这段代码
gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
println'buildStarted'
}
@Override
void settingsEvaluated(Settings settings) {
println 'settings 评估完成(settings.gradle 中代码执行完毕)'
println '这里project 还没有初始化完成'
}
@Override
void projectsLoaded(Gradle gradle) {
println '项目结构加载完成(初始化阶段结束)'
println '初始化结束,这里已经完成project的初始化,可访问根项目:' + gradle.rootProject
}
@Override
void projectsEvaluated(Gradle gradle) {
println '所有项目评估完成(配置阶段结束)'
}
@Override
void buildFinished(BuildResult result) {
println '构建结束 '
}
})
执行gradle assemble任务
L-96FCG8WP-1504:gradle renxiaohui$ ./gradlew assemble
setting----1518204878
setting --- homedir/Users/renxiaohui/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx/gradle-5.4.1
setting --- userhomedir/Users/renxiaohui/.gradle
setting -----paraentnull
"hahah"
settings 评估完成(settings.gradle 中代码执行完毕)
这里project 还没有初始化完成
项目结构加载完成(初始化阶段结束)
初始化结束,这里已经完成project的初始化,可访问根项目:root project 'gradle'
> Configure project :app
gradle----1518204878
gradle --- homedir/Users/renxiaohui/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx/gradle-5.4.1
gradle --- userhomedir/Users/renxiaohui/.gradle
gradle -----paraentnull
所有项目评估完成(配置阶段结束)
> Task :app:assemble
构建结束
BUILD SUCCESSFUL in 5s
Hook点
这里借用Gradle基础 构建生命周期和Hook技术 文章中的图片,来展示整个生命周期何时进行Hook
Gradle在各个阶段都提供了回调,在添加监听器的时候需要注意一点,监听器要在回调的声明周期之前添加,一般情况加在setting.gradle中
beforeProject
和beforeEvaluate
这俩个方法的调用时机是一样的,只不过beforeProject调用应用于所有项目,beforeEvaluate之应用于调用的Project
afterProject
和afterEvaluated
也是一样的道理
获取构建的耗时时间
在setting.gradle中加入如下代码,gradle.taskGraph.beforeTask
这个方法会在Task调用之前回调
long beginOfSetting = System.currentTimeMillis()
gradle.projectsLoaded {
println '初始化阶段,耗时:' + (System.currentTimeMillis() - beginOfSetting) + 'ms'
}
def beginOfConfig
def configHasBegin = false
def beginOfProjectConfig = new HashMap()
gradle.beforeProject { project ->
if (!configHasBegin) {
configHasBegin = true
beginOfConfig = System.currentTimeMillis()
}
beginOfProjectConfig.put(project, System.currentTimeMillis())
}
gradle.afterProject { project ->
def begin = beginOfProjectConfig.get(project)
println '配置阶段,' + project + '耗时:' + (System.currentTimeMillis() - begin) + 'ms'
}
def beginOfProjectExcute
gradle.taskGraph.whenReady {
println '配置阶段,总共耗时:' + (System.currentTimeMillis() - beginOfConfig) + 'ms'
beginOfProjectExcute = System.currentTimeMillis()
}
gradle.taskGraph.beforeTask { task ->
task.doFirst {
task.ext.beginOfTask = System.currentTimeMillis()
}
task.doLast {
println '执行阶段,' + task + '耗时:' + (System.currentTimeMillis() - task.beginOfTask) + 'ms'
}
}
gradle.buildFinished {
println '执行阶段,耗时:' + (System.currentTimeMillis() - beginOfProjectExcute) + 'ms'
}
运行gradle assemble
L-96FCG8WP-1504:gradle renxiaohui$ ./gradlew assemble
初始化阶段,耗时:25ms
> Configure project :
配置阶段,root project 'gradle'耗时:78ms
> Configure project :app
配置阶段,project ':app'耗时:19ms
> Configure project :mylibrary
配置阶段,project ':mylibrary'耗时:7ms
> Configure project :mylibrary2
配置阶段,project ':mylibrary2'耗时:10ms
配置阶段,总共耗时:391ms
> Task :app:preBuild
执行阶段,task ':app:preBuild'耗时:0ms
> Task :app:preDebugBuild
执行阶段,task ':app:preDebugBuild'耗时:0ms
....
当Gradle执行脚本的时候会生成对应的实例,Gradle主要有三种对象,每种对象对于对应一种脚本
- Gradle对象:在项目初始化时构建,全局单例存在,只有这一个对象
- Project对象:每一个build.gradle都会转换成一个Project对象
- Settings对象:Setting.gradle 会转变成一个Settings对象
官方文档Gradle 介绍
Gradle对象 API介绍
Gradle对象 API文档 具体的API看上面的文档,下面介绍一些可能用到的API
gradle.afterProject/gradle.beforeProject
这俩个方法是在每个Project执行完毕之后,或者开始执行之前调用的
在setting.gradle写入代码
gradle.afterProject {
println 'gradle.afterProject 调用'
}
gradle.beforeProject {
println 'gradle.beforeProject 调用'
}
执行./gradlew clean
L-96FCG8WP-1504:gradle renxiaohui$ ./gradlew clean
setting.gradle 开始执行
初始化阶段,耗时:3ms
> Configure project :
gradle.beforeProject 调用
gradle.afterProject 调用
配置阶段,root project 'gradle'耗时:43ms
> Configure project :app
gradle.beforeProject 调用
com.aliyun.gradle
gradle----2006449733
gradle --- homedir/Users/renxiaohui/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx/gradle-5.4.1
gradle --- userhomedir/Users/renxiaohui/.gradle
gradle -----paraentnull
com.aliyun.gradle
gradle.afterProject 调用
配置阶段,project ':app'耗时:19ms
> Configure project :mylibrary
gradle.beforeProject 调用
gradle.afterProject 调用
配置阶段,project ':mylibrary'耗时:5ms
> Configure project :mylibrary2
gradle.beforeProject 调用
gradle.afterProject 调用
配置阶段,project ':mylibrary2'耗时:5ms
配置阶段,总共耗时:115ms
> Task :clean
执行阶段,task ':clean'耗时:0ms
> Task :app:clean
执行阶段,task ':app:clean'耗时:0ms
> Task :mylibrary:clean
执行阶段,task ':mylibrary:clean'耗时:0ms
> Task :mylibrary2:clean
执行阶段,task ':mylibrary2:clean'耗时:0ms
执行阶段,耗时:8ms
Gradle对象 其他API
API | 描述 |
---|---|
TaskExecutionGraph getTaskGraph() | 获取Project中Task的关系图 |
buildStarted | 当构建开始回调 |
settingsEvaluated | 当setting.gradle评估完成回调 |
projectsLoaded | 在初始化阶段中projects创建完成回调 |
projectsEvaluated | 配置阶段完成回调 |
buildFinished | 构建完毕回调 |
addBuildListener | 添加构建监听 |
TaskExecutionGraph API介绍
API | 描述 |
---|---|
addTaskExecutionGraphListener | 给任务执行图添加监听 |
addTaskExecutionListener | 给任务的执行添加监听 |
whenReady | 当任务执行图填充完毕被调用 |
beforeTask | 当一个任务执行之前被调用 |
afterTask | 当一个任务执行完毕被调用 |
hasTask | 查询是否有这个task |
getAllTasks | 获取所有的Task |
Set getDependencies(Task task) | 返回参数Task的依赖关系 |
Project 介绍
介绍一些可能用到API,具体的自己看文档
API | 描述 |
---|---|
getRootProject() | 获取根Project |
getRootDir | 返回根目录文件夹 |
getBuildDir | 返回构建目录,所有的build生成物都放入到这个里面 |
setBuildDir(File path) | 设置构建文件夹 |
getParent() | 获取此Project的父Project |
getChildProjects | 获取此Project的直系子Project |
setProperty(String name, @Nullable Object value) | 给此Project设置属性 |
getProject() | 但会当前Project对象,可用于访问当前Project的属性和方法 |
getAllprojects | 返回包括当前Project,以及子Project的集合 |
allprojects(Closure configureClosure) | 返回包括当前Project,以及子Project的集合到闭包中 |
getSubprojects | 返回当前Project下的所有子Project |
subprojects(Closure configureClosure) | 返回当前Project下的所有子Project到闭包中 |
Task task(String name) | 创建一个Task,添加到此Project |
getAllTasks(boolean recursive) | 如果recursive为true那么返回当前Project和子Project的全部Task,如果为false只返回当前Project的所有task |
getTasksByName(String name, boolean recursive) | 根据名字返回Task,如果recursive为true那么返回当前Project和子Project的Task,如果为false只返回当前Project的task |
beforeEvaluate(Closure closure) | 在Project评估之前调用 |
afterEvaluate(Closure closure); | 在项目评估之后调用 |
hasProperty(String propertyName) | 查看是否存在此属性 |
getProperties() | 获取所有属性 |
findProperty(String propertyName); | 返回属性的value |
dependencies(Closure configureClosure) | 为Project配置依赖项 |
buildscript(Closure configureClosure) | 为Project配置build脚本 |
project(String path, Closure configureClosure) | 根据路径获取Project实例,并在闭包中配置Project |
getTasks() | 返回此Project中所有的tasks |
属性
在我们写java代码的时候,当遇到很多类同时用的一些方法,我们会把这个共用的方法,抽取到Utils作为公共方法使用,而在Gradle中也会存在相同的问题,那么在Gradle中改如何抽取公共方法呢?
在Gradle中提供了extra property
的方法,extra property
是额外属性的意思,第一次定义需要使用ext前缀表示额外属性,定义好了之后,再次存取就不需要ext前缀了,ext额外属性支持Gradle和Project对象
在local.properties中加入一个新属性
在setting.gradle 中取出sdk.api属性,并且设置给Gradle对象,然后打印Gradle对象刚刚赋值的属性
def text(){
Properties properties = new Properties()
File propertyFile = new File(rootDir.getAbsolutePath() + "/local.properties")
properties.load(propertyFile.newDataInputStream())
gradle.ext.api = properties.getProperty('sdk.api')
println(gradle.api)
}
text()
输出
setting.gradle 开始执行
"hahah"
接下来我们定义一个utils.gradle作为一个公共的类,为其他的build.gradle提供公共的方法
//这个方法取出AndroidManifest.xml中的包名
def getxmlpackage(boolean x){
// 注释1 此处的 project 是指的那个project?
def file=new File(project.getProjectDir().getPath()+"/src/main/AndroidManifest.xml");
def paser = new XmlParser().parse(file)
return paser.@package
}
//注释2 此处的ext 是谁的ext?
ext{
//除了这种赋值ext.xxx=xxx,还有这种闭包形式的赋值
getpackage = this.&getxmlpackage
}
在 app模块
mylibrary模块
和mylibrary2模块
的build.gradle 引入 utils.gradle
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
...
println(getpackage(true))
输出
> Configure project :app
取出的包名为= com.renxh.gradle
配置阶段,project ':app'耗时:18ms
> Configure project :mylibrary
取出的包名为= com.renxh.mylibrary
配置阶段,project ':mylibrary'耗时:73ms
> Configure project :mylibrary2
取出的包名为= com.renxh.mylibrary2
配置阶段,project ':mylibrary2'耗时:59ms
配置阶段,总共耗时:271ms
看到了输出,那就应该知道上面注释1
和注释2
的答案了吧
- 注释1处的project,指的是 谁加载utils.gradle 就是谁的 project
- 注释2 同样也是谁加载utils.gradle就是为谁的Project加载属性
通过这种方式,我们就可以把一些常用的函数放在utils.gradle中,然后为他加载的project设置一些ext属性
有关文件操作的API
定位文件
this.getText("utils.gradle")
def getText(String path) {
try {
// 不同与 new file 的需要传入 绝对路径 的方式,
// file 从相对于当前的 project 工程开始查找
File mFile = file(path)
println mFile.text
} catch (GradleException e) {
println e.toString()
return null
}
}
拷贝文件
等assemble
任务完成之后,把生成的的app-debug.apk
改名为renxhui.apk
,然后拷贝到项目根目录,不拷贝release
相关文件文件
tasks.getByName("assemble") {
it.doLast {
copy {
// 既可以拷贝文件,也可以拷贝文件夹
// 这里是将 app moudle 下生成的 apk 目录拷贝到
// 根工程下的 build 目录
from file("build/outputs/apk")
into getRootDir().path+ "/apk/"
rename('app-debug.apk', 'renxhui.apk')
exclude { details ->
details.file.name.contains('release') }
}
}
}
文件树
遍历build/outputs/apk
文件夹,打印出每个文件的文件名
fileTree("build/outputs/apk"){ freeTree ->
freeTree.visit{fileTreeElement->
println "遍历,文件名为="+"$fileTreeElement.file.name"
}
}
输出
遍历,文件名为=release
遍历,文件名为=app-release-unsigned.apk
遍历,文件名为=output.json
遍历,文件名为=debug
遍历,文件名为=output.json
遍历,文件名为=app-debug.apk
dependencies依赖相关
dependencies {
implementation('org.hibernate:hibernate:3.1') {
//在版本冲突的情况下优先使用3.1版本
force = true
//排除特定的依赖
exclude module: 'cglib' //by artifact name
exclude group: 'org.jmock' //by group
exclude group: 'org.unwanted', module: 'iAmBuggy' //by both name and group
//禁用依赖传递
// 传递依赖:A => B => C ,B 中使用到了 C 中的依赖,
// 且 A 依赖于 B,如果打开传递依赖,则 A 能使用到 B
// 中所使用的 C 中的依赖,默认都是打开,即 true
transitive = false
}
}
Task
Task是Gradle中的一种数据类型,他代表要执行的工作,不同的插件可以添加不同的Task,每一个Task都要和Project关联
Task的创建
由于Task是和Project相关联的,所以我们使用Project
中的task(String name)
方法来创建
task myTask <==myTask 是新建 Task 的名字
task myTask { configure closure }
task myTask(type: SomeType)
task myTask(type: SomeType) { configure closure }
task myTask(dependsOn:SomeTask){configure closure}
task aa{
println "ccc"
doFirst{
println "aaa"
}
doLast{
println"bbb"
}
}
- 一个Task包含多个Action,Task有俩个函数,
doLast和doFrist
- doLast:指的是任务执行完之后在进行操作,
- doFrist:指的是任务执行之前进行操作,Action 就是一个闭包
- Task创建的时候可以指定Type,通过
Type:name
告诉Gradle新建的Task对象会从那个Task基类派生,比如task myTask(type:Copy)
创建的Task继承于Copy,是一个Copy Task - 当我们使用task myTask { configure closure },还括号是一个闭包,这样会导致Gradle创建这个Task之后,返回用户之前,会先执行闭包中内容
- 创建Task的时候可以使用dependsOn属性,表示当前Task依赖 xx Task,当前Task执行的时候需要先执行xx Task
task aa{
doFirst{
println "aaa"
}
doLast{
println"aa"
}
}
task bb(dependsOn:'aa') {
doFirst{
println "bb"
}
doLast{
println 'bb'
}
}