Gradle学习系列(二):Gradle核心探索

概述

又开始了一个新的系列,这个系列学习Gradle,目标就是彻底理解Gradle,主要还是做下自己理解的笔记,防止忘记

Gradle系列(一):Groovy学习

Gradle学习系列(二):Gradle核心解密

理解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 查看工程信息

执行这个命令可以查看这个工程到底包含多少个子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关系都确定了,我们可以通过gradlegetTaskGraph方法访问,对应的类为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中

beforeProjectbeforeEvaluate这俩个方法的调用时机是一样的,只不过beforeProject调用应用于所有项目,beforeEvaluate之应用于调用的Project

afterProjectafterEvaluated也是一样的道理

获取构建的耗时时间

在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对象:Seeting.gradle 会转变成一个Seetings对象

官方文档Gradle 介绍

Gradle对象 API介绍

Gradle对象 API文档
具体的API看上面的文档,下面介绍一些可能用到的API

gradle.afterProject/gradle.beforeProject

这俩个方法是在每个Project执行完毕之后,或者开始执行之前调用的

在seeting.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介绍

TaskExecutionGraph API文档

API 描述
addTaskExecutionGraphListener 给任务执行图添加监听
addTaskExecutionListener 给任务的执行添加监听
whenReady 当任务执行图填充完毕被调用
beforeTask 当一个任务执行之前被调用
afterTask 当一个任务执行完毕被调用
hasTask 查询是否有这个task
getAllTasks 获取所有的Task
Set getDependencies(Task task) 返回参数Task的依赖关系

Project 介绍

Project API 文档

介绍一些可能用到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,添加到此Priject
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 中的依赖,默认都是不打开,即 false
     transitive = false
   }
 }

Task

Task是Gradle中的一种数据类型,他代表要执行的工作,不同的插件可以添加不同的Task,每一个Task都要和Project关联

Task API文档

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和doFristdoLast:指的是任务执行完之后在进行操作,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'
    }
}

参考

深入理解Android之Gradle

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

猜你喜欢

转载自blog.csdn.net/qq_34760508/article/details/114543942