磨刀不误砍柴工:Gradle 语法浅谈——从 Groovy 闭包到 Android 构建脚本

发布时间:2026/10/10 2:39:10
磨刀不误砍柴工:Gradle 语法浅谈——从 Groovy 闭包到 Android 构建脚本 文档教程知识库【免费下载链接】android-tech-frontier【停止维护】一个定期翻译国外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目项目地址https://gitcode.com/gh_mirrors/an/android-tech-frontier点击查看免费下载本文是「开发技术前线」第 19 期收录的 Gradle 语法入门指南原文作者 Pavlo Dudka译者 FTExplore校对 chaossss。它承接同系列「Gradle 小知识」中关于 Task 的讲解聚焦 Gradle 构建脚本的语法根基Groovy 闭包、闭包传参、Project 与 Script blocks 机制并给出一个通过testOptions自定义测试报告目录的实战练习。读完本文你将能读懂任何一段build.gradle的写法逻辑并学会在官方文档中精准定位 Android 插件的配置项从而自主完成项目的定制化配置。回顾从 Task 到语法在上一篇同系列文章Gradle小知识#1tasks中我们已经讨论了 Gradle 构建的基本单位 Task以及构建过程经历的配置阶段与执行阶段两个生命周期任务中的顶层代码在配置阶段就会被求值——这就是为什么执行gradle tasks时也会打印出任务顶层println输出的原因只有通过doLast { ... }或简写指定的动作才会在任务真正被调用时执行。在掌握了 Task 之后我们面临的下一道门槛就是build.gradle的语法。很多人拿到一份复杂的构建脚本会一头雾水并不是因为逻辑难懂而是因为不熟悉它背后的语言。本文就来专门解决这个问题——理解了语法阅读、学习或编写 Gradle 脚本的效率都会大幅提升正所谓磨刀不误砍柴工。引言Gradle 是 Groovy 实现的构建工具Gradle 是使用Groovy语言实现的构建工具。Groovy 是运行在 JVM 平台上的一门敏捷开发语言其语法与 Java 有诸多类似之处但同时又具备一些 Java 没有的概念需要细细体会。好消息是Groovy 的语法和 Java 很像只要熟悉 Java理解起来不会有太大障碍。在 Gradle 的世界里最需要理解的概念只有一个——闭包Closure。它是 Groovy 中最重要、也最让新手困惑的概念之一。下面我们就从闭包讲起。1、闭包的基本语法简单地说闭包Closures就是一段代码块这段代码块可以接受参数并具有返回值。有一点要特别注意闭包往往不是在需要使用的时候才临时写出来这么一段代码就像 Java 的匿名类那样而是通过def关键字声明一个变量来代表闭包然后在需要的时候直接使用该变量。一个简单的 Hello World 闭包def myClosure { println Hello world! } //execute our closure myClosure()output: Hello world!接受一个参数的闭包def myClosure {String str - println str } //execute our closure myClosure(Hello world!)output: Hello world!单参数闭包的隐式参数 it如果闭包只接受一个参数那么这个参数在代码块中可以直接用it代替无需声明def myClosure {println it } //execute our closure myClosure(Hello world!)output: Hello world!接受多个参数的闭包def myClosure {String str, int num - println $str : $num } //execute our closure myClosure(my string, 21)output: my string : 21注意上面用到了 Groovy 的字符串模板语法$str : $num这与 Java 的字符串拼接不同是 Groovy 原生的特性。参数类型可以省略闭包里面的参数类型可以省略不写让 Groovy 自行推断def myClosure {str, num - println $str : $num } //execute our closure myClosure(my string, 21)output: my string : 21闭包可以直接访问上下文变量闭包还有一个比较酷的写法可以直接调用 context上下文里面的变量。默认的 context 就是创建这个闭包的类classdef myVar Hello World! def myClosure {println myVar} myClosure()output: Hello world!注意myVar并不是闭包的参数而是在闭包创建时所在的类中定义的变量闭包在执行时会去上下文环境中查找它。用 setDelegate 改变闭包的上下文上面提到闭包可以直接调用 context 的变量而这个 context 可以通过setDelegate()方法来改变这极大地增加了闭包的灵活性def myClosure {println myVar} //Im referencing myVar from MyClass class MyClass m new MyClass() myClosure.setDelegate(m) myClosure() class MyClass { def myVar Hello from MyClass! }output: Hello from MyClass!在这个例子中闭包创建时myVar变量并不存在于创建它的类中但执行之前我们把闭包的上下文delegate切换成了MyClass的实例于是myVar就被解析为Hello from MyClass!。这套在执行时切换闭包上下文的机制正是理解 Gradle 脚本块Script blocks的钥匙下文会反复用到。2、闭包可以作为参数进行传递在 Groovy 中将闭包作为参数传递进函数是将逻辑进行分离解耦的重要手段。在上面的例子中我们已经尝试了如何调用闭包。下面总结一下传递闭包给方法的六种写法编号写法说明1myMethod(myClosure)接受一个参数的函数常规写法2myMethod myClosure函数只接受一个参数时括号可以忽略3myMethod {println Hello World}以插入语内联的形式创建并传入闭包4myMethod(arg1, myClosure)函数接受两个参数5myMethod(arg1, { println Hello World })两个参数闭包内联创建6myMethod(arg1) { println Hello World }存在多个参数且最后一个参数是闭包时闭包可以不写在括号内细心的朋友应该已经发现第三种和第六种写法正是 Gradle 构建脚本中司空见惯的形式。比如dependencies { ... }、android { ... }这种写法本质就是把闭包作为最后一个参数传给方法并把它从括号里拿出来。Gradle解析一段真实的构建脚本在知道了 Groovy 的基本语法尤其是闭包之后下面以一个简单的 Gradle 脚本为例具体感受一下buildscript { repositories { jcenter() } dependencies { classpath com.android.tools.build:gradle:1.2.3 } } allprojects { repositories { jcenter() } }结合前文的例子我们可以很容易理解到buildscript是一个接受闭包作为参数的函数这个函数会在编译的时候被 Gradle 调用其定义类似于def buildscript(Closure closure)同理allprojects也是一个接受闭包作为参数的函数。脚本内部的repositories { jcenter() }与dependencies { classpath ... }同样是函数 闭包的调用形式repositories闭包告诉 Gradle 从哪个仓库这里是jcenter()拉取依赖dependencies闭包则通过classpath配置声明构建脚本自身的依赖——例如 Android Gradle 插件com.android.tools.build:gradle:1.2.3。那么问题来了这些函数具体会在什么时候被 Gradle 调用呢要回答这个问题就需要介绍另一个知识点——Project。Project构建脚本的执行主体理解 Gradle 配置文件中的 script 如何被调用的关键就是理解 Project 的相关概念。在 Gradle 执行某个任务的时候会按照各个 task 的依赖关系依次执行而执行这些 task 的对象就是Project。说得再通俗一些Project 就是你希望 Gradle 为你做的事情而要完成这些事情需要将事情分成一步一步的步骤这些步骤就是task任务。用同系列文章Gradle小知识#2学学语法里更严谨的说法来表述构建脚本中所有的顶级语句都会被代理到Project实例上。也就是说我们在build.gradle里写的每一个顶层方法调用buildscript { }、allprojects { }等最终都是在一个Project实例上执行的方法。如果你想知道某个顶层方法到底做了什么事去Project类的文档中查找即可。Script blocks脚本块到底是什么通过前文的学习我们已经很清楚地了解到Script block脚本块就是一段接受闭包的函数这些函数会被 Project 调用。官方文档对这一术语的定义是脚本块就是接收一个闭包为参数的回调方法。默认情况下Gradle 已经准备好了很多脚本块script block用于我们对项目进行配置例如buildscript { ... }。同时你也可以自己写出符合规范的 task 来在编译过程中被调用。脚本块内部的代理机制以buildscript为例查阅它的文档会发现一句话从 buildscript 代理到ScriptHandler。也就是说在执行阶段作为参数传进去的闭包会在ScriptHandler实例的作用域内执行——我们在闭包中调用的repositories(Closure)和dependencies(Closure)方法实际上是ScriptHandler类上的方法。顺着这个思路继续追查dependencies(Closure)的定义是给脚本配置依赖它会在DependencyHandler上执行给定的闭包。所以闭包{ classpath com.android.tools.build:gradle:1.2.3 }的真实含义就是用classpath这个配置把com.android.tools.build:gradle:1.2.3声明为一个依赖。这里有一个贯穿整个 Gradle 的核心术语务必牢记代理到某某某和配置某某某这两句话完全是一个意思——闭包会在某个指定的类上执行。Gradle 中大量用到这种代理策略理解了它你就能看懂任何脚本块内部的调用链。android 脚本块它从哪里来下面我们先看一下 Android Studio 中默认的脚本位于项目的app/build.gradleapply plugin: com.android.application android { compileSdkVersion 22 buildToolsVersion 22.0.1 defaultConfig { applicationId com.trickyandroid.testapp minSdkVersion 16 targetSdkVersion 22 versionCode 1 versionName 1.0 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile(proguard-android.txt), proguard-rules.pro } } }按照我们已经有的知识上面的脚本说明有一个名称为android的函数该函数接收闭包作为参数。然而在 Gradle 官方文档的Project类中并不存在这个函数。那么android脚本块怎么会出现在这里呢答案就在最上面的apply plugin: com.android.application。Project中默认定义了一些脚本块但你可以通过 Gradle 插件定义新的脚本块。Android 应用插件扩展了 Project定义了android脚本块接收闭包作为参数并把闭包代理到AppExtension类上。由此可以得出一个非常实用的判读方法如果你在构建脚本中看到类似something { ... }的代码但在 Gradle 官方文档中却找不到这个脚本块那么它极大概率来自某个插件。查文档时应该先去查对应插件的 DSL 文档。Android 插件的 DSL 文档怎么查既然 Gradle 官方文档中没有android相关的脚本信息我们该去哪里查阅呢可以前往 Android 官方开发者网站下载android-gradle-plugin-dsl文档压缩包文件名形如android-gradle-plugin-dsl.zip。下载解压后会发现一个 HTML 格式的文档名字是AppExtension。这份文档主要介绍的就是Android configuration blocks——即在 Gradle 官方文档中没有的、关于 Android 配置的各种 Gradle 脚本块都可以在这里查阅。举几个例子compileSdkVersion文档中的描述是Required. Compile SDK version.——即 Gradle 进行 Android 构建所必需用于描述编译时使用的 SDK 版本buildToolsVersion文档中的描述是Required. Version of the build tools to use.——同样是构建所必需告诉 Gradle 使用哪个版本的 build toolsdefaultConfig是一个脚本块其闭包被代理到ProductFlavor类applicationId、minSdkVersion、targetSdkVersion、versionCode、versionName等属性都在其中配置buildTypes同样是一个脚本块release、debug等构建类型、minifyEnabled混淆开关、proguardFiles混淆规则文件等都在这里配置。掌握了查Project→ 查插件 DSLAppExtension→ 查代理目标类这条检索路径你就能在官方文档中精准定位每一个配置项的含义与默认值。练习用 testOptions 自定义测试报告输出目录有了前文的学习作为基础我们已经了解了 Gradle 语法以及 Android 插件的脚本查阅方法。接下来我们实际运用这些知识对 Android 项目进行一些自定义配置。在上述 AppExtension 文档中我们可以查到一个脚本块叫testOptions。它代表TestOptions类的调用TestOptions类里有三个属性reportDir、resultsDir和unitTests。其中reportDir就是测试报告最后保存的位置我们现在就来改一下这个地方android { ...... testOptions { reportDir $rootDir/test_reports } }这里使用了$rootDir/test_reports作为测试结果的储存位置$rootDir是 Project 的一个属性指向项目的根目录。现在如果我们通过命令行执行./gradlew connectedCheckGradle 就会进行一系列的测试程序并且将测试报告保存在项目根目录下的test_reports目录中。注意一点这个关于测试的小例子不要用在真实的生产环境中尽量保持项目结构的清洁。因为所有构建产物默认都应该放在build目录下connectedCheck产生的测试报告默认也是如此——自定义到项目根目录虽然可行但会污染项目结构。延伸让测试日志直接输出到控制台testOptions脚本块中的unitTests属性unitTests.all { ... }还有一个很实用的用途让单元测试的执行事件直接打印到控制台。在 Gradle小知识#4把单元测试的日志打印到控制台 中给出了两种做法其中一种同样是在android脚本块内配置android { ... testOptions.unitTests.all { testLogging { events passed, skipped, failed, standardOut, standardError outputs.upToDateWhen { false } showStandardStreams true } } }配置完成后运行./gradlew test就能看到类似com.trickyandroid.testproj.ExampleUnitTest exampleTest1 PASSED的逐条测试结果而不是默认的安静执行。小结本文的核心脉络可以归纳为一条理解链闭包是 Groovy 的灵魂——一段可传参、可返回、可赋值、可切换执行上下文delegate的代码块闭包传参的六种写法——尤其是最后一个参数是闭包时可移出括号的语法构成了 Gradle 脚本块的字面形态Script block 的本质——接收闭包的回调方法闭包会被代理到某个类上执行代理到某某某配置某某某Project 是执行主体——构建脚本的所有顶层语句都代理到 Project 实例插件可以定义新的脚本块——android脚本块来自com.android.application插件其配置项全部记录在 AppExtensionandroid-gradle-plugin-dsl文档中实战验证——通过testOptions { reportDir $rootDir/test_reports }完成一次真实的定制配置。沿着这条链路你可以继续深入本仓库中的同系列文章Gradle小知识#1tasks任务的配置/执行阶段、Gradle小知识#3任务的顺序dependsOn、mustRunAfter、finalizedBy控制任务执行顺序、Gradle小知识#4把单元测试的日志打印到控制台测试日志配置以及 Gradle提示和使用技巧BuildConfig 字段、构建类型定制、签名信息管理等进阶实战。掌握了语法这把钥匙剩下的就是一路畅通。赞分享文档教程知识库【免费下载链接】android-tech-frontier【停止维护】一个定期翻译国外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目项目地址https://gitcode.com/gh_mirrors/an/android-tech-frontier点击查看免费下载相关推荐Gradle 脚本语法完全指南从 Groovy 闭包到 Project 与脚本块Android 技术前线 Gradle 系列第 2 篇Gradle 脚本语法完全指南从 Groovy 闭包到 Project 与脚本块Android 技术前线 Gradle 系列第 2 篇 本文是 andro文档教程知识库在 Gradle 构建脚本中直接使用 Jib Core用 Groovy 代码构建与修改 Java 容器镜像在 Gradle 构建脚本中直接使用 Jib Core用 Groovy 代码构建与修改 Java 容器镜像 Jib Core 是面向 JVM 语言的容器化库开发工具构建工具云原生LGSTrayBattery无线罗技鼠标电池监控的终极解决方案LGSTrayBattery无线罗技鼠标电池监控的终极解决方案 你是否厌倦了不断猜测无线罗技鼠标的剩余电量或者经常在关键时刻发现鼠标突然没电LGSTray桌面应用上一篇OceanBase 数据库架构深度解析从 Shared-Nothing 集群到多租户与日志流下一篇在 Rolldown 中利用 resolveId 的 isEntry 参数为入口点注入 Polyfill 代理模块创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询