Gradle 相关总结

Gradle 相关总结
APT 和 AGPTransform 区别
Gradle+Transform+Asm自动化注入代码
Android 360加固+Walle多渠道自动化打包上传蒲公英

gradle 是什么?

  • 官方解释:Gradle is an open-source build automation tool focused on flexibility and performance. Gradle build scripts are written using a Groovy or Kotlin DSL

  • 使用纯Java语言编写的,基于Ant、Maven概念开源的自动化构建工具,专注于灵活性和性能的构建脚本,摒弃了基于XML的繁琐配置,采用 Groovy 或 Kotlin 的 特定领域语言(DSL) 来编写。

  • Gradle 作为一个自动化构建工具,Gradle 是通过组织一系列 Task 来最终完成自动化构建的,所以 Task 是 Gradle 中最重要的概念,Gradle保证这些Task按照其依赖关系的顺序执行,并且每个任务仅执行一次,这些任务形成有向无环图,在执行任何Task之前,Gradle会构建完整的依赖关系图。比如以Android开发构建 APK 为例,整个过程要经过 资源的处理,javac 编译,dex 打包,签名等等步骤,而这一系列步骤就对应到 Gradle 里的Task,Gradle 可以类比做一条流水线,而Task 就好比作流水线上的工人,而每个工人负责自己不同的事情,经过每个人处理最终生成完整的构建产物,如果对责任链模式了解就知道这个概念有点类似责任链模式。Gradle文档

Gradle基础

基本概念

开始学习Gradle前,我们应该先大概了解关于Gradle中的一些基础概念:

  • Gradle脚本是配置脚本。在脚本执行时,它将配置特定类型的对象。例如,在执行构建脚本时,它将配置类型为Project的对象。该对象称为脚本的委托对象,比如:在脚本执行时会为build.gradle 生成对应的Project对象。下表展示了每种Gradle脚本的委托对象:
Type of script Delegates to instance of
Build script (build.gradle) Project
Init script Gradle
Settings script(settings.gradle) Settings

你可以在脚本中使用委托对象的属性和方法。

  • 每个Gradle脚本都实现Script接口。该接口定义了可以在脚本中使用的许多属性和方法,如下图:


    script (2).png

    script.png

1. Gradle构建生命周期 Build Lifecycle

1.1初始化阶段(Initialization):
  • Gradle支持单项目和多项目构建。在初始化阶段,Gradle确定将要参与构建的项目,并为每个项目创建一个Project实例。

  • Init Script 读取全局脚本,初始化一些全局通用属性,如:获取Gradle User Home 目录(新建init.gradle脚本打印信息)、Gradle Home(新建*.gradle脚本打印信息)、Gradle 版本信息;

  • Settting Script 初始化一次构建所参与的所有模块,对应setting.gradle脚本,组织管理项目中所有模块的脚本,参加构建的脚本都需要在此脚本中进行配置申明;

  • 初始化阶段主要做的事情是读取setting.gradle脚本中配置的模块,有哪些模块需要参与本次的构建,然后为对应的模块创建 Project 对象,settings.gradle 是负责配置项目的脚本对应 Settings 类,gradle 构建过程中,会根据 settings.gradle 生成 Settings 的对象;

1.2配置阶段(Configuration):
  • 开始加载所有在setting.gradle配置声明的所有模块Build Script即执行build.gradle语句,根据build.gradle脚本配置对应模块的Project对象,并创建模块中对应Task,最终根据所有的Task生成依赖图(DAG有向无环图);

  • 配置阶段主要做的事情是对Initialization阶段创建的Project对象进行配置,这时候会执行对应模块的build.gradle 脚本,并且会生成要执行的 Task;

1.3执行阶段(Execution):

*执行阶段主要做的事情就是执行 Task,进行主要的构建工作;

小结:

  • 在Initialization阶段首先会生成一个全局唯一的Gradle对象和Settings对象,其次是根据setting.gradle脚本中的配置信息为不同模块生成Project对象。

  • Configuration阶段主要是配置或初始化或配置Initialization阶段生成的Project对象和生成Project对应的Task依赖图。

  • 在Gradle脚本中,setting.gradle、build.gradle它们是配置脚本,脚本在执行时,实际上是生成或配置了一个特殊类型的对象,比如:Init Script → Gradle对象、Settings Script → Settings对象、Build Script → Project对象;这些对象又称脚本的代理对象,代理对象上的每个属性、方法都可以在脚本中使用

  • 每个Gradle脚本都实现了Script接口,由0个或多个脚本语句 (statements)和脚本块组成。 脚本语句可以包含:函数调用、属性赋值和本地变量定义,脚本块则是一个方法调用,传入一个 配置闭包,执行时对代理对象进行配置。

Initialization(初始化阶段)

1.Init Script(初始化脚本)

涉及到的脚本执行顺序如下:

GRADLE_USER_HOME/init.gradle → GRADLE_USER_HOME/init.d/*.gradle

这一步生成 Gradle 对象,并且会优先执行GRADLE_USER_HOME/init.gradle脚本和GRADLE_USER_HOME/init.d/*.gradle脚本,如果存在的话,下面是Gradle类部分源码如下:

public interface Gradle extends PluginAware {
    String getGradleVersion();//执行此次构建的Gradle版本;
    File getGradleUserHomeDir();//Gradle User Home目录;
    File getGradleHomeDir();//执行此次构建的Gradle目录;
    Project getRootProject()// 获取当前构建的根项目
    StartParameter getStartParameter();//获取传入当前构建的所有参数
    TaskExecutionGraph getTaskGraph();//获取当前构建的task graph,此对象在taskGraph.whenReady { } 后才具有内容
}

Gradle父类 PluginAware 源码 :

public interface PluginAware {
      PluginContainer getPlugins();
      void apply(Closure closure);
      void apply(Action<? super ObjectConfigurationAction> action);
      void apply(Map<String, ?> options);
      PluginManager getPluginManager();
 }

2. Settings Script(设置脚本)

  • settings.gradle脚本文件由Gradle通过命名约定确定。该文件的默认名称是settings.gradle,settings.gradle脚本文件在初始化阶段执行。多项目构建必须在多项目层次结构的根项目中具有settings.gradle文件。这是必需的,因为settings.gradle脚本文件定义了哪些项目正在参与多项目构建。对于单项目构建,设置文件是可选的。

  • settings.gradle负责配置声明参与构建过程的模块,对应 Settings 类,Gradle 构建过程中,Gradle会根据 settings.gradle脚本生成 Settings 的对象。Settings部分源码:

    public interface Settings extends PluginAware, ExtensionAware {
      String DEFAULT_SETTINGS_FILE = "settings.gradle";
      void include(String... projectPaths);
      void includeFlat(String... projectNames);
      ProjectDescriptor project(File projectDir) throws UnknownProjectException;
    }
    
  • Settings类中,比较常用的方法就是 include(String… projectPaths) 方法,用于添加参与构建的模块,ProjectDescriptor project(File projectDir) 方法也是比较常用,当子模块不和settings.gradle脚本同一个目录时需使用相对路径描述,比如: project(':user').projectDir = File(rootDir, "user-module"),user模块的目录只向根目录先的user-module目录。

  • 前面有提到过Settings类实现PluginAware(插件相关接口),所以可以通过 Settings.pluginManagement方法的配置插件构建过程中需要的插件仓库:

    pluginManagement {
      // 对应PluginManagementSpec类
      repositories { // 管理Plugin Repository(仓库)
            google { url "https://maven.aliyun.com/repository/gradle-plugin" }
      }
    
      resolutionStrategy {
           eachPlugin {  
           // 替换模块
          if (requested.id.namespace == "com.github.plugin") {
              useModule("com.xxxxxx.plugin:${requested.version}")
          }
          // 统一插件版本
          if (requested.id.id == "com.android.tools.build") {
              useVersion("4.1.2")
          }
        }
      }
    }
    

配置阶段(Configuration)

1.Build Script(构建脚本build.gradle脚本)

build.gradle用于配置模块的构建所需要信息,分为根目录的 Root Build Script 和 子模块的 Module Build Script。

1.1、Root Build Script

  • 根目录的 Root Build Script ,一般是对所有子模块进行全局的统一的配置,build.gradle 脚本负责整体项目的基本配置,对应的是Project类,我们称之为:rootProject;
  • Gradle 在构建的时候,会根据根目录下的build.gradle脚本生成 Project 对象,所以在 build.gradle 里写的 dsl,其实都是 Project 接口的一些方法,Project具体的实现类是DefaultProject类。

1.2、Module Build Script

  • 子模块的 Module Build Script,对当前子模块进行配置,同样的对应的是Project类,我们称之为:childProject;子项目和根项目的配置基本是相似,不过在子项目里有一个明显的区别就是引用 apply plugin "com.android.application",而 androiddsl 就是 application 插件的 extension,关于 android plugin dsl 可以看 android-gradle-dsl
1.2.1 插件引入

Gradle插件是什么,官方解释

Gradle at its core intentionally provides very little for real world automation. All of the useful features, like the ability to compile Java code, are added by plugins. Plugins add new tasks (e.g. JavaCompile), domain objects (e.g. SourceSet), conventions (e.g. Java source is located at src/main/java) as well as extending core objects and objects from other plugins.

  • 简单点说就是,Gradle自身并没有提供多少真实可用的功能,它只是一个负责定义流程和规则的框架,具体的功能都是由插件来完成的,比如:编译Java 则引用用java插件,编译Kotlin用kotlin插件,而这些插件并不是Gradle中自带的功能,而是有第三方作者定义的插件,所以说Gradle只负责定义流程和规则的框架。
1.2.1 .1 插件类型

Gradle中有两种通用的插件类型,即二进制插件和脚本插件。

  • 二进制插件: 通过实现Plugin接口以编程的方式编写;
  • 脚本插件:使用Gradle DSL语言以声明方式编写,通常存在于另一个脚本文件中的一段脚本代码;

示例:
引用二进制插件:

apply plugin: 'com.android.application'

应用工程目录下的脚本插件:

 apply from: rootProject.file("script_plugin.gradle")
1.2.2 插件属性配置和方法调用

如果项目中应用了某个插件,就可以使用此插件提供的DSL进行配置,以此干预模块的构建过程。以com.android.application插件构建为例:

 // 引入android.application插件 
 apply plugin: 'com.android.application'

android {
  compileSdkVersion 30
  buildToolsVersion "30.0.3"

  defaultConfig {
    applicationId "com.github.gradle"
    minSdkVersion 21
    targetSdkVersion 30
    versionCode 1
    versionName "1.0"
}
buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
                'proguard-rules.pro'
    }
}

sourceSets {
    main {}
}
}

关于自定义插件请移步Gradle+Transform+Asm自动化注入代码

Gradle 相关API介绍

由于Gradle类的方法属性很多,Gradle API文档,我就挑几个平时可能会常用的的方法说一说:

API 描述
void beforeProject(Closure closure) 当每个Project对象配置之前被调用,包括根Project
void afterProject(Closure closure) 当每个Project对象配置完毕之后被调用,包括根Project
buildStarted(Closure closure) 当构建开始时回调,这个一般是看不到打印的
settingsEvaluated(Closure closure) 当Settings对象配置完成时回调
projectsLoaded 在初始化阶段中所有的Project对象创建完成时回调
projectsEvaluated 当所有的Project配置完成时回调
buildFinished 当构建完成时回调
addBuildListener 添加构建监听
getRootProject 返回Root Project
TaskExecutionGraph getTaskGraph() 返回此次构建TaskExecutionGraph,此对象在taskGraph.whenReady { } 后才具有内容,因为task还没填充完毕。
String getGradleVersion() 执行此次构建的Gradle版本
File getGradleUserHomeDir() Gradle User Home目录
File getGradleHomeDir() 执行此次构建的Gradle目录
Project getRootProject() 获取当前构建的根项目
StartParameter getStartParameter() 获取传入当前构建的所有参数

TaskExecutionGraph 相关API介绍

TaskExecutionGraph负责管理作为构建部分的Task实例的执行。 TaskExecutionGraph维护要执行(或已执行)的任务的执行计划,您可以从构建文件中查询该计划。 您可以通过调用Gradle.getTaskGraph()访问TaskExecutionGraph。在构建文件中,您可以使用gradle.taskGraph对其进行访问。 仅在配置了此次构建中的所有Project之后,才填充TaskExecutionGraph。在此之前是空的。当填充Graph时,可以使用whenReady或addTaskExecutionGraphListener方法接收通知。

API 描述
addTaskExecutionGraphListener 向TaskExecutionGraph添加TaskExecutionGraphListener监听,在填充TaskExecutionGraph之后以及执行任何任务之前调用此方法
whenReady(Action<TaskExecutionGraph> action) 当TaskExecutionGraph填充完成时被调用
addTaskExecutionListener 向TaskExecutionGraph添加TaskExecutionListener监听
beforeTask(Action<Task> action) 当TaskExecutionGraph管理的某个任务执行之前被调用
afterTask(Action<Task> action) 当TaskExecutionGraph管理的某个任务执行完成之后被调用
hasTask(Task task) 查询TaskExecutionGraph是否存在该task
getAllTasks 获取TaskExecutionGraph管理的所有的Task
Set getDependencies(Task task) 返回TaskExecutionGraph管理的与参数Task的依赖关系

Project相关API介绍

该接口是用于与构建文件中的Gradle对象交互的主要API。可以通过Projec对象以编程方式访问Gradle的所有功能。Project本质上是Task对象的集合

Project生命周期Lifecycle

Project对象和"build.gradle"文件之间存在一对一的关系。在构建初始化期间,Gradle 为要参与构建的每个模块创建一个Project对象,如下所示:

  • 为构建创建一个Settings实例。
  • 根据"settings.gradle"脚本(如果存在)配置Setttings对象。
  • 使用已配置的Settings对象创建Project实例的层次结构。
  • 通过执行"build.gradle"文件(如果存在)来配置对应的Project对象。这些Project对象以广度顺序进行配置,因此在其子项目之前先对其进行配置。可以通过调用EvaluationDependsOnChildren()或通过使用EvaluationDependsOn(String)添加显式配置依赖项来覆盖此顺序。

这里仅仅只介绍常用的API,详细API请看-官方文档

API 描述
void beforeEvaluate(Action <? super Project> action) 在配置该Project之前立即调用方法。
void afterEvaluate(Action <? super Project> action) 在配置该Project之后立即调用方法。
getRootProject() 获取根Project
getRootDir 返回该Project根目录。根目录是根Project的Project目录。
getBuildDir 返回该项目的构建目录,构建目录是构建过程中build产物都放入到这个里面,构建目录的默认值为projectDir / build。
setBuildDir(File path) 设置该Project的构建目录。构建目录是构建过程中build产物都放入到这个里面。
getParent() 获取此Project的父Project
getChildProjects 获取此Project的所有直接子Project
setProperty(String name, 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
hasProperty(String propertyName) 查看是否存在此属性
getProperties() 获取所有属性
dependencies(Closure configureClosure) 为Project配置依赖项
buildscript(Closure configureClosure) 为该Project配置构建脚本classpath。
getTasks() 返回此Project中所有的tasks
WorkResult copy(Action<? super CopySpec> action) 复制指定的文件.
CopySpec copySpec(Action<? super CopySpec> action) 创建一个CopySpec,以后可以将其用于复制文件或创建存档。给定的操作用于在此方法返回CopySpec之前对其进行配置。
delete<? super DeleteSpec> action) 删除指定的文件。
Task task(String name, Closure configureClosure) 使用给定名称创建一个Task并将其添加到该Project中。
Task task(Map<String,?) args,String name) 使用给定名称创建一个Task并将其添加到该项目中。可以将创建参数的Map传递给此方法,以控制Task的创建方式

Task介绍

Project本质上是Task对象的集合。每个任务执行一些基本工作,例如编译类,运行单元测试或压缩WAR文件。可以使用TaskContainer的create方法将任务添加到Project中。还可以使用TaskContainer上的查找方法,例如:TaskCollection.getByName(String)方法查找现有Task。

每个Task都会附属一个Project,可以在TaskContainer上使用各种方法来创建和查找任务实例。例如,TaskContainer.create(String)使用给定名称创建一个空任务。您还可以在构建文件中使用task关键字:

task myTask
task myTask1 { Task task ->}
task myTask2(type: JavaCompile)
task myTask3(type: JavaCompile) { Task task -> }
  • Task由一系列Action对象组成。执行Task时,通过调用Action.execute(T)依次执行每个Action。通过调用doFirst(Action)或doLast(Action)将操作添加到任务中。

    task myTaskAction {Task task->
            task.doFirst(new Action<Task>() {
            @Override
            void execute(Task taskAction) {
                      println("execute task>>>>>>>>>>>>>>>>操作")
             }
      })
    }
    
  • Groovy闭包也可以用于提供Task 的Action。当执行Action时,以Task做为参数调用闭包。通过调用doFirst(Closure)或doLast(Closure)将Action闭包添加到任务中。

      task myTaskClosure { Task task->
          task.doFirst{
       }
        task.doLast {
              println("execute task closure>>>>>>>>>>>>>>>>操作")
        }
      }
    
  • 一个Task可能依赖于其他Task,或者可能被安排为始终在另一个Task之后运行。任务的依赖关系使用dependsOn(Object ...)或setDependsOn(Iterable)进行控制。

task myTaskDepends { Task task ->
      task.doLast {
    println("execute task: " + task.getName())
}
}

task myTaskDepends1 { Task task ->
task.dependsOn("myTaskDepends")
task.doLast {
    println("execute task: " + task.getName())
}
}


task myTaskDepends2 { Task task ->
task.setDependsOn([myTaskDepends,"myTaskDepends1"])
task.doLast {
    println("execute task: " + task.getName())
}
}

特点:

  • 一个Task可能依赖于其他Task,或者可能被安排为始终在另一个Task之后运行,如:taskA dependsOn taskB ,那么当执行taskA 就必须会优先执行taskB,不管是否同时执行。但是如果只执行taskB,那么taskA并不会执行。

task排序

在某些情况下, 我们希望能控制Task的执行顺序, 这种控制并不是像上面显式地加入(dependsOn(Object ...)或setDependsOn(Iterable)进行控制)依赖关系。 最主要的区别是我们设定的排序规则不会影响那些要被执行的任务, 只是影响Task执行的顺序本身.

task myTaskX { Task task ->
task.doLast {
    println("execute task: " + task.getName())
}
}

task myTaskY { Task task ->
task.doLast {
    println("execute task: " + task.getName())
}
}
myTaskY.mustRunAfter(myTaskX)

gradlew myTaskY myTaskX -q 的输出:

execute task: myTaskX
execute task: myTaskY

注意 B.mustRunAfter(A) 或者 B.shouldRunAfter(A) 并不影响任何Task间的执行tasks AtaskB可以被独立的执行。排序规则只有当 2 个任务同时执行时才会生效。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容